目录
- 1. == 和 equals
- 2. hashCode
- 1. hash 概述
- 2. hashCode
- 1. 概念
- 2. 获取对象地址
- 3. hashCode 与 equals
- 1. 两者关系
- 2. 重写 equals并 重写 hashCode
- 1. 只重写 equals
- 2. 重写 equals 并重写 hashCode
- 3. 小结
1. == 和 equals
在 Java 中,判断两个对象是否相等,使用的是 == 和 equals
== 和 equals 的区别:
-
== 判断两对象相等,是判断它们的地址是否相等
-
equals 未被重写,也是通过判断地址来判断相等,等同于 ==
public boolean equals(Object obj) { return (this == obj); }
但,当我们的业务有特殊需求的时候,往往会 重写 equals,如:String 中的 equals 就被重写
// 地址相等 => 相等 // 字符串内容相同 => 相等 public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; // String底层是用 char value[]存储字符的 int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
2. hashCode
1. hash 概述
hash: 散列算法,用来标志 文件/数据 的唯一性,相当于一种指纹。
- 特点:
- 正向快速:明文 + hash算法 => 快速获取 hash值
- 逆向困难:几乎不可能根据给定 hash值 推导出密文
- 输入敏感:原始输入信息轻微不同就会导致 hash 值 有很大的区别
- 避免冲突:两段不同的明文几乎不可能出现相同的hash值
hash表: 存储 hash值 。
2. hashCode
1. 概念
hashCode 并不是对象的地址!!!
- 每个对象都有一个 hashCode,其代表的是,对象存入散列表(HashTable、HashMap、HashSet)的地址
- 只有要将对象存入 散列表 的时候才会用到 hashCode
获取 hashCode :
Object obj = new Object();
System.out.println(obj);
int hashCode = obj.hashCode();
System.out.println("hashCode: " + hashCode);
System.out.println("hashCode16进制: " + Integer.toHexString(hashCode));
System.out.println("使用最初的hashCode算法:" + System.identityHashCode(hashCode));
java.lang.Object@1b6d3586
hashCode: 460141958
hashCode16进制: 1b6d3586
使用最初的hashCode算法:1163157884
通过 JVM Options 指定 hashCode 的生成策略: N的取值在 [0,4]之间的随机整数,当N不在此区间,就会使用默认的生成策略。
-XX:hashCode=N
2. 获取对象地址
既然已经知道 hashCode 与对象的存储地址没关系,那么我们想要获取对象地址咋办?
-
引入依赖:
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.16</version> </dependency>
-
VM.current().addressOf(obj) 获取对象地址
Object obj = new Object(); String str = new String("123"); long strAddress = VM.current().addressOf(str); long objAddress = VM.current().addressOf(obj);
3. hashCode 与 equals
1. 两者关系
重写了 equals,不一定要重写 hashCode !!!
只有要将对象存入 散列表 ,并且已经重写了 equals,才需要重写 hashCode !!!
在 散列表 中,两个对象相等(equals 返回 true),那么他们的 hashCode 一定相同
先来简单回顾一下 HashMap 存储 key-value 的过程:
- 计算 hash 值:key.hashCode()^( key.hashCode() >>>16 ) 计算出hash值
- 如果 没发生hash碰撞(两个对象的hash值相同),就把对象直接存入桶中
如果 发生了hash碰撞,就判断 key 是否相等,并使用 链表/红黑树 进行数据处理
在 HashMap 中判断两个对象相等的条件时:key相等 且 value相等
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
所以,在计算对象的hash值,并将其装入散列表的时候,需要计算其 key的 hashCode ,也需要计算其 value的 hashCode:
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
通过参考 HashMap 重写 HashMap 和 HashMap 的思路,我们就能自己重写这两个方法。
2. 重写 equals并 重写 hashCode
1. 只重写 equals
我们自定义对象 User,重写了其 equals (使得 name 相等,两个对象就相等)。
代码:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private Integer age;
/**
* @Description 名字相同也返回true
* @param obj
* @return boolean
*/
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof User)) return false;
if (obj == this) return true;
User user = (User) obj;
return (user.name .equals(this.name));
}
public static void main(String[] args) {
Set<Object> set = new HashSet<>();
User user1 = new User("张三", 3);
User user2 = new User("张三", 4);
User user3 = new User("李四", 5);
set.add(user1);
set.add(user2);
set.add(user3);
System.out.println(user1.equals(user2));
System.out.println(set);
set.forEach( item -> {
System.out.print(item.hashCode() + " ");
});
}
}
输出:
分析:
- HashSet 不能装重复对象,而 user1 和 user2 是同一对象(equals 返回 true),但是两者都被装入了 HashSet,这逻辑明显就是错误的
- 这就是 equals 返回 true 的范围 > hashCode 相等的范围 导致的
2. 重写 equals 并重写 hashCode
在上述案例的基础上,重写一下 hashCode
代码:
@Override
public int hashCode() {
return name.hashCode();
}
输出:
分析:
- user1 和 user2 是同一对象(equals 返回 true),所以,只装入了 user1
- 这就是重写了两方法之后, equals 返回 true 的范围 = hashCode 相等的范围的结果
那么,我判断对象相等,需要其 name 和 age 都相等呢?
先重写 equals :
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof User)) return false;
if (obj == this) return true;
User user = (User) obj;
return (user.name .equals(this.name) && user.age == this.age);
}
其 hashCode 应该这样写:
@Override
public int hashCode() {
return name.hashCode() ^ age.hashCode();
}
main 方法:
public static void main(String[] args) {
Set<Object> set = new HashSet<>();
User user1 = new User("张三", 3);
User user2 = new User("张三", 1);
User user3 = new User("李四", 5);
User user4 = new User("李四", 5);
set.add(user1);
set.add(user2);
set.add(user3);
set.add(user4);
System.out.println(user1.equals(user2));
System.out.println(user3.equals(user4));
System.out.println(set);
set.forEach( item -> {
System.out.print(item.hashCode() + " ");
});
}
输出:
3. 小结
- 重写了 equals 不一定要重写 hashCode
- 在没有使用 散列表(HashTable、HashMap、HashSet)的时候,hashCode 就是一串没有人调用的整数
- 在 散列表 中,两个对象相等(equals 返回 true),那么他们的 hashCode 一定相同
- 一旦使用了散列表,并且重写了 equals,那么就必须重写 hashCode