Java中的HashMap和Map对象有什么不同?
我创build的以下地图之间有什么区别(在另一个问题中,人们似乎可以互换地使用它们,我想知道它们是不同的):
HashMap<String, Object> map = new HashMap<String, Object>(); Map<String, Object> map = new HashMap<String, Object>();
对象之间没有区别; 在这两种情况下你都有一个HashMap<String, Object>
。 你在界面上有一个不同的地方。 在第一种情况下,接口是HashMap<String, Object>
,而第二种情况是Map<String, Object>
。 但是底层对象是一样的。
使用Map<String, Object>
的优点是,您可以将底层对象更改为不同types的地图,而不会违反使用它的任何代码的约定。 如果你声明它为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-> object的内部映射,它通过子类来共享(通过访问器方法)。 比方说,我用HashMap
编写,因为我认为这是编写类时适当的结构。
后来,玛丽写代码子类。 她有两things
需要moreThings
,她自然而然地采用了一种常用的方法,她在定义她的方法时使用了与getThings
/ getMoreThings
相同的types:
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
曾经说它提供了HashMap
,但是现在它提供了TreeMaps
。 所以我们现在必须修复SpecialFoo
(这种东西可以通过代码库)。
除非我有一个很好的理由来分享我的实现是使用HashMap
(而且确实发生了),否则我应该做的就是将getThings
和getMoreThings
声明为只返回Map<String, Object>
而不仅仅是这个。 事实上,除了一个好的理由去做别的事情,即使在Foo
我也应该把things
和moreThings
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>
,只是在创build实际对象时才具体。
如果我这样做了,玛丽会做到这一点:
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
。
这不是盲目的规则,但总的来说, 编码到最通用的接口将比编码更具体的更脆弱。 如果我记得的话,我不会创build一个Foo
,因为SpecialFoo
而失败。 如果玛丽记得那个,那么即使我搞砸了Foo
,她也会用Map
而不是HashMap
来声明她的私有方法,而且我改变Foo
的合同不会影响她的代码。
有时你不能这样做,有时你必须具体。 但是,除非你有一个理由,否则就会碰到最不具体的界面。
Map是HashMap实现的接口。 不同的是,在第二个实现中,对HashMap的引用只允许使用Map接口中定义的函数,而第一个将允许在HashMap(包括Map接口)中使用任何公共函数。
如果您阅读Sun的界面教程,这可能会更有意义
我只是要做这个接受答案的评论,但它太时髦了(我讨厌没有换行符)
啊,所以区别在于一般情况下,Map有一些相关的方法。 但有不同的方法或创build一个地图,如HashMap,这些不同的方式提供了不是所有地图都有的独特方法。
确切地说 – 你总是希望使用最通用的界面。 考虑ArrayList vs LinkedList。 你如何使用它们有很大的区别,但是如果你使用“List”,你可以很容易地在它们之间切换。
事实上,你可以用更dynamic的语句replace初始化器的右边。 怎么样这样的事情:
List collection; if(keepSorted) collection=new LinkedList(); else collection=new ArrayList();
这样,如果要使用插入sorting来填充集合,则可以使用链接列表(对数组列表进行插入sorting是犯罪行为)。但是,如果您不需要保留sorting并且只是追加,你使用一个ArrayList(更有效的其他操作)。
这是一个很大的延伸,因为集合不是最好的例子,但是在OOdevise中,最重要的概念之一是使用界面外观来访问具有完全相同代码的不同对象。
编辑回应评论:
至于你的地图评论下面,是使用“地图”界面限制你只有这些方法,除非你把集合从映射到HashMap(完全失败的目的)。
通常你要做的就是创build一个对象,并用它的特定types(HashMap)填充它,在某种“创build”或“初始化”方法中,但是该方法将返回一个不需要的“映射”操作作为一个HashMap的更多。
如果你不得不依靠,你可能使用了错误的界面,或者你的代码结构不够好。 请注意,将代码中的一部分视为“HashMap”,而另一部分视为“Map”则是可以接受的,但是这应该是“向下”的。 所以你永远不会铸造。
还要注意接口指示的angular色的半整齐方面。 LinkedList是一个很好的堆栈或队列,ArrayList是一个很好的堆栈,但却是一个可怕的队列(再一次,移除会导致整个列表的移位),所以LinkedList实现了Queue接口,而ArrayList则没有。
具有以下实现的地图,
-
HashMap
Map m = new HashMap();
-
LinkedHashMap
Map m = new LinkedHashMap();
-
Tree Map
Map m = new TreeMap();
-
WeakHashMap
Map m = new WeakHashMap();
假设你已经创build了一个方法(这只是spudo代码)。
public void HashMap getMap(){ return map; }
假设你的项目需求每次都在变化如下,
- 方法应该返回地图内容 – 需要返回
HashMap
。 - 方法应该返回地图键的插入顺序 – 需要将返回types
HashMap
更改为LinkedHashMap
。 - 方法应该返回映射键的sorting顺序 – 需要将返回types
LinkedHashMap
更改为TreeMap
。
如果您的方法返回特定的类而不是Map
接口,则每次都必须更改getMap()
方法的返回types。
但是,如果使用java的多态性function,而不是返回具体的类使用的接口Map
,它会导致代码的可重用性和更less的影响,如果任何要求改变。
正如TJ Crowder和Adamski所指出的,一个参考是一个接口,另一个是接口的具体实现。 根据Joshua Block的说法,你应该总是尝试编写接口代码,以便更好地处理底层实现的变化 – 也就是说,如果HashMap突然不适合你的解决scheme,并且需要更改地图实现,那么仍然可以使用Map接口,并更改实例化types。
在你的第二个例子中,“map”引用的types是Map
,它是由HashMap
(和其他types的Map
)实现的接口。 这个接口是一个合约,表示该对象将键映射到值并支持各种操作(例如put
, get
)。 它没有提到 Map
的实现 (在本例中是一个HashMap
)。
第二种方法通常是首选的,因为您通常不希望将特定的映射实现公开给使用Map
或通过API定义的方法。
Map是地图的静态types ,而HashMap是地图的dynamictypes 。 这意味着编译器会将你的地图对象视为Maptypes之一,尽pipe在运行时它可能指向它的任何子types。
这种针对接口而不是实现进行编程的做法具有保持灵活性的附加好处:例如,您可以在运行时replacedynamictypes的地图,只要它是Map的子types(例如LinkedHashMap),然后将地图的行为更改为苍蝇。
一个好的经验法则是在API级别保持尽可能的抽象:例如,如果你正在编程的方法必须在地图上工作,那么声明一个参数为Map就足够了,而不是更严格(因为不太抽象)HashMaptypes。 这样,您的API的使用者可以灵活地将他们想要传递给您的方法的一种Map实现。
你创build相同的地图。
但是当你使用它时,你可以填补这个差异。 在第一种情况下,您可以使用特殊的HashMap方法(但我不记得任何真正有用的),您可以将其作为HashMapparameter passing给它:
public void foo (HashMap<String, Object) { ... } ... HashMap<String, Object> m1 = ...; Map<String, Object> m2 = ...; foo (m1); foo ((HashMap<String, Object>)m2);
Map是接口,Hashmap是一个实现Map接口的类
Map是接口,Hashmap是实现它的类。
所以在这个实现中你创build了相同的对象
HashMap是Map的一个实现,所以它是完全一样的,但是具有“clone()”方法,正如我在参考指南中看到的那样))
HashMap<String, Object> map1 = new HashMap<String, Object>(); Map<String, Object> map2 = new HashMap<String, Object>();
首先Map
是一个接口,它具有不同的实现,如HashMap
, TreeHashMap
, LinkedHashMap
等。接口的工作方式类似于实现类的超类。 所以根据OOP的规则,任何实现Map
具体类都是一个Map
。 这意味着我们可以将任何HashMap
types的variables赋值给一个Map
typesvariables,而不需要任何types的转换。
在这种情况下,我们可以将map1
分配给map2
而不需要任何投射或丢失数据 –
map2 = map1
除了强调“更通用,更好”之外,我还想再多加一点。
Map
是结构契约,而HashMap
是一个实现,它提供了自己的方法来处理不同的实际问题:如何计算索引,如何计算索引,如何增加容量,如何插入,如何保持索引独特等。
让我们来看看源代码:
在Map
我们有containsKey(Object key)
:
boolean containsKey(Object key);
JavaDoc的:
boolean java.util.Map.containsValue(Object value)
如果此映射将一个或多个键映射到指定的值,则返回true。 更正式地说,当且仅当该映射包含至less一个映射到值
v
使得(value==null ? v==null : value.equals(v))
返回true。 对于大多数Map接口实现,此操作可能需要时间线性的地图大小。参数:value
价值的存在,在这张地图是betested
返回:
如果这个映射将一个或多个键映射到指定的
valueThrows:
ClassCastException – 如果此映射的值的types不合适(可选)
NullPointerException – 如果指定的值为null,并且此映射不允许空值(可选)
它需要它的实现来实现它,但“如何”是在它的自由,只是为了确保它返回正确的。
在HashMap
:
public boolean containsKey(Object key) { return getNode(hash(key), key) != null; }
事实certificate, HashMap
使用散列码来testing这个映射是否包含密钥。 所以它有散列algorithm的好处。