【问题标题】:Java TreeMap: Different object after "put"?Java TreeMap:“放置”后的不同对象?
【发布时间】:2018-07-30 14:25:43
【问题描述】:

我使用派生自 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 比较中没有使用任何信息修改。

在 TreeMap 中添加额外的东西会改变 TreeMap 的 equals() 方法吗?我是否必须以某种方式覆盖equals()hashCode()

【问题讨论】:

    标签: java equals comparator hashcode treemap


    【解决方案1】:

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

    hashCode 的一般约定是:每当在 Java 应用程序执行期间对同一个对象多次调用它时,hashCode 方法必须始终返回相同的整数,如果没有在 equals 比较中使用的信息对象被修改

    每当您在地图中添加(或删除)数据时,您都会更改其 equals 方法中使用的信息 - 空地图不等于其中包含 [1-&gt;a] 的地图,而地图中包含[1-&gt;a] 不等于带有[1-&gt;a; 2-&gt;b] 的地图。

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

    【讨论】:

    • 我确实认为hashCode() 对于一个对象来说保持一定的不变。感谢您的提醒。我最初的问题是,当我在外部LinkedHashMap 中访问它时,对TreeMap 所做的更改不可见。但我设法解决了这个问题。
    【解决方案2】:

    在 TreeMap 中添加额外的东西会改变 TreeMap 的 equals() 方法吗?

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

    比较指定对象与此映射是否相等。如果给定对象也是一个映射并且这两个映射表示相同的映射,则返回 true。更正式地说,如果 m1.entrySet().equals(m2.entrySet()) ,则两个映射 m1 和 m2 表示相同的映射

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

    您期望 hashCode 是对象的某种不可变标识符。它不是。一点也不。

    【讨论】:

      【解决方案3】:

      在 TreeMap 中添加额外的东西会改变 TreeMap 的 equals() 方法吗?我是否必须以某种方式覆盖 equals() 和 hashCode()?

      为什么不呢?新的TreeMap 为空。因此,根据您的推理,如果其equals()hashCode() 方法没有根据TreeMap 的内容进行调整,TreeMap 的所有实例(从空开始)将具有相同的哈希码,因为它将是无论之后添加什么,在创建时计算的相同,其中没有条目。

      大多数集合的equals()hashCode() 函数会根据其内容进行调整。这样,SetList 元素可以与 SetList 元素的另一个实例进行比较,如果它们包含相同的元素,equals() 将返回 true(以相同的顺序,以防万一List)。同样对于Map,内部equals() 实现确保检查任何被认为与Map 实现等效的内容。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-31
        • 2013-06-05
        • 2015-07-21
        • 2021-01-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多