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

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

当前回答

对象之间没有区别;你有一个HashMap<String,对象>在这两种情况下。你和对象之间的界面是有区别的。在第一种情况下,接口是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->对象的内部映射,它与子类共享(通过访问方法)。假设我一开始是用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...
}

后来,我决定,实际上,如果我在Foo中使用TreeMap而不是HashMap会更好。我更新Foo,将HashMap更改为TreeMap。现在,SpecialFoo不再编译了,因为我已经打破了契约:Foo曾经说它提供HashMaps,但现在它提供TreeMaps。所以我们现在必须修复SpecialFoo(这类事情可能会波及整个代码库)。

除非我有一个很好的理由来分享我的实现是使用HashMap(这确实发生了),我应该做的是声明getThings和getMoreThings只是返回Map<String,对象>而不是任何更具体的。事实上,除非有一个很好的理由去做别的事情,即使在Foo中,我也应该将things和更多的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>在我可以的任何地方,只有在我创建实际对象时才具体。

如果我那样做了,玛丽就会这样做:

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。

这不是盲目的规则,但一般来说,为最通用的接口编码要比为更具体的接口编码更容易。如果我记得这一点,我就不会创建一个让Mary使用SpecialFoo失败的Foo。如果Mary记得这一点,那么即使我搞砸了Foo,她也会用Map而不是HashMap声明她的私有方法,而我改变Foo的契约也不会影响她的代码。

有时你不能这样做,有时你必须具体一点。但除非你有理由这么做,否则最好选择最不特定的界面。

其他回答

对象之间没有区别;你有一个HashMap<String,对象>在这两种情况下。你和对象之间的界面是有区别的。在第一种情况下,接口是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->对象的内部映射,它与子类共享(通过访问方法)。假设我一开始是用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...
}

后来,我决定,实际上,如果我在Foo中使用TreeMap而不是HashMap会更好。我更新Foo,将HashMap更改为TreeMap。现在,SpecialFoo不再编译了,因为我已经打破了契约:Foo曾经说它提供HashMaps,但现在它提供TreeMaps。所以我们现在必须修复SpecialFoo(这类事情可能会波及整个代码库)。

除非我有一个很好的理由来分享我的实现是使用HashMap(这确实发生了),我应该做的是声明getThings和getMoreThings只是返回Map<String,对象>而不是任何更具体的。事实上,除非有一个很好的理由去做别的事情,即使在Foo中,我也应该将things和更多的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>在我可以的任何地方,只有在我创建实际对象时才具体。

如果我那样做了,玛丽就会这样做:

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。

这不是盲目的规则,但一般来说,为最通用的接口编码要比为更具体的接口编码更容易。如果我记得这一点,我就不会创建一个让Mary使用SpecialFoo失败的Foo。如果Mary记得这一点,那么即使我搞砸了Foo,她也会用Map而不是HashMap声明她的私有方法,而我改变Foo的契约也不会影响她的代码。

有时你不能这样做,有时你必须具体一点。但除非你有理由这么做,否则最好选择最不特定的界面。

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

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

正是如此——您总是希望尽可能使用最通用的接口。考虑一下数组列表和LinkedList。在你使用它们的方式上有巨大的差异,但如果你使用“列表”,你可以很容易地在它们之间切换。

实际上,可以用更动态的语句替换初始化式的右边。这样怎么样:

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

这样,如果您要用插入排序填充集合,您将使用链表(插入排序到数组列表是违法的)。但如果你不需要保持它的排序,只是追加,你使用ArrayList(更有效的其他操作)。

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

编辑回应评论:

对于下面的map注释,是的,使用“map”接口限制你只能使用这些方法,除非你将集合从map转换回HashMap(这完全违背了目的)。

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

如果你不得不强制转换,你可能使用了错误的接口,或者你的代码结构不够好。请注意,代码的一部分将其作为“HashMap”,而另一部分将其作为“Map”,这是可以接受的,但这应该是“向下”的。这样你就不会选角了。

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

在第二个示例中,“map”引用的类型是map,它是由HashMap(和其他类型的map)实现的接口。这个接口是一个契约,表示对象将键映射到值,并支持各种操作(例如put, get)。它没有提到Map(在本例中是HashMap)的实现。

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

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

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

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