Java中的HashMap和Map对象有什么区别?

时间:2009-08-28 16:46:46

标签: java dictionary hashmap

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

HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();

13 个答案:

答案 0 :(得分:390)

对象之间没有区别;在这两种情况下你都有HashMap<String, Object>。您对该对象的接口有所不同。在第一种情况下,界面为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-&gt;对象的内部映射,它与子类共享(通过访问器方法)。假设我用HashMap来编写它,因为我认为这是在编写类时使用的适当结构。

后来,Mary写了代码子类化它。她有thingsmoreThings所需要的东西,所以她自然而然地将其放在一个常用的方法中,并使用我在getThings / {{1}上使用的相同类型定义她的方法时:

getMoreThings

稍后,我决定实际上,如果我在class SpecialFoo extends Foo { private void doSomething(HashMap<String, Object> t) { // ... } public void whatever() { this.doSomething(this.getThings()); this.doSomething(this.getMoreThings()); } // ...more... } 中使用TreeMap而不是HashMap,那会更好。我更新Foo,将Foo更改为HashMap。现在,TreeMap不再编译了,因为我违反了合同:SpecialFoo曾经说它提供了Foo s,但现在却提供了HashMap。所以我们现在必须修复TreeMaps(这种事情会在代码库中产生影响)。

除非我有充分的理由分享我的实施是使用SpecialFoo(并且确实发生了),我应该做的就是声明HashMapgetThings返回getMoreThings而不是更具体。事实上,即使在Map<String, Object>内,我也应该将Foothings声明为moreThings,而不是Map / {{HashMap。 1}}:

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,因为Foo将Mary设置为失败。如果 Mary 已经记住了这一点,那么即使我搞砸了SpecialFoo,她也会用Foo而不是Map声明她的私人方法,并且我会更改{{ 1}}的合同不会影响她的代码。

有时候你不能这样做,有时你必须具体。但是,除非你有理由,否则不要使用最不具体的界面。

答案 1 :(得分:54)

MapHashMap实现的接口。不同之处在于,在第二个实现中,对HashMap的引用只允许使用Map接口中定义的函数,而第一个允许在HashMap中使用任何公共函数(包括Map接口)。

如果你阅读Sun's interface tutorial

,可能会更有意义

答案 2 :(得分:20)

enter image description here

Map具有以下实现:

  1. HashMap Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. 树形地图Map m = new TreeMap();

  4. WeakHashMap Map m = new WeakHashMap();

  5. 假设您创建了一个方法(这只是伪代码)。

    public void HashMap getMap(){
       return map;
    }
    

    假设您的项目要求发生变化:

    1. 该方法应返回地图内容 - 需要返回HashMap
    2. 该方法应按插入顺序返回地图键 - 需要将返回类型HashMap更改为LinkedHashMap
    3. 该方法应按排序顺序返回map键 - 需要将返回类型LinkedHashMap更改为TreeMap
    4. 如果您的方法返回特定的类而不是实现Map接口的类,则每次都必须更改getMap()方法的返回类型。

      但是,如果您使用Java的多态性功能,而不是返回特定的类,请使用接口Map,它可以提高代码的可重用性并减少需求更改的影响。

答案 3 :(得分:17)

我只是将其作为对已接受答案的评论,但它太时髦了(我讨厌没有换行符)

  啊,所以区别在于   一般来说,Map有一定的方法   与之相关联。但是这里有   不同的方式或创建地图,如此   作为HashMap,以及这些不同的方式   提供并非全部的独特方法   地图有。

确切地说 - 您总是希望使用最常用的界面。考虑ArrayList vs LinkedList。你如何使用它们有很大的不同,但如果你使用“List”,你可以很容易地在它们之间切换。

实际上,您可以使用更动态的语句替换初始化程序的右侧。这样的事情怎么样:

List collection;
if(keepSorted)
    collection=new LinkedList();
else
    collection=new ArrayList();

这样一来,如果您要使用插入排序填充集合,您将使用链接列表(对数组列表的插入排序是犯罪行为。)但是如果您不需要对其进行排序并且是只需附加,就可以使用ArrayList(对其他操作更有效)。

这是一个相当大的延伸,因为集合不是最好的例子,但在OO设计中,最重要的概念之一是使用接口Facade以完全相同的代码访问不同的对象。

编辑回复评论:

至于下面的地图评论,是的,使用“地图”界面会限制您只使用那些方法,除非您将集合从地图强制转换为HashMap(完全违背了目的)。

通常你要做的是创建一个对象并使用它的特定类型(HashMap)填充它,在某种“创建”或“初始化”方法中,但该方法将返回一个不具有的“地图”需要再被操作为HashMap。

如果你不得不顺便使用,你可能使用了错误的界面,或者你的代码结构不够好。请注意,可以让代码的一部分将其视为“HashMap”,而另一部分将其视为“地图”,但这应该“向下”流动。所以你永远不会施展。

还要注意接口指示的角色的半整洁方面。 LinkedList是一个很好的堆栈或队列,一个ArrayList是一个很好的堆栈,但是一个可怕的队列(同样,一个删除会导致整个列表的移位)所以LinkedList实现了Queue接口,而ArrayList没有。

答案 4 :(得分:12)

如TJ Crowder和Adamski所述,一个参考是接口,另一个参考接口的特定实现。根据Joshua Block的说法,您应该总是尝试对接口进行编码,以便更好地处理对底层实现的更改 - 即如果HashMap突然不适合您的解决方案并且您需要更改地图实现,您仍然可以使用Map接口,并更改实例化类型。

答案 5 :(得分:8)

在第二个示例中,“map”引用的类型为Map,这是HashMap(以及其他类型的Map)实现的接口。此界面是合同,表示对象将键映射到值并支持各种操作(例如putget)。它对Map(在这种情况下为HashMap)的实施没有任何暗示。

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

答案 6 :(得分:8)

Map是地图的静态类型,而HashMap是地图的动态类型。这意味着编译器会将您的地图对象视为Map类型之一,即使在运行时,它也可能指向它的任何子类型。

这种针对接口而不是实现的编程实践具有保持灵活性的额外好处:例如,您可以在运行时替换动态类型的map,只要它是Map的子类型(例如LinkedHashMap),并更改地图的动态行为。

一个好的经验法则是在API级别上保持尽可能抽象:例如,如果您编程的方法必须在地图上工作,那么将参数声明为Map而不是更严格就足够了(因为不太抽象) )HashMap类型。这样,您的API的使用者可以灵活地了解他们希望将哪种Map实现传递给您的方法。

答案 7 :(得分:3)

您可以创建相同的地图。

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

public void foo (HashMap<String, Object) { ... }

...

HashMap<String, Object> m1 = ...;
Map<String, Object> m2 = ...;

foo (m1);
foo ((HashMap<String, Object>)m2); 

答案 8 :(得分:2)

Map是接口,Hashmap是实现Map Interface

的类

答案 9 :(得分:2)

除了强调“更通用,更好”之外,还有很多投票答案和许多以上的答案,我想多挖一点。

Map是结构契约,而HashMap是一个实现,提供自己的方法来处理不同的实际问题:如何计算索引,容量是什么以及如何增加它,如何插入,如何保持索引的独特性等。

让我们看一下源代码:

Map我们有containsKey(Object key)的方法:

boolean containsKey(Object key);

的JavaDoc:

  

boolean java.util.Map.containsValue(Object value)

     

如果此映射将一个或多个键映射到指定值,则返回true。更正式地说,当且仅当此地图包含至少一个映射到值v的映射(value==null ? v==null : value.equals(v))时才返回true。对于Map接口的大多数实现,此操作可能需要地图大小的线性时间。

     

参数:值

     

在此地图中存在的值

     

返回:true

     

如果此地图将一个或多个键映射到指定的

     

valueThrows:

     

ClassCastException - 如果该地图的值类型不合适(可选)

     

NullPointerException - 如果指定的值为null并且此映射不允许空值(可选)

它需要它的实现来实现它,但“如何”是自由的,只是为了确保它返回正确。

HashMap

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

事实证明,HashMap使用哈希码来测试此映射是否包含密钥。所以它有哈希算法的好处。

答案 10 :(得分:1)

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

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

答案 11 :(得分:0)

HashMap是Map的一个实现,所以它完全相同,但有参考指南中的“clone()”方法))

答案 12 :(得分:0)

HashMap<String, Object> map1 = new HashMap<String, Object>();
Map<String, Object> map2 = new HashMap<String, Object>();  

首先,Map是一个接口,它具有不同的实现,如 - HashMapTreeHashMapLinkedHashMap等。接口的工作方式类似于实现类的超类。因此,根据OOP的规则,任何实现Map的具体类也是Map。这意味着我们可以将任何HashMap类型变量分配/放入Map类型变量,而无需任何类型的转换。

在这种情况下,我们可以将map1分配给map2,而不会进行任何投射或丢失任何数据 -

map2 = map1