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

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

我知道使用 (1),可以交换List接口的实现。似乎(1)通常在应用程序中使用,无论需要(我自己总是使用它)。

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

此外,这种情况实际上需要使用 (1) 而不是 (2) 的频率(即,如果 (2) 不够……除了对接口和最佳实践进行编码等)

4

15 回答 15

486

几乎总是List首选,ArrayList因为例如,List可以翻译成 aLinkedList而不会影响代码库的其余部分。

如果使用一种ArrayList代替List,则很难将ArrayList实现更改为LinkedList一种,因为ArrayList在代码库中使用了特定的方法,这些方法也需要重组。

您可以在此处阅读有关List实现的信息。

您可能从 开始ArrayList,但很快发现另一种实现是更合适的选择。

于 2010-02-17T07:46:11.893 回答
124

我想知道是否有人使用(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) 不够……除了“对接口进行编码”和最佳实践等)

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

(我可以举个例子吗)

有时,应用程序可能会要求您使用ArrayListAPI 中包含在ListAPI 中的方法。例如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和。StackVector

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


最后,我不会说“因为它的风格很好”。这个“原因”既是一个循环论证(“为什么它是‘好风格’?”),也是对一个未说明的(并且可能不存在!)更高权威的呼吁(“谁说它是‘好风格’?”) .

(我确实认为对界面进行编程是一种很好的风格,但我不会将其作为理由。您最好了解真正的原因并自己得出(IMO)正确的结论。正确的结论可能并不总是相同的......取决于上下文。)

于 2010-02-17T07:50:13.810 回答
46

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

采用:

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

代替:

ArrayList list = new ArrayList();

以供参考:

在此处输入图像描述

(主要为集合图发布)

于 2014-09-09T15:12:05.337 回答
26

在 Set 类型的变量中存储对 a或的引用被认为是一种很好的风格。HashSetTreeSet

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设计得很好,你应该使用它们。

于 2013-08-21T11:46:38.087 回答
14

如果代码是列表的“所有者”,我使用 (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);
    }
}
于 2013-04-21T12:20:37.030 回答
14

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

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

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

查看 Java 文档:

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

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

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

于 2013-12-05T17:14:10.523 回答
9
(3) Collection myCollection = new ArrayList<?>();

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

于 2010-02-17T07:51:38.717 回答
9

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

于 2010-02-17T07:55:27.803 回答
9

实际上,在某些情况下,(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<>();
于 2016-11-14T10:22:30.170 回答
8

在以下两个中:

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

首先通常是首选。由于您将仅使用接口中的方法,因此它为您提供了将来使用例如List其他一些实现的自由。因此,它使您与具体的实现脱钩。现在有两点值得一提:ListLinkedList

  1. 我们应该始终对接口进行编程。更多在这里
  2. 你几乎总是会使用ArrayListover LinkedList。更多在这里

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

有时是的(很少阅读)。当我们需要属于实现ArrayList但不属于接口的方法时List。例如ensureCapacity.

此外,这种情况实际上需要多久(我可以举个例子)使用(1)而不是(2)

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

于 2015-07-03T08:40:04.940 回答
4

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

并且对于将来您可以更改List obj = new ArrayList<>List obj = new LinkList<>或其他实现List 接口的类型。

于 2015-10-27T11:50:44.923 回答
3

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

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最终调用INVOKEINTERFACEaList调用INVOKEVIRTUAL。根据 Bycode Outline Plugin 参考,

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

而调用虚拟

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

总之, invokevirtual 将objectref从堆栈中弹出,而对于 invokeinterface

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

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

于 2016-12-23T11:06:00.157 回答
2

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

于 2010-02-17T07:54:29.607 回答
1

我会说 1 是首选,除非

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

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

  • 例如removeAll,或add(null)
于 2010-02-17T07:47:58.603 回答
1

List接口有几个不同的类 -ArrayListLinkedList. 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);

结论:

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

于 2018-06-15T21:38:04.427 回答