我所创建的以下地图之间的区别是什么(在另一个问题中,人们似乎可以互换地使用它们,我想知道它们是否/如何不同):
HashMap<String, Object> map = new HashMap<String, Object>(); Map<String, Object> map = new HashMap<String, Object>();
你创建了相同的地图。
但是当你使用它的时候,你可以弥补这个差异。对于第一种情况,你将能够使用特殊的HashMap方法(但我不记得有什么真正有用的方法),并且你将能够将它作为HashMap参数传递:
public void foo (HashMap<String, Object) { ... } ... HashMap<String, Object> m1 = ...; Map<String, Object> m2 = ...; foo (m1); foo ((HashMap<String, Object>)m2);
对象之间没有区别;在这两种情况下都有HashMap<String, Object>。接口和对象是有区别的。在第一种情况下,接口是HashMap<String, Object>,而在第二种情况下,接口是Map<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->对象的内部映射,它与子类共享(通过访问方法)。假设我以HashMaps开始编写它,因为我认为这是编写类时使用的适当结构。
HashMap
后来,Mary编写代码继承它。她对things和moreThings都有一些需要做的事情,所以她自然地把它放在一个公共方法中,并且在定义她的方法时,她使用了我在getThings/getMoreThings上使用的相同类型:
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曾经说它提供了__abc1,但现在它提供了TreeMaps。所以我们现在必须修复SpecialFoo(这类事情会波及整个代码库)。
Foo
TreeMap
SpecialFoo
TreeMaps
除非我有一个非常好的理由来分享我的实现正在使用HashMap(这种情况确实发生了),否则我应该做的是将getThings和getMoreThings声明为只返回Map<String, Object>,而不需要更具体。事实上,除非有很好的理由做其他事情,即使在Foo中,我也可能应该将things和moreThings声明为Map,而不是HashMap/TreeMap:
Map
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在SpecialFoo中失败。如果玛丽记得这一点,那么即使我搞砸了Foo,她也会用Map而不是HashMap声明她的私有方法,而我改变Foo的契约也不会影响她的代码。
有时你不能这样做,有时你必须具体一点。但除非你有理由这么做,否则最好选择最不特定的界面。
在第二个例子中,“map”引用的类型是Map,这是一个由HashMap(和其他类型的Map)实现的接口。这个接口是一个合同,表示对象将键映射到值,并支持各种操作(例如put, get)。它说Map的与实现无关(在这种情况下是HashMap)。
put
get
第二种方法通常是首选的,因为你通常不希望将特定的映射实现暴露给使用Map或通过API定义的方法。
Map是接口,Hashmap是实现接口的类。
在这个实现中,你创建了相同的对象
正如TJ Crowder和Adamski所指出的,一个引用指向一个接口,另一个引用指向接口的特定实现。根据Joshua Block的说法,你应该总是尝试对接口进行编码,以允许你更好地处理底层实现的变化——也就是说,如果HashMap突然不适合你的解决方案,你需要改变映射实现,你仍然可以使用map接口,并改变实例化类型。
地图是由HashMap实现的接口。不同之处在于,在第二个实现中,对HashMap的引用将只允许使用Map接口中定义的函数,而第一个实现将允许使用HashMap中的任何公共函数(包括Map接口)。
如果你读过Sun的界面教程,这可能会更有意义
Map是Map的静态类型,而HashMap是Map的动态类型。这意味着编译器将把您的map对象视为map类型之一,即使在运行时,它可能指向它的任何子类型。
这种针对接口而不是实现进行编程的实践具有保持灵活性的额外好处:例如,您可以在运行时替换映射的动态类型,只要它是map的子类型(例如LinkedHashMap),并动态地更改映射的行为。
一个好的经验法则是在API级别上尽可能保持抽象:例如,如果您正在编程的方法必须在Map上工作,那么将参数声明为Map而不是更严格的(因为不太抽象)HashMap类型就足够了。这样,API的使用者就可以灵活地决定他们想要传递给方法的Map实现的类型。
我只是想把它作为对已接受答案的评论,但它太时髦了(我讨厌没有换行符)
啊,所以差值是in 一般来说,Map有一定的方法 与之相关。但是有 不同的方式或创建地图,如 作为HashMap,以及这些不同的方式 提供唯一的方法 地图。< / p >
正是如此——您总是希望尽可能使用最通用的接口。考虑一下数组列表和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没有。
HashMap是Map的一个实现,所以它是完全相同的,但有“clone()”方法,因为我在参考指南中看到))
HashMap<String, Object> map1 = new HashMap<String, Object>(); Map<String, Object> map2 = new HashMap<String, Object>();
首先,Map是一个接口,它有不同的实现,如- HashMap, TreeHashMap, LinkedHashMap等。接口的工作方式类似于实现类的超类。因此,根据OOP的规则,任何实现Map的具体类也是Map。这意味着我们可以将任何HashMap类型变量赋值/放置给Map类型变量,而不需要任何类型的强制转换。
TreeHashMap
LinkedHashMap
在这种情况下,我们可以将map1赋值给map2,而不需要任何强制转换或丢失数据-
map1
map2
map2 = map1
Map有以下实现:
HashMap Map m = new HashMap();
Map m = new HashMap();
LinkedHashMap Map m = new LinkedHashMap();
Map m = new LinkedHashMap();
树映射Map m = new TreeMap();
Map m = new TreeMap();
WeakHashMap Map m = new WeakHashMap();
Map m = new WeakHashMap();
假设您已经创建了一个方法(这只是伪代码)。
public void HashMap getMap(){ return map; }
假设你的项目需求改变了:
如果你的方法返回特定的类,而不是实现Map接口的东西,你必须每次都改变getMap()方法的返回类型。
getMap()
但是如果你使用Java的多态性特性,而不是返回特定的类,而是使用接口Map,它可以提高代码的可重用性并减少需求更改的影响。
Map是接口,Hashmap是实现Map interface的类
加上投票最多的答案和上面许多强调“更通用,更好”的答案,我想再挖掘一点。
Map是结构契约,而HashMap是一个实现,它提供了自己的方法来处理不同的实际问题:如何计算索引,容量是什么以及如何增加它,如何插入,如何保持索引惟一,等等。
让我们看看源代码:
在Map中,我们有containsKey(Object key)方法:
containsKey(Object key)
boolean containsKey(Object key);
JavaDoc:
布尔java.util.Map。containsValue(对象值) 如果此映射将一个或多个键映射到指定值,则返回true。更正式地说,当且仅当该映射包含至少一个到值v的映射,从而(value==null ? v==null : value.equals(v)). 0返回true。对于map接口的大多数实现来说,该操作可能需要线性映射的时间。 参数:价值 在这张地图中存在的价值有待检验 返回:真 如果此映射将一个或多个键映射到指定的 valueThrows: ClassCastException -如果值的类型不适合此映射(可选) NullPointerException -如果指定的值为空,并且此映射不允许空值(可选)
布尔java.util.Map。containsValue(对象值)
如果此映射将一个或多个键映射到指定值,则返回true。更正式地说,当且仅当该映射包含至少一个到值v的映射,从而(value==null ? v==null : value.equals(v)). 0返回true。对于map接口的大多数实现来说,该操作可能需要线性映射的时间。
v
(value==null ? v==null : value.equals(v))
参数:价值
在这张地图中存在的价值有待检验
返回:真
如果此映射将一个或多个键映射到指定的
valueThrows:
ClassCastException -如果值的类型不适合此映射(可选)
NullPointerException -如果指定的值为空,并且此映射不允许空值(可选)
它需要它的实现来实现它,但“如何”是自由的,只是为了确保它返回正确的结果。
在HashMap:
public boolean containsKey(Object key) { return getNode(hash(key), key) != null; }
结果是HashMap使用hashcode来测试这个映射是否包含键。所以它有哈希算法的优点。