(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) 不够……除了对接口和最佳实践进行编码等)
几乎总是List
首选,ArrayList
因为例如,List
可以翻译成 aLinkedList
而不会影响代码库的其余部分。
如果使用一种ArrayList
代替List
,则很难将ArrayList
实现更改为LinkedList
一种,因为ArrayList
在代码库中使用了特定的方法,这些方法也需要重组。
您可以在此处阅读有关List
实现的信息。
您可能从 开始ArrayList
,但很快发现另一种实现是更合适的选择。
我想知道是否有人使用(2)?
是的。但很少有正当理由(IMO)。
人们被烫伤是因为他们ArrayList
在应该使用的时候使用了List
:
实用方法喜欢Collections.singletonList(...)
或Arrays.asList(...)
不返回ArrayList
.
API中的方法List
不保证返回相同类型的列表。
例如有人被烧伤,在https://stackoverflow.com/a/1481123/139985中,张贴者在“切片”方面存在问题,因为ArrayList.sublist(...)
不返回ArrayList
... 并且他设计了他的代码以ArrayList
用作他所有的列表变量。他最终通过将子列表复制到新的ArrayList
.
您需要了解List
行为方式的参数在很大程度上通过使用RandomAccess
标记接口来解决。是的,它有点笨拙,但替代方案更糟。
此外,这种情况实际上需要使用 (1) 而不是 (2) 的频率(即,在哪里 (2) 不够……除了“对接口进行编码”和最佳实践等)
问题的“多久”部分客观上无法回答。
(我可以举个例子吗)
有时,应用程序可能会要求您使用ArrayList
API 中未包含在List
API 中的方法。例如ensureCapacity(int)
,trimToSize()
或removeRange(int, int)
。(只有当你创建了一个声明方法为 ArrayList 的子类型时,最后一个才会出现public
。)
这是对类而不是接口进行编码的唯一合理原因,IMO。
(理论上,您可能会在性能上获得轻微的提升......在某些情况下......在某些平台上......但除非你真的需要最后的 0.05%,否则不值得这样做。这不是合理的理由,IMO。)
如果您不知道随机访问是否有效,就无法编写有效的代码。
这是一个有效的观点。然而,Java 提供了更好的方法来处理这个问题;例如
public <T extends List & RandomAccess> void test(T list) {
// do stuff
}
如果您使用未实现的列表调用它,RandomAccess
您将收到编译错误。
如果静态类型太尴尬,您还可以动态测试...使用instanceof
...。您甚至可以根据列表是否支持随机访问来编写代码以(动态地)使用不同的算法。
请注意,这ArrayList
不是唯一实现的列表类RandomAccess
。其他包括CopyOnWriteList
和。Stack
Vector
我见过人们提出同样的论点Serializable
(因为List
没有实现它)......但上面的方法也解决了这个问题。(在某种程度上,它完全可以使用运行时类型来解决ArrayList
。如果任何元素不可序列化,则序列化将失败。)
最后,我不会说“因为它的风格很好”。这个“原因”既是一个循环论证(“为什么它是‘好风格’?”),也是对一个未说明的(并且可能不存在!)更高权威的呼吁(“谁说它是‘好风格’?”) .
(我确实认为对界面进行编程是一种很好的风格,但我不会将其作为理由。您最好了解真正的原因并自己得出(IMO)正确的结论。正确的结论可能并不总是相同的......取决于上下文。)
例如,您可能决定 aLinkedList
是您的应用程序的最佳选择,但后来ArrayList
出于性能原因决定可能是更好的选择。
采用:
List list = new ArrayList(100); // will be better also to set the initial capacity of a collection
代替:
ArrayList list = new ArrayList();
以供参考:
(主要为集合图发布)
在 Set 类型的变量中存储对 a或的引用被认为是一种很好的风格。HashSet
TreeSet
Set<String> names = new HashSet<String>();
这样,如果您决定改用 a ,则只需更改一行TreeSet
。
此外,对集合进行操作的方法应指定 Set 类型的参数:
public static void print(Set<String> s)
然后该方法可用于所有集合实现。
理论上,我们应该对链表做出同样的建议,即将 LinkedList 引用保存在 List 类型的变量中。ArrayList
但是,在 Java 库中,List 接口对于类和类都是通用的LinkedList
。特别是,它具有用于随机访问的 get 和 set 方法,尽管这些方法对于链表来说效率非常低。
如果您不知道随机访问是否有效,就无法编写有效的代码。
这显然是标准库中的一个严重设计错误,因此我不建议使用 List 接口。
要了解该错误有多令人尴尬,请查看Collections类的binarySearch
方法的源代码。该方法采用 List 参数,但二分查找对链表没有意义。然后代码笨拙地尝试发现列表是否是链表,然后切换到线性搜索!
Set
界面和界面,Map
设计得很好,你应该使用它们。
如果代码是列表的“所有者”,我使用 (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);
}
}
当您编写时List
,您实际上是在告诉您,您的对象List
只实现了接口,但您没有指定您的对象属于哪个类。
当您编写 时ArrayList
,您指定您的对象类是一个可调整大小的数组。
因此,第一个版本使您的代码在未来更加灵活。
查看 Java 文档:
类ArrayList
-List
接口的可调整大小的数组实现。
接口List
- 有序集合(也称为序列)。此界面的用户可以精确控制每个元素在列表中的插入位置。
Array
- 包含固定数量的单一类型值的容器对象。
(3) Collection myCollection = new ArrayList<?>();
我通常使用这个。只有当我需要 List 方法时,我才会使用 List。与 ArrayList 相同。你总是可以切换到更“窄”的界面,但你不能切换到更“宽”的界面。
我认为使用(2)的人不知道Liskov 替换原理或依赖倒置原理。或者他们真的必须使用ArrayList
.
实际上,在某些情况下,(2)不仅是首选,而且是强制性的,我很惊讶,这里没有人提到这一点。
序列化!
如果您有一个可序列化的类并且希望它包含一个列表,那么您必须将该字段声明为具体且可序列化的类型,ArrayList
因为List
接口没有扩展java.io.Serializable
显然大多数人不需要序列化并且忘记了这一点。
一个例子:
public class ExampleData implements java.io.Serializable {
// The following also guarantees that strings is always an ArrayList.
private final ArrayList<String> strings = new ArrayList<>();
在以下两个中:
(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
首先通常是首选。由于您将仅使用接口中的方法,因此它为您提供了将来使用例如List
其他一些实现的自由。因此,它使您与具体的实现脱钩。现在有两点值得一提:List
LinkedList
我想知道是否有人使用(2)
有时是的(很少阅读)。当我们需要属于实现ArrayList
但不属于接口的方法时List
。例如ensureCapacity
.
此外,这种情况实际上需要多久(我可以举个例子)使用(1)而不是(2)
几乎总是您更喜欢选项(1)。这是 OOP 中的经典设计模式,您总是尝试将代码从特定实现和程序解耦到接口。
列表是一个接口。它没有方法。当您在 List 引用上调用方法时,实际上在这两种情况下都会调用 ArrayList 的方法。
并且对于将来您可以更改List obj = new ArrayList<>
为 List obj = new LinkList<>
或其他实现List 接口的类型。
有人再次问这个问题(重复),这让我在这个问题上更深入了一点。
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
ArrayList<String> aList = new ArrayList<String>();
aList.add("a");
aList.add("b");
}
如果我们使用字节码查看器(我使用了http://asm.ow2.org/eclipse/index.html),我们会看到以下列表片段(仅列表初始化和分配):
L0
LINENUMBER 9 L0
NEW ArrayList
DUP
INVOKESPECIAL ArrayList.<init> () : void
ASTORE 1
L1
LINENUMBER 10 L1
ALOAD 1: list
LDC "a"
INVOKEINTERFACE List.add (Object) : boolean
POP
L2
LINENUMBER 11 L2
ALOAD 1: list
LDC "b"
INVOKEINTERFACE List.add (Object) : boolean
POP
对于alist:
L3
LINENUMBER 13 L3
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
ASTORE 2
L4
LINENUMBER 14 L4
ALOAD 2
LDC "a"
INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
POP
L5
LINENUMBER 15 L5
ALOAD 2
LDC "b"
INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
POP
不同之处在于list最终调用INVOKEINTERFACE而aList调用INVOKEVIRTUAL。根据 Bycode Outline Plugin 参考,
invokeinterface 用于调用在 Java 接口中声明的方法
而调用虚拟
调用除了接口方法(使用invokeinterface)、静态方法(使用invokestatic)和invokespecial处理的少数特殊情况之外的所有方法。
总之, invokevirtual 将objectref从堆栈中弹出,而对于 invokeinterface
解释器从操作数堆栈中弹出“n”个项目,其中“n”是取自字节码的 8 位无符号整数参数。这些项目中的第一项是 objectref,它是对其方法被调用的对象的引用。
如果我理解正确,区别基本上是每种方式如何检索objectref。
我知道(2)在哪里可以更好的唯一情况是使用 GWT,因为它减少了应用程序占用空间(不是我的想法,但谷歌网络工具包团队是这么说的)。但是对于在 JVM 中运行的常规 java (1) 可能总是更好。
我会说 1 是首选,除非
我的猜测是,在 99% 的情况下,您可以使用 List,这是首选。
removeAll
,或add(null)
List
接口有几个不同的类 -ArrayList
和LinkedList
. LinkedList
用于创建索引集合和ArrayList
- 创建排序列表。因此,您可以在参数中使用任何它,但您可以允许使用您的代码、库等的其他开发人员使用不同类型的列表,而不仅仅是您使用的列表,所以,在这个方法中
ArrayList<Object> myMethod (ArrayList<Object> input) {
// body
}
您只能将它与ArrayList
, notLinkedList
一起使用,但您可以允许List
在它使用方法的其他地方使用任何类,这只是您的选择,因此使用接口可以允许它:
List<Object> myMethod (List<Object> input) {
// body
}
在此方法参数中,您可以使用任何List
要使用的类:
List<Object> list = new ArrayList<Object> ();
list.add ("string");
myMethod (list);
结论:
尽可能在任何地方使用接口,不要限制您或其他人使用他们想要使用的不同方法。