侧边栏壁纸
博主头像
王一川博主等级

努力成为一个不会前端的全栈工程师

  • 累计撰写 70 篇文章
  • 累计创建 20 个标签
  • 累计收到 39 条评论

目 录CONTENT

文章目录

== 和 equals

王一川
2021-10-11 / 0 评论 / 0 点赞 / 981 阅读 / 3,663 字
温馨提示:
本文最后更新于 2022-06-02,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

今天听同事的饭后谈资聊到 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 方法

0

评论区