我所创建的以下地图之间的区别是什么(在另一个问题中,人们似乎可以互换地使用它们,我想知道它们是否/如何不同):
HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();
我所创建的以下地图之间的区别是什么(在另一个问题中,人们似乎可以互换地使用它们,我想知道它们是否/如何不同):
HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();
你创建了相同的地图。
但是当你使用它的时候,你可以弥补这个差异。对于第一种情况,你将能够使用特殊的HashMap方法(但我不记得有什么真正有用的方法),并且你将能够将它作为HashMap参数传递:
public void foo (HashMap<String, Object) { ... }
...
HashMap<String, Object> m1 = ...;
Map<String, Object> m2 = ...;
foo (m1);
foo ((HashMap<String, Object>)m2);
对象之间没有区别;你有一个HashMap<String,对象>在这两种情况下。你和对象之间的界面是有区别的。在第一种情况下,接口是HashMap<String, Object>,而在第二种情况下,接口是Map<String, Object>。但是底层的对象是一样的。
使用Map<String, Object>的优点是,您可以将底层对象更改为不同类型的映射,而不会破坏使用它的任何代码的契约。如果你声明它为HashMap<String, Object>,如果你想改变底层实现,你必须改变你的契约。
示例:假设我写这样一个类:
class Foo {
private HashMap<String, Object> things;
private HashMap<String, Object> moreThings;
protected HashMap<String, Object> getThings() {
return this.things;
}
protected HashMap<String, Object> getMoreThings() {
return this.moreThings;
}
public Foo() {
this.things = new HashMap<String, Object>();
this.moreThings = new HashMap<String, Object>();
}
// ...more...
}
类有一对string->对象的内部映射,它与子类共享(通过访问方法)。假设我一开始是用HashMaps写的因为我认为这是写类时使用的合适结构。
后来,Mary编写代码继承它。她对things和moreThings都有一些需要做的事情,所以她自然地把它放在一个公共方法中,并且在定义她的方法时,她使用了我在getThings/getMoreThings上使用的相同类型:
class SpecialFoo extends Foo {
private void doSomething(HashMap<String, Object> t) {
// ...
}
public void whatever() {
this.doSomething(this.getThings());
this.doSomething(this.getMoreThings());
}
// ...more...
}
后来,我决定,实际上,如果我在Foo中使用TreeMap而不是HashMap会更好。我更新Foo,将HashMap更改为TreeMap。现在,SpecialFoo不再编译了,因为我已经打破了契约:Foo曾经说它提供HashMaps,但现在它提供TreeMaps。所以我们现在必须修复SpecialFoo(这类事情可能会波及整个代码库)。
除非我有一个很好的理由来分享我的实现是使用HashMap(这确实发生了),我应该做的是声明getThings和getMoreThings只是返回Map<String,对象>而不是任何更具体的。事实上,除非有一个很好的理由去做别的事情,即使在Foo中,我也应该将things和更多的things声明为Map,而不是HashMap/TreeMap:
class Foo {
private Map<String, Object> things; // <== Changed
private Map<String, Object> moreThings; // <== Changed
protected Map<String, Object> getThings() { // <== Changed
return this.things;
}
protected Map<String, Object> getMoreThings() { // <== Changed
return this.moreThings;
}
public Foo() {
this.things = new HashMap<String, Object>();
this.moreThings = new HashMap<String, Object>();
}
// ...more...
}
注意,我现在使用Map<String, Object>在我可以的任何地方,只有在我创建实际对象时才具体。
如果我那样做了,玛丽就会这样做:
class SpecialFoo extends Foo {
private void doSomething(Map<String, Object> 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定义的方法。
正如TJ Crowder和Adamski所指出的,一个引用指向一个接口,另一个引用指向接口的特定实现。根据Joshua Block的说法,你应该总是尝试对接口进行编码,以允许你更好地处理底层实现的变化——也就是说,如果HashMap突然不适合你的解决方案,你需要改变映射实现,你仍然可以使用map接口,并改变实例化类型。
Map是HashMap实现的接口。不同之处在于,在第二个实现中,对HashMap的引用将只允许使用Map接口中定义的函数,而第一个实现将允许使用HashMap中的任何公共函数(包括Map接口)。
如果您阅读Sun的界面教程,可能会更有意义
Map是静态类型的映射,而HashMap是动态类型的映射。这意味着编译器将把您的map对象视为map类型之一,即使在运行时,它可能指向它的任何子类型。
这种针对接口而不是实现进行编程的实践具有保持灵活性的额外好处:例如,您可以在运行时替换映射的动态类型,只要它是map的子类型(例如LinkedHashMap),并动态地更改映射的行为。
一个好的经验法则是在API级别上尽可能保持抽象:例如,如果您正在编程的方法必须在Map上工作,那么将参数声明为Map而不是更严格的(因为不太抽象)HashMap类型就足够了。这样,API的使用者就可以灵活地决定他们想要传递给方法的Map实现的类型。
我只是想把它作为对已接受答案的评论,但它太时髦了(我讨厌没有换行符)
啊,所以区别在于 一般来说,Map有一定的方法 与之相关。但是有 不同的方式或创建地图,如 作为HashMap,以及这些不同的方式 提供唯一的方法 地图。
正是如此——您总是希望尽可能使用最通用的接口。考虑一下数组列表和LinkedList。在你使用它们的方式上有巨大的差异,但如果你使用“列表”,你可以很容易地在它们之间切换。
实际上,可以用更动态的语句替换初始化式的右边。这样怎么样:
List collection;
if(keepSorted)
collection=new LinkedList();
else
collection=new ArrayList();
这样,如果您要用插入排序填充集合,您将使用链表(插入排序到数组列表是违法的)。但如果你不需要保持它的排序,只是追加,你使用ArrayList(更有效的其他操作)。
这是一个相当大的扩展,因为集合不是最好的例子,但在OO设计中,最重要的概念之一是使用接口外观来访问使用完全相同的代码的不同对象。
编辑回应评论:
对于下面的map注释,是的,使用“map”接口限制你只能使用这些方法,除非你将集合从map转换回HashMap(这完全违背了目的)。
通常你要做的是创建一个对象,并使用它的特定类型(HashMap)填充它,在某种“create”或“initialize”方法中,但该方法将返回一个“Map”,不再需要作为HashMap进行操作。
如果你不得不强制转换,你可能使用了错误的接口,或者你的代码结构不够好。请注意,代码的一部分将其作为“HashMap”,而另一部分将其作为“Map”,这是可以接受的,但这应该是“向下”的。这样你就不会选角了。
还要注意由接口指示的角色的半整洁方面。LinkedList是一个很好的堆栈或队列,ArrayList是一个很好的堆栈,但是一个可怕的队列(同样,删除会导致整个列表的移位),所以LinkedList实现了队列接口,ArrayList没有。
HashMap<String, Object> map1 = new HashMap<String, Object>();
Map<String, Object> map2 = new HashMap<String, Object>();
首先,Map是一个接口,它有不同的实现,如HashMap, TreeHashMap, LinkedHashMap等。接口的工作方式类似于实现类的超类。因此,根据面向对象的规则,任何实现Map的具体类也是Map。这意味着我们可以将任何HashMap类型变量赋值/放置到Map类型变量,而不需要任何类型的强制转换。
在这种情况下,我们可以将map1分配给map2,而不需要任何强制转换或丢失数据
map2 = map1
Map有以下实现:
HashMap Map m = new HashMap(); LinkedHashMap映射m = new LinkedHashMap(); TreeMap Map m = new TreeMap(); WeakHashMap映射m = new WeakHashMap();
假设您已经创建了一个方法(这只是伪代码)。
public void HashMap getMap(){
return map;
}
假设你的项目需求改变了:
该方法应该返回映射内容-需要返回HashMap。 该方法应该返回映射键的插入顺序-需要改变返回类型HashMap到LinkedHashMap。 该方法应该按顺序返回映射键-需要将返回类型LinkedHashMap更改为TreeMap。
如果您的方法返回特定的类,而不是实现Map接口的东西,那么您每次都必须更改getMap()方法的返回类型。
但是如果您使用Java的多态性特性,而不是返回特定的类,而是使用接口Map,它将提高代码的可重用性并减少需求更改的影响。
加上投票最多的答案和上面许多强调“更通用,更好”的答案,我想再挖掘一点。
Map是一个结构契约,而HashMap是一个实现,它提供了自己的方法来处理不同的实际问题:如何计算索引,容量是多少以及如何增加它,如何插入,如何保持索引惟一,等等。
让我们看看源代码:
在Map中,我们有containsKey(对象键)方法:
boolean containsKey(Object key);
JavaDoc:
boolean java.util.Map.containsValue(Object value) Returns true if this map maps one or more keys to the specified value. More formally, returns true if and only if this map contains at least one mapping to a value v such that (value==null ? v==null : value.equals(v)). This operation will probably require time linear in the map size for most implementations of the Map interface. Parameters:value value whose presence in this map is to betested Returns:true if this map maps one or more keys to the specified valueThrows: ClassCastException - if the value is of an inappropriate type for this map (optional) NullPointerException - if the specified value is null and this map does not permit null values (optional)
它需要它的实现来实现它,但“如何”是自由的,只是为了确保它返回正确的结果。
在HashMap:
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
事实证明,HashMap使用hashcode来测试这个映射是否包含键。所以它有哈希算法的优点。