392

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

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

13 回答 13

522

对象之间没有区别;在这两种情况下你都有一个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 开头,因为我认为这是编写类时使用的适当结构。

后来,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...
}

后来,我决定实际上,如果我使用TreeMap而不是HashMapin会更好Foo。我更新Foo,更改HashMapTreeMap. 现在,SpecialFoo不再编译了,因为我违反了合同:Foo以前说它提供了HashMaps,但现在它TreeMaps改为提供了。所以我们现在必须修复SpecialFoo(这种事情可能会波及代码库)。

除非我有充分的理由分享我的实现正在使用 a HashMap(并且确实发生了),否则我应该做的是声明getThings并且getMoreThings只是返回Map<String, Object>而没有比这更具体的内容。事实上,除非有充分的理由去做其他事情,即使在Foo我可能应该声明thingsand moreThingsas 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,她也会声明她的私有方法 withMap而不是,HashMap并且我更改Foo的合同不会影响她的代码。

有时你不能这样做,有时你必须具体。但是,除非你有理由这样做,否则会错误地选择最不具体的界面。

于 2009-08-28T16:50:56.367 回答
58

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

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

于 2009-08-28T16:53:20.810 回答
38

在此处输入图像描述

Map 有以下实现:

  1. 哈希映射 Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

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

  4. 弱哈希映射 Map m = new WeakHashMap();

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

public void HashMap getMap(){
   return map;
}

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

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

如果你的方法返回特定的类而不是实现Map接口的东西,你必须getMap()每次改变方法的返回类型。

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

于 2015-02-16T10:06:49.390 回答
18

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

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

正是——而且您总是希望尽可能使用最通用的界面。考虑 ArrayList 与 LinkedList。你如何使用它们有很大的不同,但如果你使用“列表”,你可以很容易地在它们之间切换。

事实上,您可以用更动态的语句替换初始化器的右侧。像这样的东西怎么样:

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

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

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

编辑回复评论:

至于您在下面的地图评论,是的,除非您将集合从 Map 转换回 HashMap(这完全违背了目的),否则使用“Map”界面将您限制为仅使用这些方法。

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

如果您不得不顺便进行转换,则可能是您使用了错误的接口,或者您的代码结构不够好。请注意,让您的代码的一部分将其视为“HashMap”而另一部分将其视为“Map”是可以接受的,但这应该“向下”流动。这样你就永远不会铸造。

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

于 2009-08-28T17:16:13.587 回答
13

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

于 2009-08-28T16:53:00.077 回答
10

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

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

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

于 2009-08-28T17:03:05.410 回答
9

在您的第二个示例中,“map”引用是 type Map,它是由HashMap(和其他类型的Map)实现的接口。该接口是一个约定,表示对象将键映射到值并支持各种操作(例如putget)。它没有说明Map在本例中为 a HashMap)的实现。

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

于 2009-08-28T16:51:00.173 回答
6

除了投票率最高的答案以及上面强调“更通用,更好”的许多答案之外,我还想进一步挖掘。

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

让我们看一下源代码:

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

boolean containsKey(Object key);

Java文档:

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

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

参数:值

值在此地图中的存在将被测试

返回:真

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

价值抛出:

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

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

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

HashMap

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

事实证明,HashMap使用 hashcode 来测试这个 map 是否包含 key。所以它具有哈希算法的好处。

于 2017-09-28T08:00:21.307 回答
4

您创建相同的地图。

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

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

...

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

foo (m1);
foo ((HashMap<String, Object>)m2); 
于 2009-08-28T16:50:22.750 回答
4

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

于 2017-05-09T06:24:26.553 回答
2

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

所以在这个实现中你创建了相同的对象

于 2009-08-28T16:52:09.830 回答
1

HashMap 是 Map 的一个实现,所以它完全一样,但有“clone()”方法,正如我在参考指南中看到的那样))

于 2014-09-23T12:49:02.067 回答
1
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
于 2015-02-09T19:10:08.643 回答