我创建的以下地图之间有什么区别(在另一个问题中,人们似乎可以互换使用它们,我想知道它们是否/如何不同):

HashMap map = new HashMap();
Map map = new HashMap();


您创建相同的地图。

但是您可以在使用时弥补差异。 在第一种情况下,您将能够使用特殊的HashMap方法(但我不记得任何人真的有用),并且您可以将其作为HashMap参数传递:

public void foo (HashMap
...
HashMap m1 = ...;
Map m2 = ...;
foo (m1);
foo ((HashMap)m2);


对象之间没有区别; 在两种情况下HashMap您都有HashMap 。 与对象之间的接口有所不同。 在第一种情况下,接口是HashMap ,而在第二种情况下,接口是Map 。 但是底层对象是相同的。

使用Map的优点在于,您可以将基础对象更改为另一种类型的映射,而不会违反使用该映射的任何代码的约定。 如果将其声明为HashMap , HashMap更改基础实现,则必须更改合同。

示例:假设我编写了此类:

class Foo {
private HashMap things;
private HashMap moreThings;
protected HashMap getThings() {
return this.things;
}
protected HashMap getMoreThings() {
return this.moreThings;
}
public Foo() {
this.things = new HashMap();
this.moreThings = new HashMap();
}
// ...more...
}

该类有一些string-> object的内部映射,它与子类共享(通过访问器方法)。 假设我首先使用HashMap编写它,因为我认为这是编写类时要使用的适当结构。

后来,玛丽编写了将其子类化的代码。 她需要同时处理things和moreThings ,因此自然而然地将其放在一个通用方法中,并且在定义她的方法时,她使用与

getThings / getMoreThings相同的类型:
class SpecialFoo extends Foo {
private void doSomething(HashMap t) {
// ...
}
public void whatever() {
this.doSomething(this.getThings());
this.doSomething(this.getMoreThings());
}
// ...more...
}

稍后,我决定实际上,最好是在Foo使用TreeMap而不是HashMap 。 我更新Foo ,将HashMap更改为TreeMap 。 现在, SpecialFoo不再编译,因为我违反了合同: Foo曾经说它提供了HashMap ,但是现在提供了TreeMaps 。 因此,我们现在必须修复SpecialFoo (这种事情可能会在代码库中引起涟漪)。

除非我有一个很好的理由要分享我的实现正在使用HashMap (并且确实发生了),否则我应该做的就是将getThings和getMoreThings声明为仅返回Map而没有比这更具体的声明。 实际上,除非有充分的理由做其他事情,即使在Foo我也应该将things和moreThings things声明为Map ,而不是HashMap / TreeMap :

class Foo {
private Map things; // <== Changed
private Map moreThings; // <== Changed
protected Map getThings() { // <== Changed
return this.things;
}
protected Map getMoreThings() { // <== Changed
return this.moreThings;
}
public Foo() {
this.things = new HashMap();
this.moreThings = new HashMap();
}
// ...more...
}

请注意,我现在如何在所有可能的地方使用Map ,仅在创建实际对象时才具体使用。

如果我这样做了,那么玛丽就会做到这一点:

class SpecialFoo extends Foo {
private void doSomething(Map t) { // <== Changed
// ...
}
public void whatever() {
this.doSomething(this.getThings());
this.doSomething(this.getMoreThings());
}
}

...并且更改Foo不会使SpecialFoo停止编译。

接口(和基类)使我们仅显示必要的内容,因此可以灵活地进行更改。 总的来说,我们希望参考文献尽可能基本。 如果我们不需要知道它是HashMap ,只需将其称为Map 。

这不是一个盲目的规则,但总的来说, 与最特定的接口编码相比,对最通用的接口进行编码将不那么困难。 如果我还记得这一点,那么我不会创建一个让Mary因SpecialFoo失败而创建的Foo 。 如果Mary记得这一点,那么即使我搞砸了Foo ,她也会使用Map而不是HashMap声明她的私有方法,并且我更改Foo的合同不会影响她的代码。

有时候你做不到,有时候你必须要具体。 但是除非有理由,否则请针对最不特定的界面。


在第二个示例中,“ map”引用的类型为Map ,这是由HashMap (以及其他类型的Map )实现的接口。 这个接口是一个约定,表示对象将键映射到值并支持各种操作(例如put和get )。 它没有提到 Map 的实现 (在本例中为HashMap )。

通常首选第二种方法,因为您通常不希望使用Map或通过API定义将特定的Map实现公开给方法。


Map是Interface,而Hashmap是实现该接口的类。

因此,在此实现中,您将创建相同的对象


正如TJ Crowder和Adamski所指出的,一种参考是接口,另一种是接口的特定实现。 根据Joshua Block的说法,您应该始终尝试对接口进行编码,以使您能够更好地处理对基础实现的更改-即,如果HashMap突然对于您的解决方案不理想,并且您需要更改Map的实现,那么您仍然可以使用Map接口,并更改实例化类型。