为什么我们在实例化它们之前首先将子类型声明为它们的超类型?
阅读其他人的代码,我见过很多:
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();
List
和Map
是接口,因此可以将实现这些接口的任何类分配给这些引用。
ArrayList
是实现List
接口的几个类之一(另一个是LinkedList
)。
与Map
相同。 HashMap
, LinkedHashMap
, TreeMap
都实现了Map。
这是一个通用原则来编程接口而不是实现 。 因此,编程任务变得更容易。 您可以动态更改引用的行为。
如果你写
ArrayList ints = new ArrayList (); HashMap map = new HashMap();
ints
和map
将永远只是ArrayList
和HashMap
。
是您编程到接口而不是实现的设计原则。
这样,您可以稍后向同一界面提供新的实现。
从以上链接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等)
这背后的原因不是技术性的,而是你必须在代码行之间阅读的内容: List
和Map
示例说:“我只对基本的列表/地图内容感兴趣,基本上你可以在这里使用任何东西。” 一个极端的例子是
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的说法,这是对接口的编程,而不是实现。 这将有助于阻止代码依赖于仅添加到特定实现的方法,并且如果由于某种原因(例如性能)变得必需,则更容易更改以使用不同的实现。