简体   繁体   English

为什么 super.hashCode 对来自同一个 Class 的对象给出不同的结果?

[英]Why does super.hashCode give different results on objects from the same Class?

I have a class DebugTo where if I have two equal instances el1 , el2 a HashSet of el1 will not regard el2 as contained.我有一个 class DebugTo ,如果我有两个相等的实例el1el2 ,则el1的 HashSet 不会将el2视为已包含。

import java.util.Objects;

public class DebugTo {

  public String foo;

  public DebugTo(String foo) {
    this.foo = foo;
  }

  @Override
  public int hashCode() {
    System.out.println(super.hashCode());
    return Objects.hash(super.hashCode(), foo);
  }

  @Override
  public boolean equals(Object o) {

    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    DebugTo that = (DebugTo) o;
    return Objects.equals(foo, that.foo);
  }

}
var el1 = new DebugTo("a");
var el2 = new DebugTo("a");
System.out.println("Objects.equals(el1, el2): " + Objects.equals(el1, el2));
System.out.println("Objects.equals(el2, el1): " + Objects.equals(el2, el1));
System.out.println("el1.hashCode(): " + el1.hashCode());
System.out.println("el2.hashCode(): " + el2.hashCode());
Objects.equals(el1, el2): true
Objects.equals(el2, el1): true
1205483858
el1.hashCode(): -1284705008
1373949107
el2.hashCode(): -357249585

From my analysis I have gathered that:根据我的分析,我收集到:

  • HashSet::contains calls hashCode not equals (relying on the Objects.equals(a, b) => a.hashSet() == b.hashSet() ) HashSet::contains调用hashCodeequals (依赖于Objects.equals(a, b) => a.hashSet() == b.hashSet()
  • super.hashCode() gives a different value both times. super.hashCode()给出不同的值。

Why does super.hashCode() give different results for el1 and el2 ?为什么super.hashCode()el1el2给出不同的结果? since they are of the same class, they have the same super class and so I expect super.hashCode() to give the same result for both.因为它们是相同的 class,所以它们具有相同的超级 class,所以我希望super.hashCode()对两者给出相同的结果。

The hashCode method was probably autogenerated by eclipse. If not answered above, why is super.hashCode used wrong here? hashCode方法应该是eclipse自动生成的。如果上面没有回答,为什么这里super.hashCode用错了?

  1. Because the default implementations of the equals and hashCode methods (which go hand in hand - you always override both or neither) treat any 2 different instances as not equal to each other.因为equalshashCode方法的默认实现(其中 go 手牵手 - 你总是覆盖两者或两者都不覆盖)将任何 2 个不同的实例视为彼此不相等。 If you want different behaviour, you override equals and hashCode, and do not invoke super.equals / super.hashCode , or there'd be no point.如果您想要不同的行为,您可以覆盖 equals 和 hashCode,并且不要调用super.equals / super.hashCode ,否则就没有意义了。

  2. HashSets work as follows: They use .hashCode() to know which 'bucket' to put the object into, and if 2 objects end up in the same bucket, equals is used only on those very few objects to double check. HashSets 的工作方式如下:它们使用.hashCode()来知道将 object 放入哪个“桶”,如果 2 个对象最终出现在同一个桶中,则仅对极少数对象使用equals进行双重检查。

In other words, these are the rules:换句话说,这些是规则:

  1. If a.equals(b) , then b.equals(a) must be true.如果a.equals(b) ,则b.equals(a)必须为真。
  2. a.equals(a) must always be true. a.equals(a)必须始终为真。
  3. If a.equals(b) and b.equals(c) , a.equals(c) must be true.如果a.equals(b)b.equals(c)a.equals(c)必须为真。
  4. If a.equals(b) , a.hashCode() == b.hashCode() must be true.如果a.equals(b)a.hashCode() == b.hashCode()必须为真。
  5. The reverse of 4 does not hold: If a.hashCode() == b.hashCode() , that doesn't mean a.equals(b) , and hashset does not require it. 4 的反转成立:如果a.hashCode() == b.hashCode() ,那并不意味着a.equals(b) ,并且 hashset 不需要它。
  6. Therefore, return 1;因此, return 1; is a legal implementation of hashCode.是 hashCode 的合法实现。
  7. If a class has really bad hashcode spread (such as the idiotic but legal option listed in bullet 6), then the performance of hashset will be very bad.如果 class 的 hashcode 传播非常糟糕(例如第 6 条中列出的愚蠢但合法的选项),那么 hashset 的性能将非常糟糕。 eg set.containsKey(k) which ordinarily takes constant time, will take linear time instead if your objects are all not-equal but have the same hashCode.例如,通常需要常数时间的set.containsKey(k)如果您的对象不相等具有相同的哈希码,则将花费线性时间。 Hence, do try to ensure hashcodes are as different as they can be.因此,请尽量确保哈希码尽可能不同。
  8. HashSet and HashMap require stable objects, meaning, their behaviour when calling hashCode and equals cannot change over time. HashSet 和 HashMap 需要稳定的对象,这意味着它们在调用 hashCode 和 equals 时的行为不会随时间改变。
  9. From the above it naturally follows that overriding equals and not hashCode or vice versa is necessarily broken.从上面可以自然地得出,重写 equals 而不是 hashCode 或反之亦然必然会被破坏。

Breaking any of the above rules does not, generally, result in a compiler error.违反上述任何规则通常不会导致编译器错误。 It often doesn't even result in an exception.它通常甚至不会导致异常。 But instead it results in bizarre behaviour with hashsets and hashmaps: You put an k/v pair in the map, and then immediately ask for the value back and you get null back instead of what you put in, or something completely different.但相反,它会导致散列集和散列映射出现奇怪的行为:你在 map 中放入一个 k/v 对,然后立即请求返回值,你得到null而不是你放入的值,或者完全不同的东西。 Just an example.只是一个例子。


NB: One weird effect of all this is that you cannot add equality-affecting state to subclasses , unless you apply a caveat that most classes including all classes in the core libraries don't apply.注意:所有这一切的一个奇怪效果是您不能将影响相等的 state 添加到子类,除非您应用一个警告,即大多数类(包括核心库中的所有类)都不适用。

Imagine as an example that we invent the notion of a 'coloured' arraylist. You could have a red '["Hello", "World"]' list, and a blue one:举个例子,我们发明了“彩色”arraylist 的概念。您可以有一个红色的“["Hello", "World"]' 列表和一个蓝色的列表:

class ColoredArrayList extends ArrayList {
  Color color;

  public ColoredArrayList(Color c) {
    this.color = color;
  }
}

You'd probably want an empty red list to not equal an empty blue one.您可能希望一个空的红色列表等于一个空的蓝色列表。 However, that is impossible if you intend to follow the rules.但是,如果您打算遵守规则,那是不可能的。 That's because the equals/hashCode impl of ArrayList itself considers any other list equal to itself if it has the same items in the same order.这是因为ArrayList的 equals/hashCode impl 本身认为任何其他列表等于它自己,如果它具有相同顺序的相同项目。 Therefore:所以:

List<String> a = new ArrayList<String>();
ColoredList<String> b = new ColoredList<String>(Color.RED);

a.equals(b); // this is true, and you can't change that!

Therefore, b.equals(a) must also be true (your impl of equals has to say that an empty red list is equal to an empty plain arraylist), and given that an empty arraylist is also equal to an empty blue one, given that a.equals(b) and b.equals(c) implies that a.equals(c) , a red empty list has to be equal to a blue empty list.因此, b.equals(a)也必须为真(你的equals的含义必须说一个空的红色列表等于一个空的普通数组列表),并且给定一个空的 arraylist 也等于一个空的蓝色列表,给定a.equals(b)b.equals(c)意味着a.equals(c) ,红色空列表必须等于蓝色空列表。

There is an easy solution for this that brings in new problems, and a hard solution that is objectively better.有一个简单的解决方案会带来新的问题,也有一个客观上更好的硬解决方案。

The easy solution is to define that you can't be equal to anything except exact instances of yourself, as in, any subclass is insta-disqualified.简单的解决方案是定义除了你自己的确切实例之外,你不能等于任何东西,因为任何子类都是 insta-disqualified。 Imagine ArrayList 's equals method returns false if you call it with an instance of a subclass of ArrayList. Then you could make your colored list just fine.想象一下,如果您使用 ArrayList 的子类实例调用它, ArrayList的 equals 方法将返回 false。然后您就可以使您的彩色列表很好。 But, this isn't necessarily great, for example, you probably want an empty LinkedList and an empty ArrayList to be equal.但是,这不一定很好,例如,您可能希望一个空的LinkedList和一个空的ArrayList相等。

The harder solution is to introduce a second method, canEqual , and call it.更难的解决方案是引入第二种方法canEqual并调用它。 You override canEqual to return 'if other is instanceof the nearest class in my hierarchy that introduces equality-relevant state'.您覆盖canEqual以返回“如果 other 是我的层次结构中最近的 class instanceof ,它引入了与平等相关的状态”。 Thus, your ColoredList should have @Override public boolean canEqual(Object other) { return other instanceof ColoredList; }因此,你的 ColoredList 应该有@Override public boolean canEqual(Object other) { return other instanceof ColoredList; } @Override public boolean canEqual(Object other) { return other instanceof ColoredList; } . @Override public boolean canEqual(Object other) { return other instanceof ColoredList; }

The problem is, all classes need to have that and use it, or it's not going to work, and ArrayList does not have it.问题是,所有类都需要有它并使用它,否则它不会工作,而ArrayList没有它。 And you can't change that.你无法改变这一点。

Project Lombok can generate this for you if you prefer.如果您愿意, Project Lombok可以为您生成它。 It's not particularly common;这不是特别常见; I'd only use it if you really know you need it.只有当你真的知道你需要它时,我才会使用它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么this.hashCode()和super.hashCode()在子类中返回相同的值? - why this.hashCode() and super.hashCode() returns the same value in subclass? 在类中返回 super.equals 和 super.hashcode 是一种不好的做法吗? - Is it a bad practice to return super.equals and super.hashcode in a class? 在子类中调用super.equals和super.hashCode? - Calling super.equals and super.hashCode in child class? 为什么使用相同种子创建的两个Random对象会产生与hashcode()不同的结果 - Why do two Random objects created with the same seed produce different results from hashcode() 我应该如何使用Google guava hashCode()调用super.hashcode - how should I invoke super.hashcode with google guava hashCode() 用超类hashCode和Object覆盖hashCode - Override hashCode with super class hashCode and Objects 为什么String对象的hashCode()与自定义类的对象的hashCode()不同? - why is hashCode() for String object different from hashCode() of a custom class' object? 为什么Java中不同对象的hashCode()可以返回相同的值? - Why can hashCode() return the same value for different objects in Java? 为什么将超类分配给子类会产生错误? - Why does super class assignment to subclass give an error? 为什么this == other和this.hashcode == other.hashcode()给出不同的结果? - Why are this == other and and this.hashcode == other.hashcode() giving different results?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM