Java 中的类型 List 与类型 ArrayList

(1) List<?> myList = new ArrayList<?>();


(2) ArrayList<?> myList = new ArrayList<?>();

我理解使用(1) ,可以交换 名单接口的实现。似乎(1)通常在应用程序中使用,而不考虑需要(我自己总是使用这个)。

我想知道是否有人使用(2) ?

另外,这种情况实际上需要使用(1)/(2)的频率是多少(我能举个例子吗)(也就是说,在哪里(2)是不够的。.除了 接口编码最佳做法等外。)

456880 次浏览

几乎总是首选List而不是ArrayList,因为,例如,List可以转换为LinkedList而不影响其余的代码库。

如果使用ArrayList而不是List,则很难将ArrayList实现更改为LinkedList,因为代码库中已经使用了ArrayList特定的方法,这也需要重新构造。

您可以阅读List实现在这里

您可以从ArrayList开始,但很快就会发现另一个实现是更合适的选择。

我会说1是首选,除非

  • 你依赖于ArrayList中可选行为*的实现,在这种情况下显式使用ArrayList更清楚
  • 你将在一个需要ArrayList的方法调用中使用ArrayList,可能用于可选的行为或性能特征

我的猜测是,在99%的情况下,您可以使用List,这是首选。

  • 例如removeAlladd(null)

我想知道是否有人使用(2)?

是的。但很少有合理的理由(我觉得)。

人们被烧伤了,因为他们在应该使用List的时候使用了ArrayList:

  • Collections.singletonList(...)Arrays.asList(...)这样的实用方法不会返回ArrayList

  • List API中的方法不保证返回相同类型的列表。

例如有人被烧伤,在https://stackoverflow.com/a/1481123/139985中,海报上的“切片”有问题;因为ArrayList.sublist(...)不返回ArrayList…他设计的代码使用ArrayList作为所有列表变量的类型。他最终“解决了”;通过复制子列表到一个新的ArrayList来解决这个问题。

您需要知道List如何行为的参数在很大程度上是通过使用RandomAccess标记接口来解决的。是的,这有点笨拙,但另一种选择更糟糕。

此外,在这种情况下,实际使用(1)而不是(2)的频率是多少(即(2)是不够的..除了“编码到接口”和最佳实践等。)

“how often"这个问题的一部分客观上是无法回答的。

(我能举个例子吗)

有时,应用程序可能要求您在ArrayList API中使用 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的列表类。其他包括CopyOnWriteListStackVector

我看到有人对Serializable(因为List没有实现它)提出了同样的论点……但是上面的方法也解决了这个问题。(就使用运行时类型的完全可以解决而言。如果任何元素不可序列化,ArrayList将序列化失败。)


最后,我不会说“because is good style”。,“reason"既是一个循环的论点(“为什么是“好风格”吗?”),也是对一个未明说(可能不存在!)的更高权威的呼吁(“说它是“好风格”?”)。

(我确实认为根据界面编程是一种很好的风格,但我不会以此作为理由。你最好了解真正的的原因,并为自己得出(IMO)正确的结论。正确的结论五月并不总是一样的…视上下文而定。)

(3) Collection myCollection = new ArrayList<?>();

我通常用这个。和只有如果我需要列表方法,我将使用列表。数组列表也是一样。你总是可以切换到“更窄”。界面,但你不能切换到更“宽”。

我所知道的可以更好的唯一情况是使用GWT,因为它减少了应用程序的占用空间(不是我的想法,但谷歌web工具包团队是这么说的)。但是对于在JVM(1)中运行的常规java来说,可能总是更好。

我认为使用(2)的人不知道利斯科夫替换原理依赖倒置原理。或者他们真的必须使用ArrayList

如果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);
}
}

被认为是好风格用于在Set类型的变量中存储对HashSetTreeSet的引用。

# EYZ0

这样,如果您决定使用TreeSet,则只需更改一行。

同样,操作Set的方法应该指定Set类型的参数:

# EYZ0

然后# EYZ0。

理论上,我们应该对链表做出同样的建议,即save LinkedList在List类型变量中的引用。然而,在Java库中,List接口对于ArrayListLinkedList类都是通用的。特别是,它有用于随机访问的get和set方法,尽管这些方法对于链表来说效率非常低

如果你不知道随机访问是否有效,你就不能写有效的代码

这显然是标准库中一个严重的设计错误,我不建议使用 List接口就是因为这个原因。< / p > 要知道这个错误有多尴尬,请看 < em > < / em >集合类的binarySearch方法的源代码。这个方法需要 列表参数,但二分搜索对链表没有意义。然后代码就笨拙了 尝试发现列表是否是链表,然后切换到线性搜索!< / p >

Set接口和Map接口设计得很好,应该使用它们。

当您编写List时,实际上告诉您的对象只实现了List接口,但是您没有指定对象属于什么类。

当您编写ArrayList时,您指定对象类是一个可调整大小的数组。

因此,第一个版本使您的代码将来更加灵活。

看看Java文档:

班级ArrayList - List接口的可调整数组实现。

接口List . -有序集合(也称为序列)。该接口的用户可以精确控制每个元素在列表中的插入位置。

容器对象,保存固定数量的单一类型的值。

例如,您可能认为LinkedList是应用程序的最佳选择,但后来由于性能原因,又认为ArrayList可能是更好的选择。

使用:

List list = new ArrayList(100); // will be better also to set the initial capacity of a collection

而不是:

ArrayList list = new ArrayList();

供参考:

enter image description here

(主要用于收集图)

出以下两种:

(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();

第一种通常是首选。由于您将只使用List接口中的方法,因此将来可以自由使用List的其他实现,例如LinkedList。所以它将你与特定的实现分离。现在有两点值得一提:

  1. 我们应该始终按照接口编程。# EYZ0。
  2. 你几乎总是会使用ArrayList而不是LinkedList。# EYZ2。

我想知道是否有人使用(2)

有时会(很少阅读)。当我们需要的方法是ArrayList实现的一部分,但不是接口List的一部分。例如ensureCapacity

此外,多长时间(我能得到一个例子)会发生这种情况 实际上需要使用(1)/ (2)

这是OOP中的一个经典设计模式,你总是试图将你的代码从特定的实现和程序解耦到接口。

List是一个接口。它没有方法。当你调用List引用上的方法时,它实际上在这两种情况下都调用了ArrayList的方法。

将来你可以将List obj = new ArrayList<>改为List obj = new LinkList<>或其他实现列表界面的类型。

事实上,有些情况下(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<>();

有人又问了这个问题(重复),这让我在这个问题上更深入了一些。

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) weĺl请参阅以下(仅列出初始化和赋值)为我们的列表片段:

   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

对于船向一边倾斜的:

   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

不同之处在于列表调用INVOKEINTERFACE,而船向一边倾斜的调用INVOKEVIRTUAL。根据Bycode大纲插件参考,

invokeinterface用于调用Java中声明的方法 界面< / p >

虽然invokevirtual

调用除接口方法(使用 Invokeinterface)、静态方法(使用invokestatic)等等

.特殊情况由invokspecial处理

总之,invokvirtual在调用invokeinterface时将objectref从堆栈中弹出

解释器从操作数堆栈中弹出'n'项,其中'n'是8位无符号 从字节码获取的整数参数。第一项是

. Objectref,对被调用方法的对象的引用

如果我理解正确的话,区别基本上是每种方法检索objectref的方式。

List接口有几个不同的类- ArrayListLinkedListLinkedList用于创建索引集合,ArrayList -用于创建排序列表。你可以在你的参数中使用它,但是你可以允许其他使用你的代码,库等的开发人员使用不同类型的列表,而不仅仅是你在这个方法中使用的

ArrayList<Object> myMethod (ArrayList<Object> input) {
// body
}

你可以只使用ArrayList,而不是LinkedList,但你可以允许使用任何List类在其他地方,它的方法正在使用,这只是你的选择,所以使用接口可以允许它:

List<Object> myMethod (List<Object> input) {
// body
}

在这个方法参数中,你可以使用任何你想使用的List类:

List<Object> list = new ArrayList<Object> ();


list.add ("string");


myMethod (list);

结论:

尽可能在任何地方使用接口,不要限制您或其他人使用他们想要使用的不同方法。