(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
我理解,使用(1),可以交换 List 接口的实现。似乎(1)通常在应用程序中使用而不管需要(我自己总是使用它)。
我想知道是否有人使用(2)?
另外,经常(并且我可以得到一个例子)情况实际上需要使用(1)over(2)(即where(2)不足以......除 编码之外接口 和 最佳做法 等。)
答案 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
的列表类。其他人包括CopyOnWriteList
,Stack
和Vector
。
我见过人们对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();
供参考:
(主要针对收集图发布)
答案 3 :(得分:23)
被认为是好样式 ,可以在类型为Set的变量中存储对HashSet
或TreeSet
的引用。
Set<String> names = new HashSet<String>();
这样,如果您决定使用TreeSet
,则只需更改一行。
此外,对集合进行操作的方法应指定Set:
类型的参数 public static void print(Set<String> s)
然后 该方法可用于所有设置实现 。
理论上,我们应该对链表做出相同的建议,即保存
Linked类型中的LinkedList引用。但是,在Java库中,List接口对ArrayList
和LinkedList
类都是通用的。特别是,它具有获取和设置随机访问的方法,即使这些方法对于链表非常低效。
如果您不知道随机访问是否有效, 无法编写有效的代码 。
这显然是标准库中的一个严重的设计错误,我不建议使用 因为这个原因的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 principle或Dependency 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
将来。因此它将您与特定实现分离。现在有两点值得一提:
答案 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是首选,除非
我的猜测是99%的情况下你可以使用List,这是首选。
removeAll
或add(null)
答案 14 :(得分:1)
List
界面有几个不同的类 - ArrayList
和LinkedList
。 LinkedList
用于创建索引集合和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);
<强>结论:强>
尽可能在任何地方使用界面,不要限制您或其他人使用他们想要使用的不同方法。