Reason for - List list = new ArrayList();

31
我经常看到这样的代码:

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

为什么人们使用ArrayList(和其他类)的父类而不是生成的对象类型呢? 这样做是否会降低性能?或者说为什么有人要这样做呢?

9
关于“性能”的执着是怎么回事?90%的时间,那都是编写代码中最不重要的方面! - Joachim Sauer
2
https://dev59.com/3XRC5IYBdhLWcg3wMd5S - kiheru
你也可以看一下这个链接:https://dev59.com/eWQm5IYBdhLWcg3wowMF - Marc
@JoachimSauer 或许最简单的方法是告诉新手,这会极大地提高性能,就像“每个人都知道…”一样。 - Ingo
1
如果我在程序中看不到某个事情的原因,我还能问什么呢?我不会说获取父引用会使代码更加“可读” :) 但现在我有了答案,感谢大家。 - codepleb
1
@TrudleR:它传达了意图:“List x = new ArrayList()”表示您实际上不需要任何“ArrayList”的特殊功能,您“承诺”仅使用“List”接口公开的功能。更进一步的是,“Collection x = new ArrayList()”甚至是“Iterable x = new ArrayList()”。 - Joachim Sauer
7个回答

43
当某人编写像这样的代码时,他/她试图遵循基本的面向对象设计原则,即:

针对接口编程,而不是具体实现

我在我的博客文章中解释了这个原则。请查看类继承VS接口继承部分。

总结一下文章,当您使用父类型的引用来引用子类型的实例时,您会获得很多灵活性。例如,如果您将来需要更改子类型实现,您可以轻松地完成此操作,而不需要更改大量的代码。

考虑以下方法-

public void DoSomeStuff(Super s) {
    s.someMethod();
}

并且对这个方法进行调用 -

DoSomeStuff(new Sub());

现在,如果您需要更改someMethod内部的逻辑,您可以通过声明Super的新子类型(例如NewSubType)并更改该实现中的逻辑来轻松完成。通过这种方式,您将永远不必触及使用该方法的其他现有代码。您仍然可以以以下方式使用您的DoSomeStuff方法 -
DoSomeStuff(new NewSubType());

如果您将DoSomeStuff的参数声明为Sub,那么您还需要更改其实现方式。
DoSomeStuff(NewSubType s) {
    s.someMethod();
}

它还可以链接/冒泡到其他几个地方。

就你的集合示例而言,这使得你可以在不费吹灰之力地更改变量指向的列表实现。你可以轻松地使用LinkedList代替ArrayList


3
“基于接口编程,而不是具体实现”是一条很好的建议。然而,这并不能成为使用 List<String> list = new ArrayList<String>(); 的论据,因为a)由于构造函数,这段代码仍然与ArrayList绑定在一起;b)这更多地涉及到API设计而非私有实现。这是从一开始学习Java时就要学的东西,但并没有真正的道理。通常情况下,实现会提供一些便利功能,如果你坚持使用接口,你将从未看到它们。尽管如此,该建议仍然是有益的。(+1) - atamanroman
1
但是,他的观点仍然站得住脚。您可以通过更改参数来交换功能。这支持几个不错的代码特性,例如依赖注入、松散耦合和可替换性。 - christopher
@atamanroman:这个原则的一个结果是鼓励您根据它们的“接口”来引用具体实例。这就是为什么我认为这在这里是相关的原因。 - MD Sayem Ahmed
@Chris:完全同意你的观点。 - MD Sayem Ahmed
在原始问题中,没有参数。只有 List<Foo> l = new ArrayList<Foo>(); 如果实现发生变化,那么这行代码也会改变。如果是 ArrayList<Foo> l = new ArrayList<Foo>(); 也不会有任何不同。如果列表是外部依赖项(以某种方式注入),整个情况就会改变。在这种情况下,编程到接口会产生差异,并且强烈建议这样做。 - atamanroman
正如我所说的,基本思想是使用超类型的引用来操作实例。如果仅使用“List”中定义的方法就足以操作该实例,则最好这样做。谁知道未来他可能会决定将其作为属性而不是局部变量,并且他还可能决定注入一个不同于“ArrayList”的实现。显然,通过“List”进行引用在这里会给他带来优势。这里重要的是“实践”。 - MD Sayem Ahmed

13

当你写作时:

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

那么你确定只会使用 接口 List 的功能。
(ArrayList 实现了 List,所以 List 更加灵活)。使用这个可以让你将来将 ArrayList 更改为其他类型 (例如 LinkedList..)。


谢谢。这真的很有帮助。 - Nibras

12

这意味着您可以在任何时候将list的类型与实现List接口的任何东西进行交换,而不是创建一个只能使用ArrayList的严格模型。例如:

private List<String> list;

public SomeConstructor()
{
     // At this point, you can make it any type of object you want.
     list = new ArrayList<String>();
     list = new LinkedList<String>();
     list = new AttributeList<String>();
}

这将抽象化使用list对象的代码,远离像list是什么精确对象类型这样的细节。它只需要知道它有add方法等。这被称为松耦合


5
为了梳理清楚:
为了更灵活,您可以启动接口:
如果您不需要所有的ArrayList,请仅使用List。
您可以编写类似于:List<String> = Arrays.asList("aa", "bb","cc") 当然,少量功能可以帮助提高性能。正如您所知,如果您想要使用多线程应用程序,请改用Vector,但它会降低性能。
从这里获取:http://www.wilsonmar.com/1arrays.htm 图片说明请见下图: enter image description here

2
http://www.wilsonmar.com/1arrays.htm - Maxim Shoustin
@Maxim Shoustin:好图!这是否意味着我应该以“AbstractMap”作为所有映射集合对象的参考? - codepleb
1
@TrudleR:不,AbstractMap是一种实现细节,你通常不需要关心它:你要么使用Map接口引用映射,要么使用更具体的接口(如NavigableMap)。在极少数情况下,您可能需要特定的实现类型,但除了在扩展它时,很少有人会直接引用AbstractMap - Joachim Sauer

4
因为一个方法并不需要知道你使用了哪种列表实现。
方法只需要知道它是一个列表。
该方法仍然可以使用。
始终针对接口进行编程,而不是具体实现(在这种情况下是List)。

3

通常最好使用接口类 (List 在这个例子中),以便稍后可以轻松地用任何 List 实现替换它,如果需求发生变化。

虽然 ArrayList 可能支持一些不在 List 接口上的方法,但是这个声明明确表明那些额外的方法在这种情况下并不相关。


2
List<String> list = new ArrayList<String>();

在集合框架中,List 是一个接口,而 ArrayList 是其实现。你这样做的主要原因是为了将代码与接口的特定 实现 解耦,同时如果你希望在未来转移到其他 List 实现,这也会很有帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接