==
在 Java 中,对于不同的数据类型,== 有不同的规则:
- 对于 byte、short、char、int、long、float、double、boolean 这些基本数据类型的比较,== 判断的是它们的值是否相等。
- 而如果出现了基本类型的包装类,比如用一个基本类型(int)和一个包装类(Integer)对象进行比较,使用 == 也能正确判断,因为在 Integer 和 int 进行比较时,会进行自动拆箱,这时也是判断它们的值是否相等。
- 而对于引用类型来说,== 比较的是它们的内存地址是否相等。
int a = 1;
int b = 1;
byte c = 1;
Integer d1 = new Integer(1);
Integer d2 = new Integer(1);
System.out.println(a == b); // true
System.out.println(a == c); // true
System.out.println(a == d1); // true
System.out.println(d2 == a); // true
System.out.println(d1 == d2); // false
还有一个比较有意思的现象是,当我们对包装类的 Integer 直接赋值的时候,d3 和 d4 返回的是 true,而 d5 和 d6 却返回的是 false。
Integer d3 = 1;
Integer d4 = 1;
Integer d5 = 128;
Integer d6 = 128;
System.out.println(d3 == d4); // true
System.out.println(d5 == d6); // false
这是什么原因呢?
实际上,Java 中存在一个 Integer 的常量池,-128 ~ 127 直接被缓存到常量池里。但是,对于 new 出来的 Integer 对象不适用于常量池。(感兴趣的小伙伴可以了解一下享元模式~)
那对于 String 类型又是怎么比较的呢?
String e = "abc";
String f = "abc";
String g = new String("abc");
String h = new String("abc");
System.out.println(e == f); // true
System.out.println(e == g); // false
System.out.println(g == h); // false
对于普通的字符串变量,使用 == 判断,是可以返回正确结果的。
而对于 String 类来说,因为没有自动拆箱功能,所以,普通字符串变量和 new 出来的 String 对象进行比较时,返回的是 false。
此外,两个 new 出来的字符串对象在使用 == 比较时,返回的也是 false。
那么有了 == 进行比较,我们为什么还需要 equals 呢?
equals
实际上,如果我们不重写 equals 方法,Object 类中的 equals 和 == 其实是等效的。我们可以通过源码看出来:
public boolean equals(Object obj) {
return (this == obj);
}
而对象重写 equals 方法,更多的是追求两个对象在逻辑上的相等,判定相等的条件我们可以自定义。
比如,我们有一个 Person 类,其中有 id、name、age 等多个属性。如果我们的判定规则是通过判断 id 是否相等来比较两个 Person 对象,那么这个时候我们就可以重写 equals 方法。
@Override
public boolean equals(Object o) {
if (o instanceof Person) {
Person person = (Person) o;
return Objects.equals(this.id, person.id);
}
return false;
}
我们也可以看看 String.java 中是如何重写 equals 方法的:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
那说完了 equals,hashCode 又有什么用呢?
hashCode
作用
为了理解 hashCode 的作用,我们首先要知道,在 Java 中的集合有两类,一类是 List,还有一类是 Set。
前者集合内的元素是有序且可以重复的,而后者是无序不可以重复的。
equals 方法可用于保证元素不重复,但如果每增加一个元素就检查一次,就会大大降低效率。因此,Java 采用了哈希表的原理:
当集合要添加新的元素的时候,先调用这个元素的 hashCode 方法,这样就可以一下子定位到它应该放置的物理位置。
- 如果得到的位置上没有元素,那么就可以直接把元素存储在这个位置上,不用再进行任何比较;
- 如果这个位置上已经有元素了,就调用它的 equals 方法与新元素进行比较,相同的话就不再进行存储了;
- 如果不相同,那么就发生了哈希冲突,这时候就会在这个位置上生成一个链表,将所有产生相同 hashCode 的对象存放在这个链表上;
因此,我们可以看出,实际调用 equals 方法的次数降低了,hashCode 大大解决了插入元素效率低下的问题。
特性
- 首先,两个不同对象 x、y,x.hashCode() 和 y.hashCode() 基本上不会相同;
- 字符串的散列码是由内容导出的,Object 类的默认 hashCode 会从对象的存储地址得出散列码;
- Objects.hash(Object... values) 返回一个散列码,是由提供的所有对象的散列码组合而得到的。
- equals 与 hashCode 的定义必须相容:如果 x.equals(y) 返回 true,那么 x.hashCode() 就必须与 y.hashCode() 返回相同的值;
- 两个对象 equals 相等,那么它们的 hashCode 一定也相等;
- 两个对象的 hashCode 相等,但它们 equals 不一定相等;
- 通常只要我们重写 equals 方法,就要重写 hashCode 方法;