【Java】杂项、Equals 和 hashCode

本文详细解析了equals方法和hashCode方法的含义及它们之间的关系。通过三个具体的测试案例,阐述了在散列结构存储中,为了确保元素的唯一性和提高查找效率,如何正确地覆盖这两个方法。同时,还讨论了内存泄漏问题及其解决方案。

equals 和 hashCode含义

equal:判断两个对象是否相等,如果相同,返回true 否则返回false

hashcode: 返回一个int数 Object 默认(内部地址转化为一个数字)

 

两者关系

  • 如果equals(obj)返回true, 那么 hashCode一定返回一样的值

  • 如果equals(obj)返回false, 那么 hashCode 不一定返回一样的值

  • 如果hashCode 返回相同的数,那么 equals 不一定返回true

  • 如果hashCode返回不一样的数,那么equals 一定返回false

 

集合中使用

放入流程

放入散列时候,先比较hashCode, 然后判断equals

HashSet 放入元素

public boolean add(E e) {  
    return map.put(e, PRESENT)==null;  
}

 

HashMap 放入元素

public V put(K key, V value) {  
    if (key == null)  
        return putForNullKey(value);  
    int hash = hash(key.hashCode());  
    int i = indexFor(hash, table.length);  
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
        Object k;  
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
            V oldValue = e.value;  
            e.value = value;  
            e.recordAccess(this);  
            return oldValue;  
        }  
    }  

    modCount++;  
    addEntry(hash, key, value, i);  
    return null;  
}

测试一: 覆盖 equals 不覆盖 hashCode

public class HashCodeTest {  
    public static void main(String[] args) {  
        Collection set = new HashSet();  
        Point p1 = new Point(1, 1);  
        Point p2 = new Point(1, 1);  
  
        System.out.println(p1.equals(p2));  
        set.add(p1);   //(1)  
        set.add(p2);   //(2)  
        set.add(p1);   //(3)  
  
        Iterator iterator = set.iterator();  
        while (iterator.hasNext()) {  
            Object object = iterator.next();  
            System.out.println(object);  
        }  
    }  
}  
  
class Point {  
    private int x;  
    private int y;  
  
    public Point(int x, int y) {  
        super();  
        this.x = x;  
        this.y = y;  
    }  
  
    @Override  
    public boolean equals(Object obj) {  
        if (this == obj)  
            return true;  
        if (obj == null)  
            return false;  
        if (getClass() != obj.getClass())  
            return false;  
        Point other = (Point) obj;  
        if (x != other.x)  
            return false;  
        if (y != other.y)  
            return false;  
        return true;  
    }  
  
    @Override  
    public String toString() {  
        return "x:" + x + ",y:" + y;  
    }  
  
} 

 

输出

true  
x:1,y:1  
x:1,y:1

 

分析

(1)当执行set.add(p1)时(1),集合为空,直接存入集合;

(2)当执行set.add(p2)时(2),首先判断该对象(p2)的hashCode值所在的存储区域是否有相同的hashCode,因为没有覆盖hashCode方法,所以jdk使用默认Object的hashCode方法,返回内存地址转换后的整数,因为不同对象的地址值不同,所以这里不存在与p2相同hashCode值的对象,因此jdk默认不同hashCode值,equals一定返回false,所以直接存入集合。

(3)当执行set.add(p1)时(3),时,因为p1已经存入集合,同一对象返回的hashCode值是一样的,继续判断equals是否返回true,因为是同一对象所以返回true。此时jdk认为该对象已经存在于集合中,所以舍弃。

 

测试二:覆盖hashCode方法,但不覆盖equals方法,仍然会导致数据的不唯一性

class Point {  
    private int x;  
    private int y;  
  
    public Point(int x, int y) {  
        super();  
        this.x = x;  
        this.y = y;  
    }  
  
    @Override  
    public int hashCode() {  
        final int prime = 31;  
        int result = 1;  
        result = prime * result + x;  
        result = prime * result + y;  
        return result;  
    }  
  
    @Override  
    public String toString() {  
        return "x:" + x + ",y:" + y;  
    }  
  
}  

输出

false  
x:1,y:1  
x:1,y:1

分析

(1)当执行set.add(p1)时(1),集合为空,直接存入集合;

(2)当执行set.add(p2)时(2),首先判断该对象(p2)的hashCode值所在的存储区域是否有相同的hashCode,这里覆盖了hashCode方法,p1和p2的hashCode相等,所以继续判断equals是否相等,因为这里没有覆盖equals,默认使用'=='来判断,所以这里equals返回false,jdk认为是不同的对象,所以将p2存入集合。

(3)当执行set.add(p1)时(3),时,因为p1已经存入集合,同一对象返回的hashCode值是一样的,并且equals返回true。此时jdk认为该对象已经存在于集合中,所以舍弃。

 

综合上述两个测试,要想保证元素的唯一性,必须同时覆盖hashCode和equals才行。

(注意:在HashSet中插入同一个元素(hashCode和equals均相等)时,会被舍弃,而在HashMap中插入同一个Key(Value 不同)时,原来的元素会被覆盖。)

测试三 内存泄漏问题

public class HashCodeTest {  
    public static void main(String[] args) {  
        Collection set = new HashSet();  
        Point p1 = new Point(1, 1);  
        Point p2 = new Point(1, 2);  
  
        set.add(p1);  
        set.add(p2);  
          
        p2.setX(10);  
        p2.setY(10);  
          
        set.remove(p2);  
  
        Iterator iterator = set.iterator();  
        while (iterator.hasNext()) {  
            Object object = iterator.next();  
            System.out.println(object);  
        }  
    }  
}  
  
class Point {  
    private int x;  
    private int y;  
  
    public Point(int x, int y) {  
        super();  
        this.x = x;  
        this.y = y;  
    }  
  
  
    public int getX() {  
        return x;  
    }  
  
  
    public void setX(int x) {  
        this.x = x;  
    }  
  
  
    public int getY() {  
        return y;  
    }  
  
  
    public void setY(int y) {  
        this.y = y;  
    }  
  
  
    @Override  
    public int hashCode() {  
        final int prime = 31;  
        int result = 1;  
        result = prime * result + x;  
        result = prime * result + y;  
        return result;  
    }  
  
  
    @Override  
    public boolean equals(Object obj) {  
        if (this == obj)  
            return true;  
        if (obj == null)  
            return false;  
        if (getClass() != obj.getClass())  
            return false;  
        Point other = (Point) obj;  
        if (x != other.x)  
            return false;  
        if (y != other.y)  
            return false;  
        return true;  
    }  
  
  
    @Override  
    public String toString() {  
        return "x:" + x + ",y:" + y;  
    }  
  
}  

结果

x:1,y:1  
x:10,y:10

 

分析

假设p1的hashCode为1,p2的hashCode为2,在存储时p1被分配在1号桶中,p2被分配在2号筒中。这时修改了p2中与计算hashCode有关的信息(x和y),当调用remove(Object obj)时,首先会查找该hashCode值得对象是否在集合中。假设修改后的hashCode值为10(仍存在2号桶中),这时查找结果空,jdk认为该对象不在集合中,所以不会进行删除操作。然而用户以为该对象已经被删除,导致该对象长时间不能被释放,造成内存泄露。解决该问题的办法是不要在执行期间修改与hashCode值有关的对象信息,如果非要修改,则必须先从集合中删除,更新信息后再加入集合中。

总结

1.hashCode是为了提高在散列结构存储中查找的效率,在线性表中没有作用。

2.equals和hashCode需要同时覆盖。

3.若两个对象equals返回true,则hashCode有必要也返回相同的int数。

4.若两个对象equals返回false,则hashCode不一定返回不同的int数,但为不相等的对象生成不同hashCode值可以提高哈希表的性能。

5.若两个对象hashCode返回相同int数,则equals不一定返回true。

6.若两个对象hashCode返回不同int数,则equals一定返回false。

7.同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。

 

原文:https://blog.youkuaiyun.com/haobaworenle/article/details/53819838

 

### 重写 `equals` `hashCode` 方法的最佳实践 在 Java 中,`equals()` `hashCode()` 是 `Object` 类中定义的重要方法。它们分别用于比较对象的相等性计算对象的哈希值。正确地重写这两个方法对于实现集合类中的对象比较、数据存储查找至关重要[^2]。 #### 1. 重写 `equals` 方法 `equals` 方法的重写需要遵循以下原则: - **自反性**:对于任何非空引用值 `x`,`x.equals(x)` 应返回 `true`。 - **对称性**:对于任何非空引用值 `x` `y`,如果 `x.equals(y)` 返回 `true`,则 `y.equals(x)` 也应返回 `true`。 - **传递性**:对于任何非空引用值 `x`、`y` `z`,如果 `x.equals(y)` 返回 `true`,并且 `y.equals(z)` 返回 `true`,那么 `x.equals(z)` 也应返回 `true`。 - **一致性**:多次调用 `x.equals(y)` 始终返回相同的结果(前提是对象的状态未被修改)。 - **与 `null` 的比较**:对于任何非空引用值 `x`,`x.equals(null)` 应返回 `false`。 以下是重写 `equals` 方法的一个示例: ```java @Override public boolean equals(Object o) { if (this == o) return true; // 比较是否为同一个对象 if (o == null || getClass() != o.getClass()) return false; // 检查类型是否一致 UserOver user = (UserOver) o; return id == user.id && Objects.equals(name, user.name) && Objects.equals(gender, user.gender); } ``` #### 2. 重写 `hashCode` 方法 `hashCode` 方法的重写需要确保以下几点: - 如果两个对象通过 `equals` 方法判断为相等,则它们的 `hashCode` 必须相同。 - 如果两个对象通过 `equals` 方法判断为不相等,则它们的 `hashCode` 不一定不同(允许哈希碰撞)。 - `hashCode` 的计算应基于对象的不可变字段,以保证对象存入集合后能够正确检索。 以下是重写 `hashCode` 方法的一个示例: ```java @Override public int hashCode() { int result = Objects.hash(id, name, gender); // 使用 Objects.hash 简化计算 return result; } ``` #### 3. 为什么需要同时重写? 当对象被存入集合类(如 `HashMap` 或 `HashSet`)时,`hashCode` 方法用于确定对象的存储位置。如果两个对象的 `hashCode` 相同,则会进一步使用 `equals` 方法来确认它们是否真正相等。因此,只要重写了 `equals` 方法,就必须重写 `hashCode` 方法,以确保两者的一致性[^4]。 #### 4. 最佳实践 - **选择业务唯一性字段**:在重写 `equals` `hashCode` 方法时,应选择能够唯一标识对象的字段参与计算。例如,`id` 字段通常是首选。 - **避免可变字段**:如果参与 `hashCode` 计算的字段被修改,可能导致对象存入集合后无法正确检索。因此,应尽量避免使用可变字段。 - **单元测试验证**:使用 JUnit 测试 `equals` `hashCode` 的契约关系,确保它们的行为符合预期。 ### 示例代码 以下是一个完整的示例,展示如何正确重写 `equals` `hashCode` 方法: ```java package entity; import java.util.Objects; public class UserOver { private int id; private String name; private String gender; // getter() and setter() @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserOver user = (UserOver) o; return id == user.id && Objects.equals(name, user.name) && Objects.equals(gender, user.gender); } @Override public int hashCode() { return Objects.hash(id, name, gender); } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值