为什么我们在实例化它们之前首先将子类型声明为它们的超类型?

阅读其他人的代码,我见过很多:

List ints = new ArrayList(); Map map = new HashMap(); 

我的问题是:以这种方式实例化它们的意义/优点是什么,而不是:

 ArrayList ints = new ArrayList(); HashMap map = new HashMap(); 

令人感到奇怪的是,我从未见过如下:

 CharSequence s = new String("String"); 

要么

 OutputStream out = new PrintStream(OutputStream); 

重复(问题的第一部分):

何时/为何使用/定义接口

在java中使用接口或类型进行变量定义?

我什么时候应该在java中使用接口?

为什么创建接口而不是每个类的实现

这两个java变量声明有什么区别?

快速回答? 使用接口和超类可以提高代码的可移植性和可维护性,主要是隐藏实现细节。 采用以下假设示例:

 class Account { private Collection transactions; public Account() { super(); transactions = new ArrayList(4); } public Collection getTransactions() { return transactions; } } 

我已经宣布了一个帐户合同,该合同规定发布到帐户的交易可以作为集合检索。 我的代码的调用者不必关心我的方法实际返回什么样的集合,也不应该。 如果我需要的话,这可以让我更改内部实现,而不会影响(也就是打破)未知数量的客户端。 因此,如果我发现我需要在我的事务中强加某种独特性,我可以将上面显示的实现从ArrayList更改为HashSet,而不会对使用我的类的任何人产生负面影响。

 public Account() { super(); transactions = new HashSet(4); } 

至于你的第二个问题,我可以说你使用可移植性和封装的原则,无论它们有意义。 在那里没有可怕的CharSequence实现,而String是迄今为止最常用的。 所以你不会看到很多开发人员在他们的代码中声明CharSequence变量。

使用接口的主要优点是, 您可以稍后更改实现 (类),而无需更改多于创建实例的单行并执行分配。

对于

 List ints = new ArrayList(); Map map = new HashMap(); 

ListMap是接口,因此可以将实现这些接口的任何类分配给这些引用。

ArrayList是实现List接口的几个类之一(另一个是LinkedList )。

Map相同。 HashMapLinkedHashMapTreeMap都实现了Map。

这是一个通用原则来编程接口而不是实现 。 因此,编程任务变得更容易。 您可以动态更改引用的行为。

如果你写

 ArrayList ints = new ArrayList(); HashMap map = new HashMap(); 

intsmap将永远只是ArrayListHashMap

是您编程到接口而不是实现的设计原则。

这样,您可以稍后向同一界面提供新的实现。

从以上链接Eric Gamma解释说:

这个原则实际上是关于依赖关系,必须在大型应用程序中仔细管理。 在类上添加依赖项很容易。 这太简单了; 只需添加一个import语句,Eclipse等现代Java开发工具甚至可以为您编写此语句。 有趣的是,反过来并不容易,摆脱不必要的依赖可能是真正的重构工作,甚至更糟,阻止你在另一个上下文中重用代码。 因此,在引入依赖关系时,您必须睁大眼睛。 这个原则告诉我们,依赖于接口通常是有益的。

这里,终端interface不仅指Java工件,而且指的是给定对象所具有的公共接口,它基本上由它拥有的方法组成,因此,它可以是Java接口(如示例中的List )或具体的超类。

因此,在您的示例中,如果您想要使用LinkedList ,则会更难,因为只有list才足以将类型声明为ArrayList

当然,如果您需要来自给定实现的特定方法,则必须声明该类型。

我希望这有帮助。

@Bhushan回答了原因。 回答你的困惑为什么没有人使用

 CharSequence s = new String("String"); 

要么

 OutputStream out = new PrintStream(OutputStream); 

CharSequence只包含几种常用方法。 实现此接口的其他类主要是缓冲区,只有String是不可变的。 CharSequence为char数组支持的类定义了公共api,并且此接口不会优化equals和hashCode方法的常规协定 (请参阅javadoc )。

OutputStream是用于写入数据的低级api。 因为PrintStream 为写入添加了更方便的方法 – 更高级别的抽象,所以它在OutputStream上使用。

这样做是为了确保以后在使用变量时(或任何使用类的人)不依赖于所选实现的特定方法(ArrayList,HashMap等)

这背后的原因不是技术性的,而是你必须在代码行之间阅读的内容: ListMap示例说:“我只对基本的列表/地图内容感兴趣,基本上你可以在这里使用任何东西。” 一个极端的例子是

 Iterable items = new ArrayList(); 

当你真的只想为每件事做一些事情。

作为一个额外的好处,这使得稍后将代码重构为不需要具体类型的常见实用程序类/方法更容易一些。 或者您想为每种集合多次编写算法代码?

另一方面, String示例并不常见,因为a) String是Java中的特殊类 – 每个"foo"文字自动为String ,迟早你必须将字符赋予某些只接受String和b的方法) CharSequence真的是最小的。 它甚至不支持BMP以外的Unicode,它错过了String大多数查询/操作方法。

将类型声明为类实现的Interface的这种(好)样式很重要,因为它迫使我们使用仅在Interface定义的方法。

因此,当我们需要更改我们的类实现时(即我们发现我们的ArraySet比标准HashSet更好),我们保证如果我们更改类,我们的代码将起作用,因为这两个类都实现了严格执行的Interface

String视为String更容易。 从List中可以WhateverList更容易(也更有益)。

奖金会多次讨论,但简而言之,您只需将问题分开:当您需要CharSequence ,您可以使用它。 你不太可能需要ArrayList 通常,任何List都可以。

当您在某个时候决定使用不同的实现时,请说:

 List ints = new LinkedList(); 

代替

 List ints = new ArrayList(); 

这种变化只需要在一个地方完成

罢工有适当的平衡:

通常你使用的类型给你最合适的保证。 显然, List也是一个Collection ,它也是可以Iterable东西。 但是一个集合不会给你一个订单 ,而一个iterable没有“添加”方法。

使用ArrayList作为变量类型也是合理的,当你想要更明确地说明对象位置需要快速随机访问时 – 在LinkedList中,“get(100)”要慢得多。 (如果Java有这样的接口会很好,但我认为没有。通过使用ArrayList,你不允许将数组作为列表。)

  List ints = new ArrayList(); 

如果你编写一些只处理List代码,那么它将适用于任何实现List类(例如LinkedList等)。 但是,如果您的代码直接处理ArrayList那么它仅限于ArrayList

 CharSequence s = new String("String"); 

手动实例化String对象并不好。 你应该使用字符串文字。 我只是猜测你没有看到CharSequence的原因可能是因为它很新,而且字符串是不可变的。

根据Gang of Four的说法,这是对接口的编程,而不是实现。 这将有助于阻止代码依赖于仅添加到特定实现的方法,并且如果由于某种原因(例如性能)变得必需,则更容易更改以使用不同的实现。