关于Java接口和多态性

3
我刚刚在阅读Java文档时遇到了一个奇怪的案例。这里是Oracle关于Arrays.asList方法的Java文档链接:http://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html#asList(T...) 文档中有一个例子。
List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");

我的问题是,由于List是一个接口,为什么我们可以将stooges声明为'List',而不是实现List的具体子类(例如ArrayList或LinkedList)?
这是否意味着我们可以拥有一个接口类型的引用变量?这对我来说看起来很奇怪,因为我一直认为接口仅代表多态,我们永远不应该真正使用接口类型变量。
请问有人能给我一些线索吗?

3
我们绝不能真正使用接口类型变量,完全相反的说法是正确的!!! - A4L
我理解多态性需要将函数参数、返回类型和数组类型声明为接口类型变量。但对于我在问题中提到的情况,我感到非常困惑。 - Bpache
2
在Java中,局部变量、函数参数和返回类型之间几乎没有区别。将对象视为接口的能力是接口的主要特征之一。 - Osama Javed
@OsamaJaved - 小修正:「能夠將一個物件視為介面的實例...」 - Hot Licks
@OsamaJaved - 我知道这有点过于追求严谨,而且在与同事进行非正式交流时我也不会这样做。但是在这里,我们正在处理那些经常没有完全掌握区别的初学者,因此强调这一点非常重要。 - Hot Licks
@HotLicks 我完全同意你的观点,也感谢你的纠正。 - Osama Javed
5个回答

3
将List接口视为保证。任何实现List的类都将保证具有接口的方法。当Arrays.asList()返回一个List时,您实际上并没有得到一个接口,而是得到了一个确保实现了List接口中列出的方法的具体类
至于您所说的“我们实际上应该从不使用接口类型变量”,您实际上应该这样做。这被称为“面向接口编程”。如果您可以返回List而不是LinkedList之类的东西,那么它会更加灵活。您方法的调用者不会与您特定的内部实现耦合在一起,这可能使用并返回LinkedList。如果您想要返回ArrayList而不是LinkedList,则在某些时候调用者不必更改任何代码,因为他们只关心接口。 什么是“面向接口编程”?

需要注意的是,Serializable 是一个标记接口,有点奇怪。它不能保证方法存在,而是保证实现 Serializable 接口的类的创建者已经考虑了与类序列化相关的许多问题(重写 readObject/writeObject 方法,与其他序列化形式的兼容性以及其他问题 http://www.javapractices.com/topic/TopicAction.do?Id=45)。因此,Serializable 仍然提供了一个保证,就像 List 一样,但它不是关于方法签名的,而是关于语言的一项附加特性。

http://en.wikipedia.org/wiki/Marker_interface_pattern


1

在Java中,使用接口作为引用类型是一种完全合法的做法。例如,在其类内部,Serializable接口将会这样做,以便可以序列化传递给它的任何对象。

这也是Java提供类似于多重继承的方式。例如:

public interface A { }
public class B implements A {}

public class program {
     B bClass = new B();
     A aObject = (A)bClass;
}

那么,同一个对象可以用不同的引用类型进行引用,而不会破坏继承链!

0
接口定义了一个实现的“契约”或“规范”,即方法及其签名。因此,实现接口的类必须遵守该“契约”。这样,您可以更改实现而不影响使用接口声明变量的代码。
在您提到的示例中:
1. 除非查看代码,否则无法知道List接口的Arrays.asList使用哪种实现。那么你怎么知道要使用哪个?(请参阅列表接口的javadoc以查看它有哪些实现)
2. 实现可能会发生变化,如果Arrays.asList决定使用另一种实现,您的代码将被破坏。
3. 方法Arrays.asList的签名是返回List<T>,因此,如果您想将具体实现作为变量,则必须转换返回值,这是不好的做法,或者创建新的-比如说ArrayList-并将所有元素复制到其中,这只是不必要的开销。

0

Effective Java by Bloch 是一本关于Java最佳实践的好书。特别是第52条讲到了这个问题:“如果存在适当的接口类型...就应该使用接口类型声明。”

总的来说,为了最大限度地提高灵活性和可理解性,您应该使用最能反映上下文的类型,通常是接口。在您提供的示例中,确切的实现是否重要,还是只要它是一个List。当然,如果代码需要ArrayList特定的方法或者代码依赖于ArrayList特定的行为,那么请使用具体类。

有时会有例外情况,比如使用GWT-RPC,但这是出于实现原因。


0

这是多态性的一个很好的例子,如果你感兴趣,可以在这里查看Arrays.asList()的源代码Arrays.asList(T...a),你会发现它接受可变长度的输入,并定义了自己的私有静态具体类ArrayList来实现List接口,而不是使用众所周知的java.util.ArrayList或其他JavaCollection类型,这可能是为了使其更有效率或其他原因。你想要实现自己的类并将其返回给用户,而不会通过实现细节来压倒他,因为有一个接口可以让他通过你的私有类进行处理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接