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


348

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

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

假设您使用HashMap实现,而Mary使用Map。会编译吗?
GilbertS

Answers:


445

对象之间没有区别;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-> object内部映射,它与子类共享(通过访问器方法)。假设我以HashMaps开头,因为我认为这是编写类时要使用的适当结构。

后来,玛丽编写了将其子类化的代码。她与thingsand 都需要做一些事情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...
}

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

除非我有一个很好的理由要分享我的实现正在使用HashMap(并且确实发生了),否则我应该做的就是声明getThingsgetMoreThings返回Map<String, Object>而没有比这更具体的内容。实际上,除非有充分的理由做其他事情,甚至在Foo我可能应该将things和声明moreThings为as 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编译停止。

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

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

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


56

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

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


我假设:first = HashMap <String,Object> map = new HashMap <String,Object>();
OneWorld 2010年

它类似于将列表实现为ArrayList的频率
Gerard

26

在此处输入图片说明

地图具有以下实现:

  1. 哈希图 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;
}

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

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

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

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


17

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

嗯,所以区别在于,Map通常具有与之关联的某些方法。但是创建地图的方式不同,例如HashMap,这些不同的方式提供了并非所有地图都具有的独特方法。

的确如此-并且您始终希望使用可能的最通用的界面。考虑ArrayList与LinkedList。使用它们的方式差异很大,但是如果使用“列表”,则可以在它们之间轻松切换。

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

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

这样,如果您要使用插入排序填充集合,则可以使用链表(将插入排序插入数组列表是犯罪的。)但是,如果您不需要保持其排序而只需要追加,您使用ArrayList(对于其他操作更有效)。

这是一个很大的扩展,因为集合不是最好的示例,但是在OO设计中,最重要的概念之一是使用界面外观以完全相同的代码访问不同的对象。

编辑回应评论:

至于下面的地图注释,“是”使用“地图”界面会将您限制为仅那些方法,除非您将集合从地图强制回退到HashMap(这完全违背了目的)。

通常,您要做的是创建对象并使用某种特定类型(HashMap)将其填充,以某种“创建”或“初始化”方法进行,但是该方法将返回不需要不再作为HashMap进行操作。

如果您必须强制转换,则可能是使用了错误的接口,或者您的代码结构不够好。请注意,让您的代码的一部分将其视为“ HashMap”是可以接受的,而另一部分则将其视为“ Map”是可以接受的,但是这应该顺其自然。这样你就永远不会投降。

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


但是在此示例中,我仅从常规List类获取方法,对吗?无论我将其设置为LinkedList()还是ArrayList()?只是如果我使用插入排序(我想它必须是LinkedList和ArrayList通过继承获取List的方法),它在LinkedList上的工作方式会更快吗?
托尼·史塔克

我想我正在寻找的是当我说Map <string,string> m = new HashMap <string,string>()时,我的Map m是否可以使用特定于HashMap的方法。我在想呢?
托尼·史塔克

啊,等等,不,我上面的Map m必须具有HashMap中的方法。
托尼·史塔克

因此,基本上,在“界面意义上”使用Map的唯一好处就是,如果我有一种需要地图的方法,那么我保证任何类型的地图都可以在此方法中使用。但是,如果我使用哈希图,那是说该方法仅适用于哈希图。或者换一种说法,我的方法仅使用Map类中定义的方法,但由扩展Map的其他类继承的方法。
托尼·史塔克

除了上面提到的特权之外,使用List意味着在运行时之前我不需要决定我想要哪种List类型,而如果不存在接口,则必须在编译和运行之前选择一个
Tony Stark

12

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


8

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

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


8

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

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

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


4

除了最受投票支持的答案之外,还有很多人强调“更通用,更好”,我想再多谈一点。

Map是结构合同,而HashMap实现则提供了自己的方法来处理各种实际问题:如何计算索引,容量是多少,如何递增,如何插入,如何保持索引唯一等。

让我们看一下源代码:

Map我们有方法containsKey(Object key)

boolean containsKey(Object key);

JavaDoc:

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

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

参数:值

其在此地图中的存在将被证明的值

返回值:true

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

valueThrows:

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

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

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

HashMap

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

事实证明,HashMap使用哈希码测试此映射是否包含密钥。因此它具有哈希算法的优势。


3

您创建相同的地图。

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

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

...

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

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

3

Map是接口,Hashmap是实现Map Interface的类



0

HashMap是Map的实现,因此完全相同,但是具有“ clone()”方法,如我在参考指南中看到的那样))


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

首先Map是它有不同的实现等的接口- ,,HashMap 等接口就像为实现类超类。因此,根据OOP的规则,实现的任何具体类也是一个。这意味着我们可以将任何类型变量分配/放置到类型变量,而无需任何类型的转换。TreeHashMapLinkedHashMapMapMapHashMapMap

在这种情况下,我们可以分配map1map2而不进行任何强制转换或任何数据丢失-

map2 = map1
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.