正确重写hashCode的办法

——————————————————————————————————————————

正确重写hashCode的办法

[1]. hashCode重写成相同的值缺点

将所有对象的hashCode都返回一样的值是不科学的。比如a1和a3这两个根本不同的对象,就没有必要去比较equals,增加无谓的计算量所以应该对象本身的内容 (属性)来重写hashCode。

一旦两个对象内部不一样,就直接判定出hashCode不一样不用再调用equals进行比较

[2]. 正确书写hashCode的办法:

【原则】按照equals( )比较两个对象是否一致条件到的属性重写hashCode()

{1}. 常用的办法就是利用涉及到的的属性进行线性组合

{2}. 线性组合过程中涉及到的组合系数自定义即可。

注意,拼接之后的数值不能超过整形的表达范围。

{3}. 公式:属性1int形式+ C1*属性2int形式C2*属性3int形式+ …

【技巧】属性是引用类型的时候,如果已经重写过hashCode()那么这个引用属性的int形式就是直接调用属性已有的hashCode值。

最典型的就是这个属性是字符串类型的,String类型已经重写了hashCode()方法,所以直接拿来使用即可。

——————————————————————————————————————————

(1). 主要代码

[java] view plain copy
  1. class Person{
  2. private String name;
  3. private int age;
  4. public static void sop(Object o){
  5. System.out.println(o);
  6. }
  7. public Person(String name, int age){
  8. this.name =name;
  9. this.age =age;
  10. }
  11. public void setName(String name){
  12. this.name =name;
  13. }
  14. public String getName(){
  15. return this.name;
  16. }
  17. public void setAge(int age){
  18. this.age =age;
  19. }
  20. public int getAge(){
  21. return this.age;
  22. }
  23. public String toString(){
  24. return this.name+"::"+this.age;
  25. }
  26. //equals已经重写
  27. public boolean equals(Object obj){
  28. if(!(obj instanceof Person)){
  29. return false;
  30. }
  31. Person p =(Person)obj;
  32. //用来查看equals方法是否被调用
  33. sop(this.name +".......equals......."+ p.name);
  34. //认为名字相同并且年龄一样大的两个对象是一个
  35. return this.name.equals(p.name) && this.age == p.age;
  36. }
  37. }

(2). 将hashCode()重写成相同的值-----解决HashSet中重复添加

[1]. 问题:内容相同但是地址不同自定义对象如何避免重复的内容添加到HashSet中?

【解决办法】必须重写hashCodeequals两个方法

{1}. 此时根据底层哈希表的存储方式:哈希表会将具有相同哈希值的元素依次顺延。

{2}. hashCode值相同,HashSet在存储对象的时候,equals方法就会起作用

{3}. 示例代码:为Person类重写如下的hashCode代码

[java] view plain copy
  1. public int hashCode(){
  2. System.out.println(this.name +"...hashCode");
  3. return 60;
  4. }

这样,每一个Person对象都具有相同的哈希值。

打印结果:


HashSet添加过程分析】

【1】a1率先存入HashSet中

添加的时候,调用一次a1的hashCode方法,打印一次a1...hashCode。由于开始HashSet中没有内容,所以没有调用a1的equals方法。

内存图如下:


【2】当a2要存入HashSet的时候,HashSet首先调用a2的hashCode方法,此时打印出a2...hashCode。发现a2的hashCode也是0x3c和a1的地址值都是一样的,此时要和a1进行内容比较,打印出a2.......equals.......a1。但是发现name和age都不一样,所以equals返回false。此时HashSet就将这个a2存到集合中来。

内存图如下:


【3】当a3要存入HashSet的时候,HashSet首先调用a3的hashCode方法,查看有没有地址相同的元素,此时打印出a3...hashCode。此时集合中已经存在两个元素a1和a2,发现a3的hashCode也是0x3c和a1、a2的地址值都是一样的,此时要一一和这些对象的内容进行比较。

当a3和a2的进行比较时,a3的equals方法被调用,打印出a3.......equals.......a2。比较发现a3和a2是地址相同但是内容不同的元素。

a3再和a1进行比较,a3的equals方法再次被调用,打印出a3.......equals.......a1。

比较发现a3和a1仍然是地址相同但是内容不同的元素。

a3就被认为是集合中以前并不存在的元素,所以仍然被添加进来。

内存图如下:


【4】当运行到第二个a2要添加进来的时候,先调用hashCode,所以马上打印a2...hashCode。

但是发现,和a2 hashCode相同的元素有a1、a2和a3。这个a2要和集合中的a1、a2和a3都做内容上是否相同的比较。

a2先比较集合中的a3,调用a2的equals方法,打印出a2.......equals.......a3。

位置相同,但是内容不同。

a2再比较集合中的a2,调用a2的equals方法,打印出a2.......equals.......a2。

但是发现两者内容、地址均相同,是重复的元素不能加到集合中来,所以没有必要再把这个a2和集合中的a1进行比较。所以没有打印a2.......equals.......a1


(3). 正确重写hashCode的办法

[1]. hashCode重写成相同的值缺点

将所有对象的hashCode都返回一样的值是不科学的。比如a1和a3这两个根本不同的对象,就没有必要去比较equals,增加无谓的计算量所以应该对象本身的内容 (属性)来重写hashCode。

一旦两个对象内部不一样,就直接判定出hashCode不一样不用再调用equals进行比较

 

e.g. 分析案例

这个例子中,重写的equals方法中是通过name和age来判定两个对象是否一致的。所以,就通过Person的这两个属性name和age的线性组合来获取这个Person的hashCode值。注意到name是String类型的,所以,可以调用name的hashCode()来直接获取name对应的int值。

这里重写的hashCode方法是:

public int hashCode(){

sop(this.name +"......hashCode");

return this.name.hashCode() + 29*age;

}

打印结果:

### Java 中 Set 的 `remove` 方法失效的原因 在 Java 中,当调用 `Set` 接口实现类(如 `TreeSet` 或 `HashSet`)的 `remove` 方法时,如果该方法未能成功移除指定对象,则可能是由于以下几个原因: #### 1. **对象正确重写 `equals()` 和 `hashCode()`** 对于基于哈希表的集合(如 `HashSet`),其依赖于对象的 `hashCode()` 和 `equals()` 方法来判断两个对象是否相同。如果自定义的对象没有正确重写这两个方法,可能导致即使逻辑上认为相同的对象也无法被识别为相等[^4]。 ```java class CustomObject { private int id; public CustomObject(int id) { this.id = id; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof CustomObject)) return false; CustomObject that = (CustomObject) o; return id == that.id; } @Override public int hashCode() { return Objects.hash(id); } } ``` 通过上述方式重写了 `equals()` 和 `hashCode()` 后,`HashSet` 才能正确比较并删除对象。 --- #### 2. **使用 `TreeSet` 时未提供合适的比较器** `TreeSet` 是基于红黑树的数据结构,默认按照自然顺序排列元素。如果自定义对象未实现 `Comparable` 接口或者提供了不一致的比较逻辑,可能会导致无法正常查找或删除元素。 ```java class CustomObject implements Comparable<CustomObject> { private int id; public CustomObject(int id) { this.id = id; } @Override public int compareTo(CustomObject other) { return Integer.compare(this.id, other.id); } } ``` 确保实现了 `compareTo` 方法,并且返回的结果与 `equals()` 方法保持一致性。 --- #### 3. **并发修改问题** 某些集合(如 `ArrayList.iterator` 实现中的 `remove` 方法)会维护一个内部计数器 `modCount` 来检测是否有其他线程正在修改集合。如果没有同步访问控制,可能引发 `ConcurrentModificationException` 或者使 `remove` 失效[^1]。 可以通过以下方式进行改进: - 使用线程安全的集合替代品,例如 `CopyOnWriteArraySet`。 - 对集合的操作加锁以防止并发冲突。 --- #### 4. **目标对象不存在于集合中** 有时开发者误以为某个对象存在于集合中而尝试调用 `remove` 方法。实际上,只有完全匹配的对象才能被移除。这通常发生在创建新实例而非引用已有实例的情况下。 ```java Set<String> set = new HashSet<>(); set.add("example"); // 错误做法:新建了一个不同的字符串对象 String toRemove = new String("example"); boolean removed = set.remove(toRemove); // 返回 false // 正确做法:直接传递字面量 removed = set.remove("example"); // 返回 true ``` 因此,在调用 `remove` 前应确认要移除的对象确实存在於集合中。 --- ### 总结解决方案 针对以上提到的各种情况,以下是具体的解决办法: 1. 如果是 `HashSet` 类型,需确保自定义对象正确覆盖 `equals()` 和 `hashCode()` 方法。 2. 若采用的是 `TreeSet`,则需要保证对象实现了 `Comparable` 接口或是显式指定了比较器。 3. 防范多线程环境下的数据竞争条件,考虑引入同步机制或选用更健壮的数据结构。 4. 调试程序前先验证待删项的确切身份及其状态。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值