(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
我知道,对于(1),List接口的实现可以交换。似乎(1)通常在应用程序中使用,无论是否需要(我自己总是使用它)。
我想知道是否有人使用(2)?
此外,情况实际上需要使用(1)而不是(2)的频率是多少(请给我一个例子)(即(2)是不够的..除了编码到接口和最佳实践等)。
(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
我知道,对于(1),List接口的实现可以交换。似乎(1)通常在应用程序中使用,无论是否需要(我自己总是使用它)。
我想知道是否有人使用(2)?
此外,情况实际上需要使用(1)而不是(2)的频率是多少(请给我一个例子)(即(2)是不够的..除了编码到接口和最佳实践等)。
当前回答
如果code是列表的“所有者”,我使用(2)。例如,对于局部变量就是如此。没有理由使用抽象类型List而不是ArrayList。 另一个展示所有权的例子:
public class Test {
// This object is the owner of strings, so use the concrete type.
private final ArrayList<String> strings = new ArrayList<>();
// This object uses the argument but doesn't own it, so use abstract type.
public void addStrings(List<String> add) {
strings.addAll(add);
}
// Here we return the list but we do not give ownership away, so use abstract type. This also allows to create optionally an unmodifiable list.
public List<String> getStrings() {
return Collections.unmodifiableList(strings);
}
// Here we create a new list and give ownership to the caller. Use concrete type.
public ArrayList<String> getStringsCopy() {
return new ArrayList<>(strings);
}
}
其他回答
我认为使用(2)的人不知道利斯科夫替换原则或依赖反转原则。或者必须使用数组列表。
如果code是列表的“所有者”,我使用(2)。例如,对于局部变量就是如此。没有理由使用抽象类型List而不是ArrayList。 另一个展示所有权的例子:
public class Test {
// This object is the owner of strings, so use the concrete type.
private final ArrayList<String> strings = new ArrayList<>();
// This object uses the argument but doesn't own it, so use abstract type.
public void addStrings(List<String> add) {
strings.addAll(add);
}
// Here we return the list but we do not give ownership away, so use abstract type. This also allows to create optionally an unmodifiable list.
public List<String> getStrings() {
return Collections.unmodifiableList(strings);
}
// Here we create a new list and give ownership to the caller. Use concrete type.
public ArrayList<String> getStringsCopy() {
return new ArrayList<>(strings);
}
}
将HashSet或TreeSet的引用存储在Set类型的变量中被认为是很好的风格。
Set<String> names = new HashSet<String>();
这样,如果您决定使用TreeSet,则只需更改一行。
同样,操作Set的方法应该指定Set类型的参数:
(Set<String> s)
然后,该方法可用于所有set实现。
理论上,我们应该对链表提出同样的建议,即保存 LinkedList在List类型变量中的引用。然而,在Java库中,List接口对于ArrayList和LinkedList类都是通用的。特别是,它有用于随机访问的get和set方法,尽管这些方法对于链表来说效率非常低。
如果你不知道随机访问是否有效,你就不能写出有效的代码。
这显然是标准库中的一个严重设计错误,我不建议使用 List接口就是因为这个原因。
看看这个错误有多尴尬吧 Collections类的binarySearch方法的源代码。这个方法需要 列表参数,但二分搜索对链表没有意义。然后代码就笨拙了 尝试发现列表是否是链表,然后切换到线性搜索!
Set接口和Map接口设计得很好,您应该使用它们。
当你写List时,你实际上告诉,你的对象只实现了List接口,但你没有指定你的对象属于什么类。
在编写ArrayList时,指定对象类是一个可调整大小的数组。
因此,第一个版本使您的代码将来更加灵活。
看看Java文档:
类ArrayList——List接口的可调整大小的数组实现。
接口列表-一个有序的集合(也称为序列)。该接口的用户可以精确控制每个元素在列表中的插入位置。
数组—容器对象,保存固定数量的单一类型的值。
我想知道是否有人使用(2)?
是的。但很少有合理的理由(我觉得)。
有些人因为在应该用List的时候用了数组列表而被惹恼了
像Collections.singletonList(…)或Arrays.asList(…)这样的实用方法不会返回数组列表。 List API中的方法不能保证返回相同类型的列表。
例如,有人被烧伤,在https://stackoverflow.com/a/1481123/139985的海报有“切片”的问题,因为ArrayList.sublist(…)没有返回一个数组列表…他设计的代码使用数组列表作为所有列表变量的类型。他最终通过将子列表复制到一个新的数组列表中来“解决”这个问题。
您需要知道List如何行为的参数在很大程度上是通过使用RandomAccess标记接口来解决的。是的,这有点笨拙,但另一种选择更糟糕。
此外,在这种情况下,实际使用(1)而不是(2)的频率是多少(即(2)是不够的..除了“编码到接口”和最佳实践等。)
“多久一次”这部分问题客观上是无法回答的。
(我能举个例子吗)
偶尔,应用程序可能会要求您使用不在List API中的ArrayList API中的方法。例如,ensureCapacity(int), trimToSize()或removeRange(int, int)。(最后一个问题只会出现在你创建了ArrayList的子类型并声明该方法为public的情况下。)
在我看来,这是针对类而不是针对接口进行编码的唯一合理理由。
(从理论上讲,你的性能可能会有轻微的提高……在某些情况下……在某些平台上……但除非你真的需要最后的0.05%,否则不值得这么做。在我看来,这不是一个合理的理由。)
如果你不知道随机访问是否有效,你就不能写出有效的代码。
这是一个有效的观点。然而,Java提供了更好的处理方法;如。
public <T extends List & RandomAccess> void test(T list) {
// do stuff
}
如果你用一个没有实现RandomAccess的列表调用它,你会得到一个编译错误。
你也可以动态测试…使用instanceof…如果静态类型太尴尬。您甚至可以根据列表是否支持随机访问来编写使用不同算法的代码(动态地)。
注意,ArrayList并不是唯一实现RandomAccess的列表类。其他包括CopyOnWriteList, Stack和Vector。
我看到人们对Serializable也有同样的看法(因为List没有实现它)…但是上面的方法也解决了这个问题。(在使用运行时类型完全可以解决的范围内。如果数组列表中有任何元素是不可序列化的,则数组列表序列化将失败。)
最后,我不会说“因为它的风格很好”。这个“理由”既是一个循环论证(“为什么这是‘好风格’?”),也是对一个未明说(可能根本不存在!)的更高权威的诉求(“谁说这是‘好风格’?”)。
(我确实认为根据界面编程是一种很好的风格,但我不会以此作为理由。你最好先了解真正的原因,然后自己得出正确的结论。正确的结论可能不总是一样的……视上下文而定。)