hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
hashCode()是怎么算出来的?
理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。
一个数与 31 相乘可以转换成移位和减法:31*x == (x<<5)-x,编译器会自动进行这个优化。
两个对象equals,这两个对象的hashCode相等;两个对象的hashCode相等,这两个对象未必相等
为什么equals()相等,则hashCode()必须相等。如果两个对象equals()相等,则它们在哈希表(如HashSet、HashMap等)中只应该出现一次;如果hashCode()不相等,那么它们会被散列到哈希表的不同位置,哈希表中出现了不止一次。
这两个方法都是Object类的方法,下面是Object类中这两个方法的源码:
// 直接相等比较,不同对象的话返回false public boolean equals(Object obj) { return (this == obj); } // hashCode为本地方法 public native int hashCode();String类中equals()和hashCode()方法的实现
private final char[] value; // 当且仅当两个字符串的长度和内容相等时才equals public boolean equals(Object anObject) { // 对象相等直接equals 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) { // 只要对应的字符不相等则不equals if (v1[i] != v2[i]) return false; i++; } return true; } // 长度不等则不equals } // 类型不同则不equals return false; } // 哈希值的计算公式:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],n为字符串长度 // String的哈希值的计算使用了31,主要是以下3个原因: // 1. 质数与其他数字相乘后,计算结果唯一的概率更大,减少哈希冲突的概率 // 2. 质数越大,哈希冲突概率越小,但是计算速度越慢,31是哈希冲突和性能的折中值,是实验观测的结果 // 3. JVM会自动对31进行优化:31 * i == (i << 5) - i public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }例子:下面这个例子里,如果只重写了equals方法,没有重写hashCode方法,则输出为null,这是因为apple1和apple2是两个不同的对象,这两个对象的hashCode不同,定位就不同,所以查不到输出null。添加上重写的hashCode方法后,定位相同,然后按equals方法比较也相等,所以查到了输出10。
public class Apple { private String color; public Apple(String color) { this.color = color; } public boolean equals(Object obj) { if (obj == null) { return false; } if (!(obj instanceof Apple)) { return false; } if (obj == this) { return true; } return ((Apple) obj).color.equals(this.color); } public int hashCode() { return color.hashCode(); } public static void main(String[] args) { HashMap<Apple, Integer> map = new HashMap<>(); Apple apple1 = new Apple("red"); Apple apple2 = new Apple("red"); map.put(apple1, 10); Integer result = map.get(apple2); System.out.println(result); } }应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。
拷贝对象和原始对象的引用类型引用同一个对象。 其实就相当于指向的是同一个内存地址,通过引用A修改的对象内容同样会在引用B上看到,因为AB指向的是同一个对象。
拷贝对象和原始对象的引用类型引用不同对象。 其实就是通过重写clone方法将原对象的所有属性全部复制到新对象中
使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。