在Java中键入List vs type ArrayList

问题描述 投票:511回答:15
(1) List<?> myList = new ArrayList<?>();

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

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

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

此外,这种情况实际上需要使用(1)over(2)(即,(2)不足以进行编码接口和最佳实践等)。

java list interface decoupling
15个回答
409
投票

几乎总是第一个优先于第二个。第一个优点是List的实现可以改变(例如LinkedList),而不会影响代码的其余部分。这对ArrayList来说将是一项艰巨的任务,不仅因为你需要将ArrayList更改为LinkedList,而且因为你可能已经使用了ArrayList特定的方法。

你可以阅读有关qazxsw poi实施qazxsw poi的信息。你可以从List开始,但不久之后发现另一个实现更合适。


7
投票

在以下两个中:

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将来。因此它将您与特定实现分离。现在有两点值得一提:

  1. 我们应该始终编程接口。更多List
  2. 你几乎总是会在LinkedList上使用here。更多ArrayList

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

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

此外,多久(我可以得到一个例子)情况实际上需要使用(1)over(2)

几乎总是你更喜欢选项(1)。这是OOP中的经典设计模式,您总是尝试将代码从特定的实现和程序与接口分离。


3
投票

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

并且将来你可以将List更改为ensureCapacity或其他实现List接口的类型。


3
投票

有人再次问这个问题(重复),这让我对这个问题有了更深入的了解。

List obj = new ArrayList<>

如果我们使用字节码查看器(我使用List obj = new LinkList<>),我们将看到以下列表(仅列表初始化和分配):

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");

}

对于alist:

http://asm.ow2.org/eclipse/index.html

不同之处在于列表最终调用INVOKEINTERFACE,而List调用INVOKEVIRTUAL。根据Bycode Outline插件参考,

invokeinterface用于调用Java接口中声明的方法

而invokevirtual

调用除接口方法(使用invokeinterface),静态方法(使用invokestatic)以及invokespecial处理的少数特殊情况之外的所有方法。

总之,invokevirtual在调用接口时将objectref从堆栈中弹出

解释器在操作数堆栈中弹出“n”项,其中“n”是取自字节码的8位无符号整数参数。这些项中的第一项是objectref,它是对正在调用其方法的对象的引用。

如果我理解正确,差异基本上是每种方式检索objectref。


2
投票

我知道哪个(2)可以更好的唯一情况是使用GWT,因为它减少了应用程序占用空间(不是我的想法,但谷歌网络工具包团队这样说)。但是对于在JVM(1)内部运行的常规Java可能总是更好。


1
投票

我会说1是首选,除非

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

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

  • 例如 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

1
投票

removeAll界面有几个不同的类 - add(null)ListArrayList用于创建索引集合和LinkedList - 创建排序列表。因此,您可以在参数中使用任何一个,但是您可以允许使用您的代码,库等的其他开发人员使用不同类型的列表,而不仅仅是您使用的列表,因此,在此方法中

LinkedList

你只能使用ArrayList,而不是ArrayList<Object> myMethod (ArrayList<Object> input) { // body } ,但你可以允许在其使用方法的其他地方使用任何ArrayList类,这只是你的选择,所以使用接口可以允许它:

LinkedList

在此方法参数中,您可以使用要使用的任何List类:

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

结论:

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


104
投票

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

是。但很少有正当理由(IMO)。

人们因为使用here而使用ArrayList而被烧毁:

  • ArrayListList这样的实用方法不会返回Collections.singletonList(...)
  • Arrays.asList(...) API中的方法不保证返回相同类型的列表。

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

你需要知道ArrayList表现如何的论点在很大程度上通过使用ArrayList标记界面来解决。是的,它有点笨重,但替代方案更糟糕。

此外,情况多久经常需要使用(1)超过(2)(即,(2)不足以“对接口进行编码”和最佳实践等)

问题的“多久”部分是客观上无法回答的。

(我可以举一个例子)

有时,应用程序可能要求您使用List API中不在RandomAccess API中的方法。例如,ArrayListListensureCapacity(int)。 (最后一个只会在你创建一个ArrayList的子类型时才会出现,该子类型声明该方法为trimToSize()。)

这是编写到类而不是接口IMO的唯一合理原因。

(从理论上讲,在某些情况下......在某些平台上......你的性能会略有改善......但除非你真的需要最后的0.05%,否则不值得这样做。这不是一个合理的理由,国际海事组织。)


如果您不知道随机访问是否有效,则无法编写有效的代码。

这是一个有效的观点。但是,Java提供了更好的方法来处理它;例如

removeRange(int, int)

如果使用未实现public的列表调用它,则会出现编译错误。

您还可以动态测试...使用public <T extends List & RandomAccess> void test(T list) { // do stuff } ...如果静态类型太笨拙。您甚至可以编写代码以使用不同的算法(动态),具体取决于列表是否支持随机访问。

请注意,RandomAccess不是唯一实现instanceof的列表类。其他包括ArrayListRandomAccessCopyOnWriteList

我见过人们对Stack提出同样的论点(因为Vector没有实现它)......但上面的方法也解决了这个问题。 (在某种程度上它可以使用运行时类型解决。如果任何元素不可序列化,Serializable将无法序列化。)


37
投票

例如,您可能会认为List是您的应用程序的最佳选择,但后来决定ArrayList可能是出于性能原因的更好选择。

使用:

LinkedList

代替:

ArrayList

以供参考:

(主要用于收集图)


23
投票

在类型为Set的变量中存储对List list = new ArrayList(100); // will be better also to set the initial capacity of a collection ArrayList list = new ArrayList(); 的引用被认为是好的样式。

HashSet

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

此外,对集合进行操作的方法应指定Set类型的参数:

Set<String> names = new HashSet<String>();

然后,该方法可用于所有集合实现。

理论上,我们应该对链表做出相同的建议,即在List类型的变量中保存LinkedList引用。但是,在Java库中,List接口对于TreeSetpublic static void print(Set<String> s)类都是通用的。特别是,它具有获取和设置随机访问的方法,即使这些方法对链表非常低效。

如果您不知道随机访问是否有效,则无法编写有效的代码。

这显然是标准库中的一个严重的设计错误,因此我不建议使用List接口。

要查看错误是多么令人尴尬,请查看Collections类的ArrayList方法的源代码。该方法采用List参数,但二进制搜索对链表没有意义。然后代码笨拙地试图发现列表是否是链表,然后切换到线性搜索!

LinkedList界面和binarySearch界面设计得很好,您应该使用它们。


12
投票

如果代码是列表的“所有者”,我使用(2)。例如,对于仅本地变量,这是真的。没有理由使用抽象类型Set而不是Map。另一个证明所有权的例子:

List

11
投票

当您编写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时,指定对象类是可调整大小的数组。

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

看看Java文档:

List - ArrayList接口的可调整大小的数组实现。

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

List - 容器对象,它包含固定数量的单一类型的值。


9
投票

(3)Collection myCollection = new ArrayList();

我通常使用这个。只有我需要List方法,我才会使用List。与ArrayList相同。你总是可以切换到更“窄”的界面,但你不能切换到更“宽”。


9
投票

我认为使用(2)的人不知道Interface ListArray。或者他们真的必须使用Liskov substitution principle


9
投票

实际上有些情况下(2)不仅是首选而是强制性的,我很惊讶,没有人在这里提到这一点。

序列化!

如果你有一个可序列化的类,并且你希望它包含一个列表,那么你必须声明该字段是一个具体和可序列化的类型,如Dependency inversion principle,因为ArrayList接口不扩展ArrayList

显然大多数人不需要序列化而忘记了这一点。

一个例子:

List
© www.soinside.com 2019 - 2024. All rights reserved.