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()方法必须满足以下特性:

  1. 自反性:对于任何非null的引用值x,x.equals(x)必须返回true
  2. 对称性:对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才返回true
  3. 传递性:对于任何非null的引用值x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也必须返回true
  4. 一致性:对于任何非null的引用值x和y,多次调用x.equals(y)始终返回true或始终返回false
  5. 非空性:对于任何非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()方法的约定

  1. 在应用程序执行期间,只要对象的equals比较中所用的信息没有被修改,那么对这同一个对象调用多次hashCode方法都必须始终如一地返回同一个整数
  2. 如果两个对象根据equals()方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果
  3. 如果两个对象根据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圣经

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务