据我所知,Java中的==
运算符比较对象的引用(int
)Object
中hashCode
方法的默认实现返回的值。
hashCode
方法有一个实现注释:
只要合理实用,定义的hashCode方法按类Object为不同的对象返回不同的整数。
相当实用:这意味着,无论多么小,两个不同的对象都有可能具有相同的hashCode或引用值。
因此,如果我使用==
比较两个不同的对象(不覆盖hashCode
和equals
),结果很可能是真的(?)。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 ?)
}
}
为什么equals
和hashCode
仅在某些情况下被重写,而其余时间(许多库类,如Thread
)依赖于相等性检查的默认实现,而不能保证返回正确的结果?
而且,一个极度厌恶风险的人如何确保上述假阳性永远不会发生?如果类至少有一个非静态字段,则可以覆盖hashCode
和equals
。但是,如果不是这样(就像上面的Test
类)呢?
你能解释一下我这里缺了什么吗?
编辑1:
为hashCode
添加API注释(取自Silvio的回答):
这通常是通过将对象的内部地址转换为整数来实现的,但JavaTM编程语言不需要这种实现技术
好吧,这里有很多问题,让我们试着把它分解一下。
据我所知,Java中的
==
运算符比较对象的引用(int
)。该值是Object
中hashCode
方法的默认实现返回的值。
Java中的==
比较引用,是的。这些引用不一定与int
兼容。在许多常见的体系结构上,int
可能会与引用可以占据的大部分可观察空间重合,但通常情况并非如此。
特别是。
int
是一个有符号的类型。这意味着它的值有一半是负数。指针通常是无符号的- 即使忽略符号问题,
int
也是32位类型。大多数现代计算机都是64位的,这意味着地址空间更适合64位整数(即Javalong
)。因此,int
中甚至只能存储一小部分地址
其次,hashCode
不需要与指针本身有任何关系。从您已经引用的hashCode
文档中
(这通常是通过将对象的内部地址转换为整数来实现的,但JavaTM编程语言不需要这种实现技术。)
Java实现可以自由选择它想要的任何hashCode
。也许你运行的是一些奇怪的嵌入式硬件,在计算中使用一些额外的标志变量是有意义的。hashCode
应该而不是被假定为指针。
为什么
equals
和hashCode
仅在某些情况下被重写,而在不能保证返回正确结果的情况下,其余时间(许多库类,如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
这样的东西来说,这意味着什么呢?两个任意线程何时应该相等?默认值就足够了,因为无论如何我们都不会从覆盖它中获得任何好处。
如果类至少有一个非静态字段,则可以覆盖
hashCode
和equals
等式与非静态字段的数量有什么关系?我可以很好地忽略这两个。看
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时,反之亦然
equals
和hashCode
在编译时有一个不可执行的约定(它本身不同于==
运算符)。
从根本上讲,一个对象应该覆盖hashCode
,使得a.equals(b)
(及其逆)与a.hashCode() == b.hashCode()
(及其反)全等。
==
运算符只希望比较数值相等,这就是为什么与对象本身(或a == a
)进行比较的对象的同一实例将返回true
,并对String
s和字符串interning给出一些警告。
因为equals
和hashCode
之间的合同是不可执行的,这表明==
将始终返回一个";正确的";结果取决于您对";正确";。
例如:
- 正方形是平行四边形是正确的;任何给定的正方形和任何给定的平行四边形都是不正确的
- 一本书就是一本字典,这是正确的;任何一本书都是字典,这是不对的
- 汽车有轮子是正确的;任何给定的汽车都有任何给定数量的车轮,这是不正确的
也要记住,hashCode
只有32位,所以两个不相关的对象之间总是有碰撞的机会(这就是让equals
弥补不足的地方)。
在这种情况下,您只能根据单个对象所具有的约束和条件,以及在给定哈希代码的情况下,哪些业务规则对等式比较有意义,来信任==
。如果您的业务规则要求equals
和hashCode
的行为方式发生偏差,那么在通过这些方法进行比较时,您必须记住该上下文。
首先,==检查内存引用。JVM在内部使用指针来实现这一点。因此,每个对象都是不同的,因为它们存储在不同的内存地址中。使用存储器地址位置32或64位int/number进行比较。
对于第二个问题,如果您需要一个哈希代码实现,但该类没有字段。然后使用System.identityHashCode()来完成。它将为null对象提供零,并为同一对象提供唯一的/smal哈希代码。
据我所知,Java中的==运算符比较引用(int)个对象。
在64位体系结构上,引用需要8个字节。这是一个long
,而不是int
。
这个值是Object中hashCode方法的默认实现返回的值。
将long
强制转换为int
时,将丢失信息。这就是为什么hashCode()
的默认实现可以为不同的对象返回相等的散列。