在Java中键入List vs type ArrayList

时间:2010-02-17 07:40:13

标签: java list interface decoupling

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

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

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

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

另外,经常(并且我可以得到一个例子)情况实际上需要使用(1)over(2)(即where(2)不足以......除 编码之外接口 最佳做法 等。)

15 个答案:

答案 0 :(得分:408)

几乎总是第一个比第二个更受欢迎。第一个优点是List的实现可以更改(例如,LinkedList),而不会影响其余的代码。这对于ArrayList来说将是一项艰巨的任务,不仅因为您需要在任何地方将ArrayList更改为LinkedList,还因为您可能已使用ArrayList具体方法

您可以阅读有关List实施here的信息。您可以从ArrayList开始,但很快就会发现另一个实现更合适。

答案 1 :(得分:103)

  

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

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

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

  • Collections.singletonList(...)Arrays.asList(...)等实用程序方法不会返回ArrayList

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

例如某人被烧伤,在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)。 (并且只有在创建了一个声明方法为public的ArrayList子类型时才会出现最后一个。)

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

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


  

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

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

public <T extends List & RandomAccess> void test(T list) {
    // do stuff
}

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

你也可以动态测试...使用instanceof ...如果静态输入太笨拙。您甚至可以编写代码以使用不同的算法(动态),具体取决于列表是否支持随机访问。

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

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

答案 2 :(得分:36)

例如,您可能认为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

(主要针对收集图发布)

答案 3 :(得分:23)

被认为是好样式 ,可以在类型为Set的变量中存储对HashSetTreeSet的引用。

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

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

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

类型的参数

public static void print(Set<String> s)

然后 该方法可用于所有设置实现

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

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

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

要了解错误是多么令人尴尬,请看一下 集合 类的binarySearch方法的源代码。那个方法需要一个 列表参数,但二进制搜索对链表没有意义。然后笨拙的代码 尝试发现列表是否是链表,然后切换到线性搜索!

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

答案 4 :(得分:12)

如果代码是列表的“所有者”,我使用(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);
    }
}

答案 5 :(得分:11)

当您编写List时,您实际上告诉您,您的对象仅实现List接口,但您没有指定您的对象所属的类。

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

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

查看Java文档:

Class ArrayList - List界面的可调整大小的数组实现。

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

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

答案 6 :(得分:9)

(3)Collection myCollection = new ArrayList();

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

答案 7 :(得分:9)

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

答案 8 :(得分:9)

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

序列化!

如果您有一个可序列化的类并且希望它包含一个列表,那么您必须将该字段声明为具体且可序列化的类型,如ArrayList,因为List接口不会扩展{ {1}}

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

一个例子:

java.io.Serializable

答案 9 :(得分:7)

以下两项:

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

首先通常是首选。由于您将仅使用List界面中的方法,因此您可以自由地使用List的其他实现,例如LinkedList将来。因此它将您与特定实现分离。现在有两点值得一提:

  1. 我们应该始终编程接口。更多here
  2. 您几乎总是会使用ArrayList而不是LinkedList。更多here
  3.   

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

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

      

    此外,这种情况经常发生(并且我能得到一个例子)   实际上需要使用(1)over(2)

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

答案 10 :(得分:3)

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

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

答案 11 :(得分: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

差异列表最终调用 INVOKEINTERFACE ,而 aList 调用 INVOKEVIRTUAL 。根据Bycode Outline插件参考,

  

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

while invokevirtual

  

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

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

  

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

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

答案 12 :(得分:2)

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

答案 13 :(得分:1)

我会说1是首选,除非

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

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

  • 例如removeAlladd(null)

答案 14 :(得分:1)

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

<强>结论:

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