今天听同事的饭后谈资聊到 java 的 == 和 equals,冥冥中我听到这么一句话:== 比较地址,equals 比较值 😱😱😱,这句话属实震惊到我了。所以这篇就好好聊聊 == 和 equals 到底比的是什么?
上案例:
package tech.kpretty;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.HashSet;
class Person {
private String name;
private int age;
Person(String name) {
this.name = name;
}
}
public class Demo {
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2);//?
System.out.println(s1.equals(s2));//?
HashSet<String> set1 = new HashSet<>();
set1.add(s1);
set1.add(s2);
System.out.println(set1.size());//?
System.out.println("======================");
Person p1 = new Person("abc");
Person p2 = new Person("abc");
System.out.println(p1 == p2);//?
System.out.println(p1.equals(p2));//?
HashSet<Person> set2 = new HashSet<>();
set2.add(p1);
set2.add(p2);
System.out.println(set2.size());//?
}
}
请说出第 23、24、28、32、33、37 行输出什么
👇🏻点击查看👇🏻

如果上面案例真的可以回答正确且能够理解,那么本文到此为止,若似懂非懂请继续往下看
八股文一:== 既可以比较基本数据类型,也可以比较引用数据类型
八股文二:== 基本数据类型比较值是否相等,引用数据类型比较内存地址
八股文三:new 出来的对象,地址值一定不相等
因此只要看到对象是 new 出来的且使用 == 比较一定是 false,因此 23、32 为 false
八股文四:equals 只能比较引用数据类型
首先明确一点,equals 是哪个类的方法?有人告诉我 equals 是 String 类的方法👏👏,好吧!可能用到equals 基本上都是在比较字符串,但 equals 是 Object 类方法,也就是说所有的引用数据类型都可以使用,从学术上来说 equals 只能比较引用数据类型,不能比较基本数据类型,但有人说下面的也不报错呀
System.out.println(p1.equals(1));
上面的代码不就是用 equals 比较基本数据类型吗?其实这个 1 最终会被转换成包装类进行比较因为从 api 来看 equals(Object obj)
,传参为 Object
八股文五:equals 比较什么需要看具体情况,需要看有没有被重写,默认就是 ==,重写过需要看重写逻辑
没有重写,那么 equals 就是 == ,源码:
public boolean equals(Object obj) {
return (this == obj);
}
至于为什么 String 可以用 equals 比较值,因为它重写了此方法
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
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;
}
首先比较内存地址是否一样,内存地址一样那肯定是一个对象,若不是判断是否是同一个类型,类型不一致直接 fasle,若比较的也是 String 那就一个字符一个字符比较。
而上面的 Person 对象没有重写 equals 方法,因此相当于 == 比较地址,因此 24、33 结果为 true、false
至于 28、34 输出什么,涉及集合类的知识点,HashSet 底层是什么?HashSet 按照什么去重?
八股文六:HashSet 底层是 HashMap
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
HashSet 底层是一个 HashMap,元素作为 key,value 是一个静态常量 Object,因为 HashSet 的去重逻辑就是 HashMap key 的去重逻辑
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
去重逻辑:先调用 hashCode() 比较 hash 值,hash 值一样再调用 equals(),若一样则判断为同一个元素;若 hash 值不一样则判断不是同一个元素,即:hash 不一样一定是不同元素,hash 一样可能是可能不是,再通过 equals 比较(hash 碰撞的存在)
public class Demo {
public static void main(String[] args) {
System.out.println("Aa".hashCode());
System.out.println("BB".hashCode());
}
}
Hash 值一样,值不一样,因此:重写 equals 方法一般都会去重写 hashCode 方法,建议再重写 toString 方法,对于集合类去重来说,hashCode 的耗时要远小于 equals 方法
评论区