我将使用自己的比较器从TreeMap派生的类用作LinkedHashMap中的键。使用这种结构,我发现一些无法解释的怪异行为。也许你们其中之一可以提供帮助。我试图用原语重现我的问题。当我创建原始类型的TreeMap时,自然排序顺序就足够了,并且在TreeMap的构造函数中不需要比较器,对吧?!
这是MWE:
package treemapputtest;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
public class TreeMapPutTest {
public static void main(String[] args) {
System.out.println("simple:");
simpleTest();
System.out.println("\n\ncomplex:");
complexTest();
}
private static void simpleTest(){
TreeMap<Integer,String> map = new TreeMap<>();
System.out.println("map: " + map.hashCode() + " | " + Integer.toHexString(map.hashCode()));
map.put(1, "a");
System.out.println("map: " + map.hashCode() + " | " + Integer.toHexString(map.hashCode()));
map.put(2, "b");
System.out.println("map: " + map.hashCode() + " | " + Integer.toHexString(map.hashCode()));
}
private static void complexTest(){
TreeMap<Integer,String> internalMap = new TreeMap<>();
internalMap.put(1, "a");
internalMap.put(2, "b");
System.out.println("prior: " + internalMap.hashCode() + " | " + Integer.toHexString(internalMap.hashCode()));
LinkedHashMap<TreeMap<Integer,String>,Double> myMap = new LinkedHashMap<>();
myMap.put(internalMap, 1.0);
doSomethingWithMyInternalMap(myMap.keySet().iterator().next());
System.out.println("after:");
for (Map.Entry<TreeMap<Integer,String>,Double> entry : myMap.entrySet()){
System.out.println(" " + Integer.toHexString(entry.getKey().hashCode()));
}
}
private static void doSomethingWithMyInternalMap(TreeMap<Integer,String> intern){
intern.put(3, "c");
}
}
输出为:
simple:
map: 0 | 0
map: 96 | 60
map: 192 | c0
complex:
prior: 192 | c0
after:
120
所以我的问题是:当我向TreeMap中添加内容时,hashCode()
的结果为什么会改变?仅对于TreeMap来说,这没什么大不了的,但是由于这会创建一个“新对象” /更改了对旧对象的引用,因此在LinkedHashMap中更新TreeMap后,我会出错。
Object API表示hashCode():
hashCode的一般约定是:在Java应用程序执行期间,每次在同一对象上多次调用hashCode时,只要在该对象的equals比较中不使用任何信息,则hashCode方法必须始终返回相同的整数。修改。
在TreeMap中添加其他内容是否会更改TreeMap的equals()方法中的某些内容?我是否必须以某种方式覆盖equals()
和hashCode()
?
答案 0 :(得分:2)
我认为您对hashCode
有误解。让我们在此处引用的文本中强调一点:
hashCode的一般约定是:在Java应用程序执行过程中,每次在同一对象上多次调用hashCode方法时,hashCode方法必须始终返回相同的整数,前提是不提供用于进行等于比较的信息。该对象已修改。
每当在地图中添加(或删除)数据时,都在更改其equals
方法中使用的信息-空地图并不等于其中包含[1->a]
的地图,并且带有[1->a]
的地图不等于带有[1->a; 2->b]
的地图。
这与创建新对象无关,并且对旧地图的引用不发生更改。如果您调用System.identityHashCode(map)
而不是map.hashCode()
,则无论调用多少次put
,对象引用都不会改变。
答案 1 :(得分:1)
在TreeMap中添加其他内容是否会更改TreeMap的equals()方法中的某些内容?
是的,因为地图的contract of equals是
将指定对象与此映射进行比较以确保相等。如果给定对象也是一个映射并且两个映射表示相同的映射,则返回true。更正式地说,如果m1.entrySet()。equals(m2.entrySet()),则两个映射m1和m2表示相同的映射
因此,映射的每个条目都用于检查是否相等,因此还用于计算hashCode。并添加一个条目,从而修改了hashCode。
您期望hashCode是某种对象的不可变标识符。不是。完全没有。
答案 2 :(得分:1)
在TreeMap中添加其他内容是否会更改TreeMap的equals()方法中的某些内容?我必须以某种方式重写equals()和hashCode()吗?
为什么不呢?新的TreeMap
为空。因此,根据您的推理,如果其equals()
和hashCode()
方法未根据TreeMap
的内容进行调整,则TreeMap
的所有实例(从空开始)都将具有相同的哈希码,因为它与创建时计算出来的相同,里面没有任何条目,无论之后添加了什么。
大多数集合的equals()
和hashCode()
函数会根据其内容进行调整。这样,可以将元素Set
或List
与元素Set
或List
的另一个实例进行比较,如果equals()
包含元素,则返回true相同的元素(在List
中以相同的顺序)。同样,对于Map
,内部equals()
实现会确保检查与该Map
实现等效的内容。