我前几天去面试,被问到这样的问题。
问:反转链表。给出以下代码:
public class ReverseList {
interface NodeList {
int getItem();
NodeList nextNode();
}
void reverse(NodeList node) {
}
public static void main(String[] args) {
}
}
我很困惑,因为我不知道接口对象可以用作方法参数。面试官稍微解释了一下,但我还是不太确定。有人可以启发我吗?
这实际上是使用界面的最常见和最有用的方法之一。接口定义了一个契约,您的代码可以与任何实现该接口的类一起使用,而不必知道具体的类 - 它甚至可以与编写代码时尚不存在的类一起使用。
Java 标准 API 中有很多示例,尤其是集合框架中。例如, Collections.sort() 可以对实现
List
接口的任何内容(不仅仅是 ArrayList
或 LinkedList
,尽管实现您自己的 List
并不常见)及其内容实现 Comparable
接口进行排序(不仅仅是 String
或数字包装类 - 并且让您自己的类实现 Comparable
来实现此目的是 相当 常见)。
传递给方法的不是接口“对象”,仍然只是一个常规对象。这只是一种说法“此参数将接受任何支持此接口的对象”。它相当于接受基类类型的某个对象,即使您传递的是子类。
这称为接口编程。您不需要对节点列表的特定实现类进行编码,而是对所有这些实现所实现的接口进行编码。
这样,如果有人在您编写反向方法后编写了新的、更好的
NodeList
实现,您的代码仍然可以工作,并且您不必为 NodeList
的每个新实现调整代码。
参数需要一个对象,该类实现一个接口(参数)。
在 pseudo Java 中代码:
void reverse(NodeList node) {
// your code
}
等于:
reverse(x) {
if(x == null || x instanceof NodeList) {
// your code
}else throw new RuntimeException("Some sort of error.");
}
注意;在此处阅读有关接口的更多信息:http://java.sun.com/docs/books/tutorial/java/IandI/interfaceAsType.html
这是一种可能的实现:
public class ReverseList {
interface NodeList {
int getItem();
NodeList nextNode();
}
static class Node implements NodeList {
private int item;
private Node next;
@Override
public int getItem() {
return item;
}
public void setItem(int si) {
item = si;
}
@Override
public NodeList nextNode() {
return this.next;
}
public void setNext(Node n) {this.next=n;}
}
Node reverse(NodeList head) {
Node node = (Node) head;
Node previous = null;
while(node.nextNode() !=null) {
Node tempNext = (Node) node.nextNode();
node.setNext(previous);
previous = node;
node = tempNext;
}
node.setNext(previous);
return node;
}
public static void main(String[] args) {
//Initialization block
ReverseList rl = new ReverseList();
Node n1= new Node(); n1.setItem(1);
Node n2=new Node(); n2.setItem(2);
Node n3 =new Node(); n3.setItem(3);
n1.setNext(n2); n2.setNext(n3); n3.setNext(null);
//Reversing the list
System.out.println("Before reversal");
System.out.println(n1.getItem() +"->"
+ n1.nextNode().getItem() + "->"
+ n1.nextNode().nextNode().getItem() + "->"
+n1.nextNode().nextNode().nextNode());
rl.reverse(n1);
System.out.println("\nAfter reversal");
System.out.println(n3.getItem() +"->"
+ n3.nextNode().getItem() + "->"
+ n3.nextNode().nextNode().getItem() + "->"
+n3.nextNode().nextNode().nextNode());
}
}
程序输出:
Before reversal
1->2->3->null
After reversal
3->2->1->null
我很好奇是否可以通过使用匿名类来解决这个问题。有什么想法吗?
在学习 lambda 的东西时也有同样的困惑。 该视频没有解释这个概念,但它可以让您清楚地了解它如何将接口作为参数传递。
恕我直言,使用接口的主要好处是能够轻松测试。假设您有一个名为 PatientManager 的接口。
您可以为“CachingPatientManager”或“LDAPPatientManager”等可想象的事物编写特定的单元测试,用例可能有无数种。
好处是接口编程变得高度可重用和可测试。
您无法创建接口的实例(/object)。 是的,您可以将接口作为函数中的参数传递。但问题似乎不完整。接口不由任何类实现。缺了点什么。如果您尝试运行它,编译器将不会显示任何错误。
但是,在reverse()方法中,您需要创建一个实现NodeList接口的类的实例。我希望这是有道理的。
也许为时已晚,但我仍然在这里留下我的答案,因为我发现你的问题很有帮助。
当接口用作方法的参数类型时,意味着该方法接受任何包含该接口的实现的对象。
这种用法有一个很好的例子,就是当你尝试将
ArrayList
传递给方法时。如您所知,您可以将您的 ArrayList
传递为 List
,如下所示:
public int getListSize(List<String> myList){
return myList.size();
}
然后,这将使您能够灵活地传递任何实现 List 方法的对象。例如,您可以这样做:
List<String> myList1 = new ArrayList<>();
List<String> myList2 = new LinkedList<>();
List<String> myList3 = new Vector<>();
int size1 = getListSize(myList1); // Valid, myList1 is an ArrayList
int size2 = getListSize(myList2); // Valid, myList2 is a LinkedList
int size3 = getListSize(myList3); // Valid, myList3 is a Vector