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

Ton*_*ark 333 java dictionary hashmap

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

HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();
Run Code Online (Sandbox Code Playgroud)

T.J*_*der 409

对象之间没有区别; 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...
}
Run Code Online (Sandbox Code Playgroud)

该类有几个string-> object的内部映射,它与子类共享(通过访问器方法).假设我先用HashMaps 编写它,因为我认为这是在编写类时使用的合适结构.

后来,Mary写了代码子类化它.她有什么她需要做既thingsmoreThings,所以自然她把在一个常用的方法,她会用我所用的同类型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...
}
Run Code Online (Sandbox Code Playgroud)

后来,我决定实际上,如果我使用TreeMap而不是HashMapin ,那就更好了Foo.我更新Foo,HashMap改为TreeMap.现在,SpecialFoo不再编译了,因为我已经破坏了合同:Foo曾经说它提供了HashMaps,但现在却提供了TreeMaps.所以我们现在必须解决SpecialFoo(这种事情可能会破坏代码库).

除非我有一个非常好的理由分享我的实现正在使用a HashMap(并且确实发生了),我应该做的是声明getThings并且getMoreThings只是返回Map<String, Object>而不是更具体而不是那样.事实上,除非有很好的理由做其他的事情,即使是在Foo我也许应该申报things,并moreThings作为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...
}
Run Code Online (Sandbox Code Playgroud)

请注意我现在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());
    }
}
Run Code Online (Sandbox Code Playgroud)

......并且改变Foo不会SpecialFoo停止编译.

接口(和基类)让我们尽可能多地展示,保持我们的灵活性,以便适当地进行更改.一般来说,我们希望我们的参考资料尽可能基本.如果我们不需要知道它是a HashMap,只需称之为Map.

这不是一个盲目的规则,但一般来说,编码到最通用的界面将比编写更具体的东西更不脆弱.如果我记得那个,我就不会创建一个Foo让玛丽失败的东西SpecialFoo.如果玛丽记得这一点,那么即使我搞砸了Foo,她也会宣布她的私人方法,Map而不是HashMap改变我Foo的合同不会影响她的代码.

有时你不能这样做,有时你必须具体.但除非你有理由这样做,否则最不具体的界面.

  • @CollinFox - 你只有一种菜刀吗?:-) 拥有一个接口和多个提供不同功能的实现是很正常的。对比一下[`HashMap`](https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/util/HashMap.html)、[`TreeMap`]的描述(https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/util/TreeMap.html)和[`LinkedHashMap`](https://docs.oracle .com/en/java/javase/13/docs/api/java.base/java/util/LinkedHashMap.html)。您可以看到它们针对差异情况提供了不同类型的运行时性能、排序保证等。 (4认同)
  • 为什么我们不只用相同的方式创建一种具有所有可用功能的单一类型地图?这难道不比为了最小的利益而过度复杂化对象更有意义吗? (2认同)
  • 像你提到的这三个问题正是我希望这篇堆栈溢出帖子能够简单提出的问题 - 这些问题的答案是什么? (2认同)

Gra*_*oob 54

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

如果您阅读Sun的界面教程,可能会更有意义


ati*_*mpi 23

在此输入图像描述

Map具有以下实现:

  1. HashMap中 Map m = new HashMap();

  2. LinkedHashMap的 Map m = new LinkedHashMap();

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

  4. WeakHashMap中 Map m = new WeakHashMap();

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

public void HashMap getMap(){
   return map;
}
Run Code Online (Sandbox Code Playgroud)

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

  1. 该方法应返回地图内容 - 需要返回HashMap.
  2. 该方法应按插入顺序返回map键 - 需要将返回类型更改HashMapLinkedHashMap.
  3. 该方法应按排序顺序返回map键 - 需要将返回类型更改LinkedHashMapTreeMap.

如果您的方法返回特定的类而不是实现Map接口的东西,则getMap()每次都必须更改方法的返回类型.

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


Bil*_*l K 17

我只是这样做是对已接受答案的评论,但它太时髦了(我讨厌没有换行)

啊,所以不同的是,一般来说,Map有一些与之相关的方法.但是有不同的方法或创建地图,例如HashMap,这些不同的方法提供了并非所有地图都具有的独特方法.

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

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

List collection;
if(keepSorted)
    collection=new LinkedList();
else
    collection=new ArrayList();
Run Code Online (Sandbox Code Playgroud)

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

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

编辑回复评论:

至于下面的地图注释,是的,使用"地图"界面仅限制您使用这些方法,除非您将集合从地图强制转换为HashMap(完全违背了目的).

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

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

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


ape*_*ins 12

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


Ada*_*ski 8

在第二个示例中,"map"引用是类型Map,它是由HashMap(和其他类型Map)实现的接口.该接口是一个合同说,对象键映射到值,并支持各种操作(例如put,get).它说,任何关于实施Map(在这种情况下HashMap).

第二种方法通常是首选方法,因为您通常不希望将特定的地图实现暴露给使用MapAPI定义或通过API定义的方法.


Mat*_*ias 8

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

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

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


Wes*_*Gun 5

添加到最高投票的答案和上面的许多强调“更通用,更好”的答案,我想多挖一点。

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

让我们看一下源代码:

Map我们有以下方法containsKey(Object key)

boolean containsKey(Object key);
Run Code Online (Sandbox Code Playgroud)

Java文档:

boolean java.util.Map.containsValue(对象值)

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

参数:值

要测试其在此地图中的存在的值

返回:真

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

值抛出:

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

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

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

HashMap

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}
Run Code Online (Sandbox Code Playgroud)

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