Is==保证返回正确的结果



据我所知,Java中的==运算符比较对象的引用(int)
ObjecthashCode方法的默认实现返回的值。

hashCode方法有一个实现注释:

只要合理实用,定义的hashCode方法按类Object为不同的对象返回不同的整数。

相当实用:这意味着,无论多么小,两个不同的对象都有可能具有相同的hashCode或引用值。

因此,如果我使用==比较两个不同的对象(不覆盖hashCodeequals),结果很可能是真的(?)。equals的默认实现执行==检查:

public class Test {
public static void main(String[] args) {
var t1 = new Test();
var t2 = new Test();
System.out.println(t1.hashCode() + ":" + t2.hashCode()); // 2055281021:1554547125 (Could've been 1554547125:1554547125 ?)
System.out.println(t1 == t2); // false (Could've been true ?)
System.out.println(t1.equals(t2)); // false (Could've been true ?)
}
}

为什么equalshashCode仅在某些情况下被重写,而其余时间(许多库类,如Thread)依赖于相等性检查的默认实现,而不能保证返回正确的结果?

而且,一个极度厌恶风险的人如何确保上述假阳性永远不会发生?如果类至少有一个非静态字段,则可以覆盖hashCodeequals。但是,如果不是这样(就像上面的Test类)呢?

你能解释一下我这里缺了什么吗?

编辑1:

hashCode添加API注释(取自Silvio的回答):

这通常是通过将对象的内部地址转换为整数来实现的,但JavaTM编程语言不需要这种实现技术

好吧,这里有很多问题,让我们试着把它分解一下。

据我所知,Java中的==运算符比较对象的引用(int)。该值是ObjecthashCode方法的默认实现返回的值。

Java中的==比较引用,是的。这些引用不一定int兼容。在许多常见的体系结构上,int可能会与引用可以占据的大部分可观察空间重合,但通常情况并非如此。

特别是。

  • int是一个有符号的类型。这意味着它的值有一半是负数。指针通常是无符号的
  • 即使忽略符号问题,int也是32位类型。大多数现代计算机都是64位的,这意味着地址空间更适合64位整数(即Javalong)。因此,int中甚至只能存储一小部分地址

其次,hashCode不需要与指针本身有任何关系。从您已经引用的hashCode文档中

(这通常是通过将对象的内部地址转换为整数来实现的,但JavaTM编程语言不需要这种实现技术。)

Java实现可以自由选择它想要的任何hashCode。也许你运行的是一些奇怪的嵌入式硬件,在计算中使用一些额外的标志变量是有意义的。hashCode应该而不是被假定为指针。

为什么equalshashCode仅在某些情况下被重写,而在不能保证返回正确结果的情况下,其余时间(许多库类,如Thread)依赖于相等性检查的默认实现?

你对";正确的";在这里Java规范要求的保证可以从文档中总结出来

equals方法在非null对象引用上实现等价关系:

  • 它是自反的:对于任何非null引用值x,x.equals(x)都应该返回true
  • 它是对称的:对于任何非null引用值x和y,x.equals(y)应返回true,当且仅当y.equals(x)返回true
  • 它是可传递的:对于任何非空的引用值x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)应该返回true
  • 它是一致的:对于任何非null引用值x和y,如果不修改对象上的equals比较中使用的信息,则多次调用x.equals(y)会一致地返回true或一致地返回false
  • 对于任何非null引用值x,x.equals(null)应返回false

。。。

在Java应用程序的执行过程中,每当对同一对象多次调用hashCode方法时,只要不修改对象上的equals比较中使用的信息,hashCode方法就必须始终返回相同的整数。从一个应用程序的一次执行到同一应用程序的另一次执行,该整数不需要保持一致。

  • 如果根据equals(Object)方法,两个对象相等,那么对这两个对象中的每一个调用hashCode方法必须产生相同的整数结果

默认的equals实现显然满足上述基本要求,并且标准保证默认的hashCode对于两个相等的对象是相同的。

当我们对平等有了更好的概念时,我们就会凌驾于equals之上。例如,如果两个字符串具有相同的字符,即使它们在内存中是不同的对象,也应该认为它们是相等的;如果两个数组列表的元素逐点相等,则应该认为它们相等。但对于Thread这样的东西来说,这意味着什么呢?两个任意线程何时应该相等?默认值就足够了,因为无论如何我们都不会从覆盖它中获得任何好处。

如果类至少有一个非静态字段,则可以覆盖hashCodeequals

等式与非静态字段的数量有什么关系?我可以很好地忽略这两个。看

public final class MySimpleClass {
public boolean equals(Object other) {
return (other != null) && (other instanceof MySimpleClass);
}
public int hashCode() {
return 42;
}
}

对于MySimpleClass来说,这是一个完全有效、一致的等式和哈希实现。特别是,由于这个类只有一个有意义的不同值,我认为这是两个方法的良好实现。不需要非静态字段。

==如果比较两个不同的对象,则始终返回false;如果比较一个对象和它自己,则总是返回true。

但不能保证两个不同的对象会返回不同的哈希代码。这是因为hashCode()返回int,而只有大约40亿个不同的int。代码中对象的数量仅受堆大小的限制。

因此,因为可能有超过40亿个不同的对象,它们的哈希代码有时可能是相同的

对于equals,默认情况下它的工作方式为==,但可以重写,因此==可以返回false,当equals返回true时,反之亦然

equalshashCode在编译时有一个不可执行的约定(它本身不同于==运算符)。

从根本上讲,一个对象应该覆盖hashCode,使得a.equals(b)(及其逆)与a.hashCode() == b.hashCode()(及其反)全等。

==运算符只希望比较数值相等,这就是为什么与对象本身(或a == a)进行比较的对象的同一实例将返回true,并对Strings和字符串interning给出一些警告。

因为equalshashCode之间的合同是不可执行的,这表明==将始终返回一个";正确的";结果取决于您对";正确";。

例如:

  • 正方形是平行四边形是正确的;任何给定的正方形和任何给定的平行四边形都是不正确的
  • 一本书就是一本字典,这是正确的;任何一本书都是字典,这是不对的
  • 汽车有轮子是正确的;任何给定的汽车都有任何给定数量的车轮,这是不正确的

也要记住,hashCode只有32位,所以两个不相关的对象之间总是有碰撞的机会(这就是让equals弥补不足的地方)。

在这种情况下,您只能根据单个对象所具有的约束和条件,以及在给定哈希代码的情况下,哪些业务规则对等式比较有意义,来信任==。如果您的业务规则要求equalshashCode的行为方式发生偏差,那么在通过这些方法进行比较时,您必须记住该上下文。

首先,==检查内存引用。JVM在内部使用指针来实现这一点。因此,每个对象都是不同的,因为它们存储在不同的内存地址中。使用存储器地址位置32或64位int/number进行比较。

对于第二个问题,如果您需要一个哈希代码实现,但该类没有字段。然后使用System.identityHashCode()来完成。它将为null对象提供零,并为同一对象提供唯一的/smal哈希代码。

据我所知,Java中的==运算符比较引用(int)个对象。

在64位体系结构上,引用需要8个字节。这是一个long,而不是int

这个值是Object中hashCode方法的默认实现返回的值。

long强制转换为int时,将丢失信息。这就是为什么hashCode()的默认实现可以为不同的对象返回相等的散列。

相关内容

  • 没有找到相关文章

最新更新