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圣经
