前言
从Java数据类型出发,聊聊Java中的==和equals。
Java数据类型
Java 语言支持的数据类型分为两种:基本数据类型(Primitive Type)和引用数据类型(Reference Type)。
引用数据类型建立在基本数据类型的基础上,包括数组、类和接口。引用数据类型是由用户自定义,用来限制其他数据的类型。空类型null也是一种引用类型,不能转换成基本类型,因此不要把一个 null 值赋给基本数据类型的变量。
对于基本数据类型,Java 为每种基本数据类型分别设计了对应的类,称之为包装类(Wrapper Classes),除了Integer和Character以外,包装类名都和基本数据类型相同,只是首字母大写。关于数据类型的更多细节,就不在这里赘述了。
== 和 equals
首先明确一点,equals方法不能作用于基本数据类型变量。关于这句话其实可以展开说明一下,首先基本类型是不能作为方法的主体的,这是一定的,但基本类型能不能作为equals中的参数呢?有时也是可以的,因为Java的“自动装箱”机制,基本类型在传入时可以被封装为包装类,事实上参与函数的是包装类。
在比较基本数据类型时,==
比较的是值。
在比较引用类型时,==
比较的是对象的内存地址。如果equals方法没有经过重写,则与==
相同,比较地址;如果equals方法经过重写,对于Java提供的类来说,则是比较对象存储的内容是否相同,也就是比较“值”。
Java提供的绝大多数类(不是全部)都重写了equals方法,以上讲述的区别与联系主要是关于Java内置类和基本数据类型。
equals() 和 hashCode()
对于我们自己写代码,新建一个类而言,要么就不重写equals方法,此时等同于==
;要么就同时重写equals和hashCode方法,按照我们定义的规则来比较对象是否相等。
Java中对equals()的规范
- 对称性:如果x.equals(y)返回是”true”,那么y.equals(x)也应该返回是”true”。
- 自反性:x.equals(x)必须返回是”true”。
- 传递性:如果x.equals(y)返回是”true”,而且y.equals(z)返回是”true”,那么x.equals(z)也应该返回是”true”。
- 一致性:如果x.equals(y)返回是”true”,只要x和y内容一直不变,不管重复x.equals(y)多少次,返回都是”true”。
- 非空性,x.equals(null),永远返回是”false”;假设z是和x不同类型的对象,则x.equals(z)永远返回是”false”。
hashCode()的作用是用来获取哈希码,用于确定对象在哈希表中的位置,与equals()一样,所有的类都有hashCode方法。hashCode()只有在创建某个类的哈希表时才有用,需要根据方法返回值确认对象在哈希表中的位置。如果一个对象一定不会在散列表中使用,那么是没有必要复写hashCode方法的。但一般情况下我们还是会复写hashCode方法,因为谁能保证这个对象不会出现在HashMap、HashSet、HashTable…中呢?
Object.hashCode()的通用约定
- 在应用程序中,只要对象的equals方法的比较操作所用的信息没有修改,那么对于同一个对象的多次调用hashCode(),必须始终返回同一个哈希值。
- 如果两个对象通过equals()比较相等,那么它们的哈希值相同。
- 如果两个对象通过equals()比较不等,他们的哈希值可能相同也可能不同,取决于hashCode的实现,由此哈希表的性能也会有区别。
考虑一个常见的场景,HashSet是一个不允许有重复元素的集合,该集合会维护一个已存入对象的哈希值表。当插入一个新的对象时,我们首先会想到使用equals()逐个比较来确定是否有重复元素,但这必然造成效率问题。另外一个合理的方式就是对该对象调用hashCode()得到哈希值,然后在哈希值表中进行比对,如果不存在则直接存入;如果存在,则再调用equals()进行比较,相同的话就不再存入,不同的话散列到其他地址。这样一来实际调用equals()的次数就大大降低了。
因此,如果不重写对象的hashCode()方法,就有可能造成相同对象产生不同哈希值的情况,这就破坏了HashSet的性质。当然,这只是不重写hashCode(),或者说不遵守hashCode()规范的其中一个坏处。
对于equals() 和 hashCode()的部分,可以用两个问题来加深印象:
1、两个对象,如果a.equals(b)==true,那么a和b是否相等?
答:相等,但对象地址不一定相等。
2、两个对象,如果哈希值一样,那么两个对象是否相等?
答:不一定相等,判断两个对象是否相等,需要使用equals()。
最后,记录一个实现高质量equals()的诀窍:
- 使用
==
操作符检查“参数是否为这个对象的引用”;- 使用
instanceof
操作符检查“参数是否为正确的类型”;- 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;
- 编写完equals方法后,问自己它是否满足对称性、传递性、一致性;
- 重写equals方法时总是要重写hashCode方法;
- 不要将equals方法参数中的Object对象替换为其他的类型,在重写时不要忘掉
@Override
注解。
参考
[1]http://c.biancheng.net/view/5672.html
[2]https://blog.csdn.net/qq_44543508/article/details/95449363
[3]https://www.jianshu.com/p/da7491e5be53
[4]https://blog.csdn.net/u013063153/article/details/78808923
[5]https://cloud.tencent.com/developer/article/1018529
后记
首发于 silencezheng.top,转载请注明出处。