Java TreeMap:"put" 之后的不同对象?

Java TreeMap: Different object after "put"?

我使用从 TreeMap 派生的 class 和我自己的比较器作为 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 这不是什么大问题,但是由于这会创建一个 "new object"/对旧对象的引用已更改,我在更新 LinkedHashMap 中的 TreeMap 后出现错误。

Object API 表示 hashCode():

The general contract of hashCode is: Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified.

在 TreeMap 中添加其他内容是否会改变 TreeMap 的 equals() 方法?我是否必须以某种方式覆盖 equals()hashCode()

我想你对这里的hashCode有误解。让我们强调一下您在此处引用的文字中的观点:

The general contract of hashCode is: Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified.

无论何时在地图中添加(或删除)数据,您都在更改其 equals 方法中使用的信息 - 空地图不等于具有 [1->a] 的地图它,并且具有 [1->a] 的地图不等于具有 [1->a; 2->b] 的地图。

这与创建新对象无关,对旧地图的引用不会改变。如果您调用 System.identityHashCode(map) instead of map.hashCode(),您将看到无论您调用多少次 put,对象引用都不会改变。

Does putting additional stuff in the TreeMap change something in the equals() method of TreeMap?

是的,当然,因为地图的 contract of equals

Compares the specified object with this map for equality. Returns true if the given object is also a map and the two maps represent the same mappings. More formally, two maps m1 and m2 represent the same mappings if m1.entrySet().equals(m2.entrySet())

因此,映射的每个条目都用于检查是否相等,因此也用于计算 hashCode。并添加一个条目从而修改了hashCode。

您期望 hashCode 是某种对象的不可变标识符。不是。完全没有。

Does putting additional stuff in the TreeMap change something in the equals() method of TreeMap? Do I have to somehow override equals() and hashCode()?

为什么不呢?一个新的 TreeMap 是空的。所以按照你的推理,如果它的 equals()hashCode() 方法没有根据 TreeMap 的内容进行调整,TreeMap 的所有实例(从空开始)都会有相同的哈希码,因为它与创建时计算的相同,其中没有任何条目,无论之后添加了什么。

大多数 Collections 的 equals()hashCode() 功能根据其内容进行调整。这样 SetList 元素可以与 SetList 元素的另一个实例进行比较,并且 equals() 将 return如果它们包含相同的元素(在 List 的情况下以相同的顺序),则为真。类似地,对于 Map,内部 equals() 实现确保检查与该 Map 实现等效的任何内容。