Java 中 List 类型与 ArrayList 类型
(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
据我所知,通过(1),可以交换List接口的实现。看起来(1)通常在应用程序中使用,无论需要如何(我自己总是使用这个)。
我想知道是否有人使用(2)?
另外,这种情况多久需要使用(1)而不是(2)(即(2)不够......除了接口编码) em> 和最佳实践等)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(15)
几乎总是
List
优于ArrayList
,因为,例如,List
可以转换为LinkedList
而不影响代码库的其余部分。如果使用
ArrayList
而不是List
,则很难将ArrayList
实现更改为LinkedList
实现,因为ArrayList
特定方法已在代码库中使用,也需要重组。您可以在此处阅读有关
List
实现的信息。您可能会从 ArrayList 开始,但很快就会发现另一个实现是更合适的选择。
Almost always
List
is preferred overArrayList
because, for instance,List
can be translated into aLinkedList
without affecting the rest of the codebase.If one used
ArrayList
instead ofList
, it's hard to change theArrayList
implementation into aLinkedList
one becauseArrayList
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.是的。但很少有合理的理由(IMO)。
人们会因为在应该使用
List
时使用了ArrayList
而感到烦恼:像
Collections.singletonList(...)这样的实用方法
或Arrays.asList(...)
不返回ArrayList
。List
API 中的方法不保证返回相同类型的列表。例如,有人被烧伤,在 https://stackoverflow.com/a/1481123/139985 中,发布者遇到了以下问题: “切片”,因为
ArrayList.sublist(...)
不返回ArrayList
...并且他设计了使用ArrayList
的代码> 作为其所有列表变量的类型。他最终通过将子列表复制到新的 ArrayList 中“解决”了问题。您需要了解
List
行为方式的争论很大程度上是通过使用RandomAccess
标记接口来解决的。是的,它有点笨重,但替代方案更糟糕。问题的“多久一次”部分客观上是无法回答的。
有时,应用程序可能会要求您使用
ArrayList
API 中不在 List 中的方法API。例如,ensureCapacity(int)
、trimToSize()
或removeRange(int, int)
。 (只有当您创建了 ArrayList 的子类型并将该方法声明为public
时,最后一个才会出现。)这是对类而不是接口进行编码的唯一合理理由,IMO。
(理论上你可能会获得性能上的轻微提升......在某些情况下......在某些平台上......但除非你真的需要最后的 0.05%,否则不值得这样做。这不是一个合理的理由,国际海事组织。)
这是一个有效的观点。然而,Java 提供了更好的方法来处理这个问题;例如,
如果您使用未实现
RandomAccess
的列表调用该函数,您将收到编译错误。如果静态类型太尴尬,您还可以使用
instanceof
动态测试...。您甚至可以编写代码以(动态)使用不同的算法,具体取决于列表是否支持随机访问。请注意,
ArrayList
并不是唯一实现RandomAccess
的列表类。其他包括 CopyOnWriteList、Stack 和 Vector。我见过人们对
Serialized
做出同样的争论(因为List
没有实现它)......但是上面的方法也解决了这个问题。 (就使用运行时类型完全可以解决而言。如果任何元素不可序列化,ArrayList
将无法序列化。)最后,我不会说“因为它的风格很好”。这个“理由”既是一个循环论证(“为什么是'好风格'?”),也是对一个未声明的(并且可能不存在!)更高权威的诉求(“谁” 说这是“好风格”?”)。
(我确实认为对接口进行编程是一种很好的风格,但我不会以此作为理由。您最好了解真正原因并了解(IMO )自己的正确结论可能并不总是相同......取决于上下文。)
Yes. But rarely for a sound reason (IMO).
And people get burned because they used
ArrayList
when they should have usedList
:Utility methods like
Collections.singletonList(...)
orArrays.asList(...)
don't return anArrayList
.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 anArrayList
... and he had designed his code to useArrayList
as the type of all of his list variables. He ended up "solving" the problem by copying the sublist into a newArrayList
.The argument that you need to know how the
List
behaves is largely addressed by using theRandomAccess
marker interface. Yes, it is a bit clunky, but the alternative is worse.The "how often" part of the question is objectively unanswerable.
Occasionally, the application may require that you use methods in the
ArrayList
API that are not in theList
API. For example,ensureCapacity(int)
,trimToSize()
orremoveRange(int, int)
. (And the last one will only arise if you have created a subtype of ArrayList that declares the method to bepublic
.)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.)
That is a valid point. However, Java provides better ways to deal with that; e.g.
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 implementsRandomAccess
. Others includeCopyOnWriteList
,Stack
andVector
.I've seen people make the same argument about
Serializable
(becauseList
doesn't implement it) ... but the approach above solves this problem too. (To the extent that it is solvable at all using runtime types. AnArrayList
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.)
例如,您可能认为
LinkedList
是您应用程序的最佳选择,但后来出于性能原因又认为ArrayList
可能是更好的选择。使用:
而不是:
供参考:
(主要针对集合图发布)
For example you might decide a
LinkedList
is the best choice for your application, but then later decideArrayList
might be a better choice for performance reason.Use:
Instead of:
For reference:
(posted mostly for Collection diagram)
在 Set 类型的变量中存储对
HashSet
或TreeSet
的引用被认为是良好的风格。设置<字符串> 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
orTreeSet
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 theLinkedList
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 aList 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 theMap
interface, are well designed, and you should use them.当您编写
List
时,您实际上告诉您的对象仅实现List
接口,但您没有指定您的对象属于哪个类。当您编写
ArrayList
时,您指定您的对象类是一个可调整大小的数组。因此,第一个版本使您的代码将来更加灵活。
查看Java文档:
类
ArrayList
< /a> -List
接口的可调整大小数组实现。接口
List
- 有序集合(也称为序列)。该界面的用户可以精确控制每个元素在列表中的插入位置。Array
- 容器对象保存固定数量的单一类型的值。When you write
List
, you actually tell, that your object implementsList
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 theList
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.如果代码是列表的“所有者”,我使用(2)。例如,这对于仅局部变量来说是正确的。没有理由使用抽象类型
List
而不是ArrayList
。另一个证明所有权的例子:
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 ofArrayList
.Another example to demonstrate ownership:
我认为使用(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
.实际上,在某些情况下,(2)不仅是首选,而且是强制性的,我很惊讶,这里没有人提到这一点。
连载!
如果您有一个可序列化的类并且希望它包含一个列表,那么您必须将该字段声明为具体的可序列化类型,例如 ArrayList ,因为
List 接口不会 。
显然,大多数人不需要序列化,并且忘记了这一点
一个例子:
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 theList
interface does not extendjava.io.Serializable
Obviously most people do not need serialization and forget about this.
An example:
我通常使用这个。 仅如果我需要 List 方法,我就会使用 List。与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".
以下两项中:
通常首选第一项。由于您将仅使用
List
接口中的方法,因此您将来可以自由地使用List
的其他实现,例如LinkedList
。因此它使您与具体的实现脱钩。现在有两点值得一提:ArrayList
而不是LinkedList
。更多信息请此处。是的有时(很少读)。当我们需要属于
ArrayList
实现的一部分但不属于List
接口的方法时。例如ensureCapacity
。几乎总是你更喜欢选项(1)。这是 OOP 中的经典设计模式,您始终尝试将代码与特定实现和程序与接口解耦。
Out of the following two:
First is generally preferred. As you will be using methods from
List
interface only, it provides you the freedom to use some other implementation ofList
e.g.LinkedList
in future. So it decouples you from specific implementation. Now there are two points worth mentioning:ArrayList
overLinkedList
. More here.Yes sometimes (read rarely). When we need methods that are part of implementation of
ArrayList
but not part of the interfaceList
. For exampleensureCapacity
.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.
列表是一个接口。它没有方法。当您调用 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<>
toList obj = new LinkList<>
or other types which implement List interface.有人再次问了这个问题(重复),这让我对这个问题有了更深入的了解。
如果我们使用字节码查看器(我使用 http://asm.ow2.org/eclipse/index .html)我们会看到 list 代码段的以下内容(仅列表初始化和赋值):
和 alist:
区别在于 list 最终调用 INVOKEINTERFACE,而 aList 调用 INVOKEVIRTUAL。根据 Bycode Outline 插件参考,
时
总之,invokevirtual 在 invokeinterface 时将 objectref 从堆栈中弹出
如果我理解正确的话,区别基本上在于每种方式如何检索objectref。
Somebody asked this again (duplicate) which made me go a little deeper on this issue.
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:
and for alist:
The difference is list ends up calling INVOKEINTERFACE whereas aList calls INVOKEVIRTUAL. Accoding to the Bycode Outline Plugin reference,
while invokevirtual
In summary, invokevirtual pops objectref off the stack while for invokeinterface
If I understand this correctly, the difference is basically how each way retrieves objectref.
我知道 (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.
我想说 1 是首选,除非
我的猜测是,在 99% 的情况下,您可以使用 List,这是首选。
removeAll
或add(null)
I would say that 1 is preferred, unless
My guess is that in 99% of the cases you can get by with List, which is preferred.
removeAll
, oradd(null)
List
接口有几个不同的类 -ArrayList
和LinkedList
。LinkedList
用于创建索引集合,ArrayList
用于创建排序列表。因此,您可以在参数中使用其中的任何一个,但是您可以允许使用您的代码、库等的其他开发人员使用不同类型的列表,而不仅仅是您使用的列表,因此,在此方法中您只能使用它使用
ArrayList
,而不是LinkedList
,但您可以允许在其方法使用的其他地方使用任何List
类,这只是您的选择,因此使用接口可以允许它:在此方法参数中,您可以使用您想要使用的任何
List
类:结论:
尽可能使用接口,不要限制您或其他人使用他们想要使用的不同方法。
List
interface have several different classes -ArrayList
andLinkedList
.LinkedList
is used to create an indexed collections andArrayList
- 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 methodyou can use it only with
ArrayList
, notLinkedList
, but you can allow to use any ofList
classes on other places where it method is using, it's just your choise, so using an interface can allow it:In this method arguments you can use any of
List
classes which you want to use:CONCLUSION:
Use the interfaces everywhere when it possible, don't restrict you or others to use different methods which they want to use.