阅读其他人的代码,我见过很多:
List<E> ints = new ArrayList<E>();
Map<K, V> map = new HashMap<K, V>();
我的问题是:以这种方式实例化它们的意义/优势是什么,而不是:
ArrayList<E> ints = new ArrayList<E>();
HashMap<K, V> map = new HashMap<K, V>();
奇怪的是,我从未见过像:
CharSequence s = new String("String");
或
OutputStream out = new PrintStream(OutputStream);
重复(问题的第一部分):
When/why to use/define an interface
Use interface or type for variable definition in java?
When should I use an interface in java?
why are interfaces created instead of their implementations for every class
What's the difference between these two java variable declarations?
答案 0 :(得分:24)
快速回答?使用接口和超类可以提高代码的可移植性和可维护性,主要是隐藏实现细节。采取以下假设的例子:
class Account {
private Collection<Transaction> transactions;
public Account() {
super();
transactions = new ArrayList<Transaction>(4);
}
public Collection<Transaction> getTransactions() {
return transactions;
}
}
我已经宣布了一个帐户合同,该合同规定发布到帐户的交易可以作为集合检索。我的代码的调用者不必关心我的方法实际返回什么样的集合,也不应该。如果需要的话,这可以让我更改内部实现,而不会影响(也就是打破)未知数量的客户端。所以,如果我发现我需要在我的事务中强加某种独特性,我可以将上面显示的实现从ArrayList更改为HashSet,对使用我的类的任何人都没有负面影响
public Account() {
super();
transactions = new HashSet<Transaction>(4);
}
就你的第二个问题而言,我可以说你在任何有意义的地方都使用了可移植性和封装的原则。在那里没有可怕的CharSequence实现,而String是迄今为止最常用的。所以你不会看到很多开发人员在他们的代码中声明CharSequence变量。
答案 1 :(得分:11)
使用接口的主要优点是以后可以更改实现(类),而无需更改多于创建实例的单行并进行分配。
答案 2 :(得分:7)
有关
List<E> ints = new ArrayList<E>();
Map<K, V> map = new HashMap<K, V>();
List
和Map
是接口,因此可以将实现这些接口的任何类分配给这些引用。
ArrayList
是实现LinkedList
接口的几个类之一(另一个是List
)。
与Map
相同。 HashMap
,LinkedHashMap
,TreeMap
都实现了Map。
这是一般原则 为接口编程而不是为实现编程 。因此,编程任务变得更容易。您可以动态更改引用的行为。
如果你写
ArrayList<E> ints = new ArrayList<E>();
HashMap<K, V> map = new HashMap<K, V>();
ints
和map
将永远只有ArrayList
和HashMap
。
答案 3 :(得分:4)
您的设计原则是program to the interface and not to the implementation。
这样,您可以稍后向同一界面提供新的实现。
从以上链接Eric Gamma解释:
这个原则实际上是关于依赖关系,必须在大型应用程序中谨慎管理。在类上添加依赖项很容易。这太简单了;只需添加一个import语句,Eclipse等现代Java开发工具甚至可以为您编写此语句。有趣的是,反过来并不容易,摆脱不必要的依赖可能是真正的重构工作,甚至更糟,阻止你在另一个上下文中重用代码。因此,在引入依赖关系时,您必须睁大眼睛。这个原则告诉我们,依赖于接口通常是有益的。
这里,终端interface
不仅指Java工件,而且指的是给定对象的公共接口,它基本上由它拥有的方法组成,因此,它可以是一个Java接口(如{你的例子中的{1}}或具体的超类。
因此,在您的示例中,如果您想要使用List
,则会更难,因为只有列表已经足够,类型已经被声明为LinkedList
。
当然,如果您需要来自给定实现的特定方法,则必须声明该类型。
我希望这会有所帮助。
答案 4 :(得分:4)
@Bhushan回答了原因。 回答你的困惑为什么没有人使用
CharSequence s = new String("String");
或
OutputStream out = new PrintStream(OutputStream);
CharSequence
只包含几种常用方法。实现此接口的其他类主要是缓冲区,只有String
是不可变的。 CharSequence
为char数组支持的类定义了公共api,此接口不会优化equals和hashCode方法的常规协定(参见javadoc)。
OutputStream
是用于写入数据的低级API。因为PrintStream
添加了更方便的编写方法 - 更高级别的抽象,所以它在OutputStream上使用。
答案 5 :(得分:3)
这样做是为了确保以后在使用变量时(或任何使用类的人)不依赖于所选实现的特定方法(ArrayList,HashMap等)
答案 6 :(得分:3)
背后的原因不是技术性的,而是你必须在代码行之间阅读的内容:List
和Map
示例说:“我只对基本的列表/地图内容感兴趣,基本上你可以在这里使用任何东西。“一个极端的例子是
Iterable<Foo> items = new ArrayList<Foo>();
当你真的只想为每件事做一些事情时。
作为一个额外的好处,这使得稍后将代码重构为不需要具体类型的常见实用程序类/方法更容易一些。或者您想为每种集合多次编写算法代码?
另一方面,String
示例并不常见,因为a)String
是Java中的特殊类 - 每个"foo"
文字自动为String
并且更快或者稍后你有给一些只接受String
的方法,而b)CharSequence
实际上是 ahh minimal。它甚至不支持BMP以外的Unicode,它错过了String
的大多数查询/操作方法。
答案 7 :(得分:2)
将类型声明为类实现的Interface
这种(好)风格很重要,因为它迫使我们使用仅在Interface
中定义的方法。
因此,当我们需要更改我们的类实现时(即我们发现我们的ArraySet
优于标准HashSet
),我们保证如果我们更改类,我们的代码将起作用,因为这两个类都实现了严格执行的Interface
。
答案 8 :(得分:1)
从String
开始考虑String
更容易。从WhateverList
开始考虑List
更容易(也更有益)。
奖金会多次讨论,但简而言之,您只需将问题分开:当您需要CharSequence
时,即可使用它。您不太可能仅需要ArrayList
:通常,任何列表都可以。
答案 9 :(得分:1)
当您在某个时候决定使用不同的实现时,请说:
List<E> ints = new LinkedList<E>();
而不是
List<E> ints = new ArrayList<E>();
此更改需要仅在一个地方。
有适当的平衡:
通常您使用的类型为您提供最合适的保证。显然,List
也是Collection
,也是Iterable
。但是一个集合没有给你一个订单,而一个iterable没有“添加”方法。
使用ArrayList
作为变量类型也是合理的,当你想更明确地说明对象位置需要快速随机访问时 - 在LinkedList中,“get(100)”是一个慢得多。 (如果Java有这样的接口会很好,但我认为没有。通过使用ArrayList,你不允许将数组作为列表。)
答案 10 :(得分:1)
List<E> ints = new ArrayList<E>();
如果您编写的代码只处理List
,那么它适用于任何实现List
的类(例如LinkedList
等)。但是,如果您的代码直接处理ArrayList
,那么它仅限于ArrayList
。
CharSequence s = new String("String");
手动实例化String
对象并不好。您应该使用字符串文字。我只是猜测你没有看到CharSequence
可能的原因,因为它很新,而且字符串是不可变的。
答案 11 :(得分:0)
根据Gang of Four,这是对接口的编程,而不是实现。这将有助于阻止代码依赖于仅添加到特定实现的方法,并且如果由于某种原因而变得必要,则使得更容易更改以使用不同的实现,例如,性能