【问题标题】:How to calculate hashCode of this class [duplicate]如何计算此类的hashCode [重复]
【发布时间】:2019-04-16 11:16:32
【问题描述】:

我正在尝试计算一个类的哈希码,但我得到了 stackoverflow。我怎样才能正确地做到这一点?我通过 IntelliJ 的想法生成了它,但仍然如此。有stackoverflow,我知道原因(可能)但我真的想计算正确的哈希码..

public class Main {
    public static void main(String[] args) {
        TestA testA = new TestA();
        TestB testB = new TestB();
        testA.id = 1;
        testA.name = "test";
        testA.testB = testB;
        testB.testA = testA;
        testB.id = 1;
        testB.name = "test";



        System.out.println(testA.hashCode());
    }
}

class TestB {
    int id;
    String name;
    TestA testA;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof TestB)) return false;

        TestB testB = (TestB) o;

        if (id != testB.id) return false;
        if (name != null ? !name.equals(testB.name) : testB.name != null) return false;
        return testA != null ? testA.equals(testB.testA) : testB.testA == null;
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + (testA != null ? testA.hashCode() : 0);
        return result;
    }
}


class TestA {
    int id;
    String name;
    TestB testB;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof TestA)) return false;

        TestA testA = (TestA) o;

        if (id != testA.id) return false;
        if (name != null ? !name.equals(testA.name) : testA.name != null) return false;
        return testB != null ? testB.equals(testA.testB) : testA.testB == null;
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + (testB != null ? testB.hashCode() : 0);
        return result;
    }
}

我也包含了 main 函数。你可以轻松打开这个..

【问题讨论】:

  • 不知道你有没有注意到,但是你已经有一个hashcode实现了,那么有什么问题
  • 尝试运行这个 - 我得到了 stackoverflow
  • 这与您的哈希码无关。您在 testA 和 testB 之间存在循环依赖关系
  • @Stultuske 我不确定这应该如何回答这个问题或帮助他,除非你试图暗示循环引用总是错误的(事实并非如此)。跨度>
  • 不,他们确实不是。但是调用哈希码与调用循环引用导致stackoverflow错误的机会..

标签: java hashcode


【解决方案1】:

您正在寻找的是一种在不进入无限循环的情况下遍历对象树的方法。这可以通过将访问的对象存储在 thread-local Set 中并在进入 hashcodethis 在该集合中时停止来实现。

而且您不能随便使用HashSet 来存储“已访问”对象,因为它在内部调用了您的hashcode,因此问题只是转移到了其他地方,您仍然会遇到堆栈溢出。幸运的是,有一个容器使用标识而不是相等,但它是 Map 变体,而不是 Set。理想情况下,您想要IdentityHashSet,但它不存在,但仍然有用的IdentityHashMap 存在。只需将键用作实际内容并使用虚拟值即可。

public class Main {
    public static void main(String[] args) {
        TestA testA = new TestA();
        TestB testB = new TestB();
        testA.id = 1;
        testA.name = "test";
        testA.testB = testB;
        testB.testA = testA;
        testB.id = 1;
        testB.name = "test";

        System.out.println(testA.hashCode());
    }
}

class TestB {
    int    id;
    String name;
    TestA  testA;

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (!(o instanceof TestB))
            return false;

        TestB testB = (TestB)o;

        if (id != testB.id)
            return false;
        if (name != null ? !name.equals(testB.name) : testB.name != null)
            return false;
        return testA != null ? testA.equals(testB.testA) : testB.testA == null;
    }

    private static final ThreadLocal<Set<Object>> VISITED = ThreadLocal.withInitial(() -> new HashSet(10));

    @Override
    public int hashCode() {
        Set<Object> visited = VISITED.get();
        if (visited.contains(this))
            return 0;

        visited.add(this);
        try {
            int result = id;
            result = 31 * result + (name != null ? name.hashCode() : 0);
            result = 31 * result + (testA != null ? testA.hashCode() : 0);
            return result;
        } finally {
            visited.remove(this);
        }
    }
}

class TestA {
    int    id;
    String name;
    TestB  testB;

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (!(o instanceof TestA))
            return false;

        TestA testA = (TestA)o;

        if (id != testA.id)
            return false;
        if (name != null ? !name.equals(testA.name) : testA.name != null)
            return false;
        return testB != null ? testB.equals(testA.testB) : testA.testB == null;
    }

    private static final ThreadLocal<Map<Object, Object>> VISITED =
            ThreadLocal.withInitial(() -> new IdentityHashMap<>(10));

    @Override
    public int hashCode() {
        Map<Object, Object> visited = VISITED.get();
        if (visited.containsKey(this))
            return 0;

        visited.put(this, this);
        try {
            int result = id;
            result = 31 * result + (name != null ? name.hashCode() : 0);
            result = 31 * result + (testB != null ? testB.hashCode() : 0);
            return result;
        } finally {
            visited.remove(this);
        }
    }
}

注意:两个 VISITED 变量可以是一个变量,但由于您的类没有公共超类(Object 除外),因此我必须制作其中两个。

警告:当树包含多次相同的类实例时,该实例的哈希码将被计算多次。这是因为每次该实例完成访问时,它都会从列表中删除。这是因为您不希望对这些实例的硬引用保留在线程本地 Map 中,从而阻止垃圾回收。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-12
    • 1970-01-01
    • 2020-01-26
    相关资源
    最近更新 更多