Java 中 List 类型与 ArrayList 类型

发布于 2024-08-21 15:17:16 字数 346 浏览 10 评论 0 原文

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

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

据我所知,通过(1),可以交换List接口的实现。看起来(1)通常在应用程序中使用,无论需要如何(我自己总是使用这个)。

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

另外,这种情况多久需要使用(1)而不是(2)(即(2)不够......除了接口编码) em> 和最佳实践等)

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

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

I understand that with (1), implementations of the List interface can be swapped. It seems that (1) is typically used in an application regardless of need (myself I always use this).

I am wondering if anyone uses (2)?

Also, how often (and can I please get an example) does the situation actually require using (1) over (2) (i.e. where (2) wouldn't suffice..aside coding to interfaces and best practices etc.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

心头的小情儿 2024-08-28 15:17:16

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

如果使用 ArrayList 而不是 List,则很难将 ArrayList 实现更改为 LinkedList 实现,因为 ArrayList 特定方法已在代码库中使用,也需要重组。

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

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

Almost always List is preferred over ArrayList because, for instance, List can be translated into a LinkedList without affecting the rest of the codebase.

If one used ArrayList instead of List, it's hard to change the ArrayList implementation into a LinkedList one because ArrayList specific methods have been used in the codebase that would also require restructuring.

You can read about the List implementations here.

You may start with an ArrayList, but soon after discover that another implementation is the more appropriate choice.

扶醉桌前 2024-08-28 15:17:16

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

是的。但很少有合理的理由(IMO)。

人们会因为在应该使用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)不够用......除了“接口编码”和最佳实践等)

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

(我可以举个例子吗)

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

这是对类而不是接口进行编码的唯一合理理由,IMO。

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


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

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

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

如果您使用未实现 RandomAccess 的列表调用该函数,您将收到编译错误。

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

请注意,ArrayList 并不是唯一实现 RandomAccess 的列表类。其他包括 CopyOnWriteList、Stack 和 Vector。

我见过人们对 Serialized 做出同样的争论(因为 List 没有实现它)......但是上面的方法也解决了这个问题。 (就使用运行时类型完全可以解决而言。如果任何元素不可序列化,ArrayList将无法序列化。)


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

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

I am wondering if anyone uses (2)?

Yes. But rarely for a sound reason (IMO).

And people get burned because they used ArrayList when they should have used List:

  • Utility methods like Collections.singletonList(...) or Arrays.asList(...) don't return an ArrayList.

  • Methods in the List API don't guarantee to return a list of the same type.

For example of someone getting burned, in https://stackoverflow.com/a/1481123/139985 the poster had problems with "slicing" because ArrayList.sublist(...) doesn't return an ArrayList ... and he had designed his code to use ArrayList as the type of all of his list variables. He ended up "solving" the problem by copying the sublist into a new ArrayList.

The argument that you need to know how the List behaves is largely addressed by using the RandomAccess marker interface. Yes, it is a bit clunky, but the alternative is worse.

Also, how often does the situation actually require using (1) over (2) (i.e. where (2) wouldn't suffice..aside 'coding to interfaces' and best practices etc.)

The "how often" part of the question is objectively unanswerable.

(and can I please get an example)

Occasionally, the application may require that you use methods in the ArrayList API that are not in the List API. For example, ensureCapacity(int), trimToSize() or removeRange(int, int). (And the last one will only arise if you have created a subtype of ArrayList that declares the method to be public.)

That is the only sound reason for coding to the class rather than the interface, IMO.

(It is theoretically possible that you will get a slight improvement in performance ... under certain circumstances ... on some platforms ... but unless you really need that last 0.05%, it is not worth doing this. This is not a sound reason, IMO.)


You can’t write efficient code if you don’t know whether random access is efficient or not.

That is a valid point. However, Java provides better ways to deal with that; e.g.

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

If you call that with a list that does not implement RandomAccess you will get a compilation error.

You could also test dynamically ... using instanceof ... if static typing is too awkward. And you could even write your code to use different algorithms (dynamically) depending on whether or not a list supported random access.

Note that ArrayList is not the only list class that implements RandomAccess. Others include CopyOnWriteList, Stack and Vector.

I've seen people make the same argument about Serializable (because List doesn't implement it) ... but the approach above solves this problem too. (To the extent that it is solvable at all using runtime types. An ArrayList will fail serialization if any element is not serializable.)


Finally, I'm not going to say "because its is good style". That "reason" is both a circular argument ("Why is it 'good style'?") and an appeal to an unstated (and probably non-existent!) higher authority ("Who says it is 'good style'?").

(I do think it is good style to program to the interface, but I'm not going to give that as a reason. It is better for you to understand the real reasons and come to the (IMO) correct conclusions for yourself. The correct conclusion may not always be the same ... depending on the context.)

吃素的狼 2024-08-28 15:17:16

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

使用:

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

而不是:

ArrayList list = new ArrayList();

供参考:

在此处输入图像描述

(主要针对集合图发布)

For example you might decide a LinkedList is the best choice for your application, but then later decide ArrayList might be a better choice for performance reason.

Use:

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

Instead of:

ArrayList list = new ArrayList();

For reference:

enter image description here

(posted mostly for Collection diagram)

冷了相思 2024-08-28 15:17:16

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

设置<字符串> name = new HashSet();

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

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

public static void print(Set s)

然后该方法可用于所有集合实现 em>

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

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

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

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

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

It is considered good style to store a reference to a HashSet or TreeSet in a variable of type Set.

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

This way, you have to change only one line if you decide to use a TreeSet instead.

Also, methods that operate on sets should specify parameters of type Set:

public static void print(Set<String> s)

Then the method can be used for all set implementations.

In theory, we should make the same recommendation for linked lists, namely to save
LinkedList references in variables of type List. However, in the Java library, the List interface is common to both the ArrayList and the LinkedList class. In particular, it has get and set methods for random access, even though these methods are very inefficient for linked lists.

You can’t write efficient code if you don’t know whether random access is efficient or not.

This is plainly a serious design error in the standard library, and I cannot recommend using
the List interface for that reason.

To see just how embarrassing that error is, have a look at
the source code for the binarySearch method of the Collections class. That method takes a
List parameter, but binary search makes no sense for a linked list. The code then clumsily
tries to discover whether the list is a linked list, and then switches to a linear search!

The Set interface and the Map interface, are well designed, and you should use them.

红衣飘飘貌似仙 2024-08-28 15:17:16

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

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

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

查看Java文档:

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

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

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

When you write List, you actually tell, that your object implements List interface only, but you don't specify what class your object belongs to.

When you write ArrayList, you specify that your object class is a resizable-array.

So, the first version makes your code more flexible in future.

Look at Java docs:

Class ArrayList - Resizable-array implementation of the List interface.

Interface List - An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted.

Array - container object that holds a fixed number of values of a single type.

↙厌世 2024-08-28 15:17:16

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

I use (2) if code is the "owner" of the list. This is for example true for local-only variables. There is no reason to use the abstract type List instead of ArrayList.
Another example to demonstrate ownership:

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);
    }
}
抚你发端 2024-08-28 15:17:16

我认为使用(2)的人不知道里氏替换原则依赖倒置原则。或者他们确实必须使用 ArrayList。

I think the people who use (2) don't know the Liskov substitution principle or the Dependency inversion principle. Or they really have to use ArrayList.

云之铃。 2024-08-28 15:17:16

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

连载!

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

显然,大多数人不需要序列化,并且忘记了这一点

一个例子:

public class ExampleData implements java.io.Serializable {

// The following also guarantees that strings is always an ArrayList.
private final ArrayList<String> strings = new ArrayList<>();

Actually there are occasions where (2) is not only preferred but mandatory and I am very surprised, that nobody mentions this here.

Serialization!

If you have a serializable class and you want it to contain a list, then you must declare the field to be of a concrete and serializable type like ArrayList because the List interface does not extend java.io.Serializable

Obviously most people do not need serialization and forget about this.

An example:

public class ExampleData implements java.io.Serializable {

// The following also guarantees that strings is always an ArrayList.
private final ArrayList<String> strings = new ArrayList<>();
染年凉城似染瑾 2024-08-28 15:17:16
(3) Collection myCollection = new ArrayList<?>();

我通常使用这个。 如果我需要 List 方法,我就会使用 List。与ArrayList相同。您始终可以切换到更“窄”的界面,但无法切换到更“宽”的界面。

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

I am using this typically. And only if I need List methods, I will use List. Same with ArrayList. You always can switch to more "narrow" interface, but you can't switch to more "wide".

甩你一脸翔 2024-08-28 15:17:16

以下两项中:

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

通常首选第一项。由于您将仅使用 List 接口中的方法,因此您将来可以自由地使用 List 的其他实现,例如 LinkedList。因此它使您与具体的实现脱钩。现在有两点值得一提:

  1. 我们应该始终针对接口进行编程。更多信息请此处
  2. 您几乎总是会使用 ArrayList 而不是 LinkedList。更多信息请此处

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

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

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

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

Out of the following two:

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

First is generally preferred. As you will be using methods from List interface only, it provides you the freedom to use some other implementation of List e.g. LinkedList in future. So it decouples you from specific implementation. Now there are two points worth mentioning:

  1. We should always program to interface. More here.
  2. You will almost always end up using ArrayList over LinkedList. More here.

I am wondering if anyone uses (2)

Yes sometimes (read rarely). When we need methods that are part of implementation of ArrayList but not part of the interface List. For example ensureCapacity.

Also, how often (and can I please get an example) does the situation
actually require using (1) over (2)

Almost always you prefer option (1). This is a classical design pattern in OOP where you always try to decouple your code from specific implementation and program to the interface.

倥絔 2024-08-28 15:17:16

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

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

List is an interface. It doesn't have methods. When you call a method on a List reference, it in fact calls the method of ArrayList in both cases.

And for the future you can change List obj = new ArrayList<> to List obj = new LinkList<> or other types which implement List interface.

九歌凝 2024-08-28 15:17:16

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

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)我们会看到 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

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 插件参考,

invokeinterface用于调用Java中声明的方法
界面

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

总之,invokevirtual 在 invokeinterface 时将 objectref 从堆栈中弹出

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

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

Somebody asked this again (duplicate) which made me go a little deeper on this issue.

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

}

If we use a bytecode viewer (I used http://asm.ow2.org/eclipse/index.html) weĺl see the following (only list initialization and assignment) for our list snippet:

   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

and for 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

The difference is list ends up calling INVOKEINTERFACE whereas aList calls INVOKEVIRTUAL. Accoding to the Bycode Outline Plugin reference,

invokeinterface is used to invoke a method declared within a Java
interface

while invokevirtual

invokes all methods except interface methods (which use
invokeinterface), static methods (which use invokestatic), and the few
special cases handled by invokespecial.

In summary, invokevirtual pops objectref off the stack while for invokeinterface

the interpreter pops 'n' items off the operand stack, where 'n' is an 8-bit unsigned
integer parameter taken from the bytecode. The first of these items is
objectref, a reference to the object whose method is being called.

If I understand this correctly, the difference is basically how each way retrieves objectref.

江南烟雨〆相思醉 2024-08-28 15:17:16

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

The only case that I am aware of where (2) can be better is when using GWT, because it reduces application footprint (not my idea, but the google web toolkit team says so). But for regular java running inside the JVM (1) is probably always better.

天气好吗我好吗 2024-08-28 15:17:16

我想说 1 是首选,除非

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

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

  • 例如 removeAlladd(null)

I would say that 1 is preferred, unless

  • you are depending on the implementation of optional behavior* in ArrayList, in that case explicitly using ArrayList is more clear
  • You will be using the ArrayList in a method call which requires ArrayList, possibly for optional behavior or performance characteristics

My guess is that in 99% of the cases you can get by with List, which is preferred.

  • for instance removeAll, or add(null)
煮茶煮酒煮时光 2024-08-28 15:17:16

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

结论:

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

List interface have several different classes - ArrayList and LinkedList. LinkedList is used to create an indexed collections and ArrayList - to create sorted lists. So, you can use any of it in your arguments, but you can allow others developers who use your code, library, etc. to use different types of lists, not only which you use, so, in this method

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

you can use it only with ArrayList, not LinkedList, but you can allow to use any of List classes on other places where it method is using, it's just your choise, so using an interface can allow it:

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

In this method arguments you can use any of List classes which you want to use:

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

list.add ("string");

myMethod (list);

CONCLUSION:

Use the interfaces everywhere when it possible, don't restrict you or others to use different methods which they want to use.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文