18.1.3 equals()和hashCode()方法重写原则
1. 基本概念理解
1.1 equals()方法
equals()方法用于比较两个对象是否相等,Object类中的默认实现是比较对象的引用地址。
// Object类中的默认实现 public boolean equals(Object obj) { return (this == obj); }
1.2 hashCode()方法
hashCode()方法返回对象的哈希码值,主要用于哈希表数据结构中快速定位对象。
// Object类中的默认实现(本地方法) public native int hashCode();
2. 重写的必要性
2.1 为什么要重写equals()
public class Student { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } // 不重写equals()的问题 public static void main(String[] args) { Student s1 = new Student("张三", 20); Student s2 = new Student("张三", 20); System.out.println(s1.equals(s2)); // false,比较的是引用 System.out.println(s1 == s2); // false,比较的是引用 // 在集合中的问题 List<Student> students = Arrays.asList(s1); System.out.println(students.contains(s2)); // false,找不到相同学生 } }
2.2 为什么要重写hashCode()
public class HashCodeProblemDemo { static class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } // 只重写equals,不重写hashCode @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Person person = (Person) obj; return age == person.age && Objects.equals(name, person.name); } } public static void main(String[] args) { Person p1 = new Person("李四", 25); Person p2 = new Person("李四", 25); System.out.println(p1.equals(p2)); // true System.out.println(p1.hashCode() == p2.hashCode()); // 可能为false // HashMap中的问题 Map<Person, String> map = new HashMap<>(); map.put(p1, "第一个李四"); System.out.println(map.get(p2)); // 可能为null,找不到 System.out.println(map.containsKey(p2)); // 可能为false } }
3. equals()方法重写规范
3.1 equals()方法的约定
根据Object类的文档,equals()方法必须满足以下特性:
- 自反性:对于任何非null的引用值x,x.equals(x)必须返回true
- 对称性:对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才返回true
- 传递性:对于任何非null的引用值x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也必须返回true
- 一致性:对于任何非null的引用值x和y,多次调用x.equals(y)始终返回true或始终返回false
- 非空性:对于任何非null的引用值x,x.equals(null)必须返回false
3.2 标准的equals()重写模板
public class StandardEqualsExample { private String name; private int age; private Double salary; @Override public boolean equals(Object obj) { // 1. 检查是否为同一个对象引用 if (this == obj) { return true; } // 2. 检查是否为null if (obj == null) { return false; } // 3. 检查类型是否相同 if (getClass() != obj.getClass()) { return false; } // 4. 强制类型转换 StandardEqualsExample other = (StandardEqualsExample) obj; // 5. 比较关键字段 return Objects.equals(name, other.name) && age == other.age && Objects.equals(salary, other.salary); } }
3.3 不同数据类型的比较方式
public class FieldComparisonExamples { private int intField; private long longField; private float floatField; private double doubleField; private boolean booleanField; private String stringField; private Object objectField; private int[] arrayField; @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; FieldComparisonExamples other = (FieldComparisonExamples) obj; return intField == other.intField && // 基本类型直接比较 longField == other.longField && Float.compare(other.floatField, floatField) == 0 && // 浮点数使用compare Double.compare(other.doubleField, doubleField) == 0 && booleanField == other.booleanField && Objects.equals(stringField, other.stringField) && // 对象使用Objects.equals Objects.equals(objectField, other.objectField) && Arrays.equals(arrayField, other.arrayField); // 数组使用Arrays.equals } }
4. hashCode()方法重写规范
4.1 hashCode()方法的约定
- 在应用程序执行期间,只要对象的equals比较中所用的信息没有被修改,那么对这同一个对象调用多次hashCode方法都必须始终如一地返回同一个整数
- 如果两个对象根据equals()方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果
- 如果两个对象根据equals()方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,不要求一定产生不同的整数结果
4.2 标准的hashCode()重写方法
public class StandardHashCodeExample { private String name; private int age; private Double salary; @Override public int hashCode() { // 方法1:使用Objects.hash()(推荐) return Objects.hash(name, age, salary); } // 方法2:手动计算(了解原理) public int hashCodeManual() { int result = 17; // 选择一个非零常数 result = 31 * result + (name != null ? name.hashCode() : 0); result = 31 * result + age; result = 31 * result + (salary != null ? salary.hashCode() : 0); return result; } }
4.3 不同数据类型的hashCode计算
public class HashCodeCalculationExamples { private int intField; private long longField; private float floatField; private double doubleField; private boolean booleanField; private String stringField; private Object objectField; private int[] arrayField; @Override public int hashCode() { // 使用Objects.hash()处理所有字段 return Objects.hash( intField, longField, floatField, doubleField, booleanField, stringField, objectField, Arrays.hashCode(arrayField) // 数组需要特殊处理 ); } // 手动计算示例 public int hashCodeManual() { int result = 17; result = 31 * result + intField; // int直接使用 result = 31 * result + (int)(longField ^ (longField >>> 32)); // long的处理 result = 31 * result + Float.floatToIntBits(floatField); // float的处理 long doubleToLong = Double.doubleToLongBits(doubleField); result = 31 * result + (int)(doubleToLong ^ (doubleToLong >>> 32)); // double的处理 result = 31 * result + (booleanField ? 1 : 0); // boolean的处理 result = 31 * result + (stringField != null ? stringField.hashCode() : 0); // 对象的处理 result = 31 * result + (objectField != null ? objectField.hashCode() : 0); result = 31 * result + Arrays.hashCode(arrayField); // 数组的处理 return result; } }
5. 完整实现示例
5.1 学生类完整实
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
Java面试圣经 文章被收录于专栏
Java面试圣经,带你练透java圣经