【Java基础】== / equals / hashcode 作比较的区别

文章目录

  • 〇. 前言
  • 一. == 的使用
  • 二. equals 的使用
  • 三. hashcode 的使用【重点】
  • 四. 总结

〇. 前言

本文尽量用简洁、贴近实际的方式介绍 == / equals / hashcode 的使用场景以及差异,若有不够详尽之处请谅解。

一. == 的使用

  1. 基本数据类型:使用 == 比较

  2. Java对象:使用 == 比较 地址

⭐ 但是,两个特殊情况:

常量池:常见于String的比较

String str1 = "hello";
String str2 = "hello";
String str3 = new String("hello");

System.out.println(str1 == str2); // true,因为str1和str2都指向字符串常量池中的同一个对象
System.out.println(str1 == str3); // false,因为str3是通过new关键字创建的新对象,不在字符串常量池中

String str4 = "hello";
String str5 = "world";
String str6 = str4 + str5;

System.out.println(str6 == "helloworld"); // false,因为str6是通过字符串拼接得到的新对象,不在字符串常量池中
System.out.println(str6.equals("helloworld")); // true,内容相同

String str7 = str6.intern();
System.out.println(str6 == str7); // true,调用intern()方法将str6添加到字符串常量池中,返回字符串常量池中的引用

包装类缓存:常见于Integer等包装类的比较

Integer a1 = 127;
Integer a2 = 127;
Integer b1 = 128;
Integer b2 = 128;
Integer i1 = new Integer(127);
Integer i2 = new Integer(127);

System.out.println(a1 == a2); // true,IntegerCache范围
System.out.println(b1 == b2); // false,超出范围,已创建两个不同的对象
System.out.println(b1.equals(b2)); // true
System.out.println(i1 == i2); // false,没有装箱过程,纯粹的地址比较

包装类自动装箱 时,Java会检查待装箱的值 是否在缓存范围内,如果是,则 返回缓存中的对象引用,否则创建新的对象。这样可以避免频繁地创建相同值的对象,从而减少内存消耗和提高性能。

IntegerCache 和 LongCache 的范围:[-128, 127]。

二. equals 的使用

  1. 八大基本数据类型:不是对象,无法使用 equals

  2. Java对象:
    ① 自定义类 需要手动 重写 equals方法,用于比较 对象的值 是否相同,否则equals 和 == 没有差别;
    ② 常用的Java类 基本都已经重写了equals方法,例如:String、包装类…,直接使用,比较 对象的值

⭐【Tips】

使用 equals方法 的【优良习惯】

用常量比较变量,即 把不会为NULL的对象放在前面;如果都有可能为NULL,可以使用Objects.equals()进行比较,确保本次比较有返回值,不会报错。

String s1 = null;
String s2 = new String("hello");

System.out.println("hello".equals(s2)); // 正常返回值,true
System.out.println(Objects.equals(s1, s2)); // 正常返回值,false
System.out.println(s2.equals(s1)); // 正常返回值,false
System.out.println(s1.equals(s2)); // 空指针异常

② 包装类 使用equals 比较时的陷阱:类型一致

Integer i = 8;
Long l = 8L;
System.out.println(i.equals(l)); // false,类型不同
System.out.println(i.equals(8)); // true,常量8 被认为是int类型
System.out.println(l.equals(8)); // false,int会自动装箱为Integer,不能用equals去和Long比较
System.out.println(l == 8); // true,Long自动拆箱,long可以和int比较

三. hashcode 的使用【重点】

在使用场景方面,针对普通的Java对象,hashcode方法和 == 、equals 有些许差异:

== 用于比较 对象的地址

equals 用于比较 对象的值

hashcode 会计算并返回一个int类型的值,代表 对象 按照一定逻辑计算出的 哈希码值

  1. 如果直接使用 Java默认的 hashcode方法,会返回一个根据对象 内存地址 计算出来的位置,所以这其实还是在比较 对象的地址(类似 ==);
  2. 而 hashcode更大的价值 在于 ⭐ 不使用内存地址做计算时,我们可以重写hashcode方法,制定【对象是否相等】的判定标准,依据为 对象的值(类似 equals);然而 hashcode 的 效率 会比 equals 高,这就是优势所在!(也有致命确缺点,继续向下看👇)
import java.util.Objects;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 重写 hashCode() 方法,根据 name 和 age 属性计算哈希码值
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    // 重写 equals() 方法,根据 name 和 age 属性判断对象是否相等
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null || getClass() != obj.getClass())
            return false;
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }
}

public class Main {
    public static void main(String[] args) {
        Person person1 = new Person("Alice", 30);
        Person person2 = new Person("Bob", 25);
        Person person3 = new Person("Alice", 30);

        // 输出 "哈希码值 person1: -1083240508"
        System.out.println("哈希码值 person1: " + person1.hashCode());
        // 输出 "哈希码值 person2: -1729076834"
        System.out.println("哈希码值 person2: " + person2.hashCode());
        //⭐ true,但是如果没有重写hashcode方法,会返回false!
        System.out.println(person1.hashCode() == person3.hashCode());
    }
}

但是有一个问题:

equals 会按照规则 仔细比较对象是否相同,100%不会误判,绝对可靠;

hashcode的本质是 计算对象的哈希值,由哈希值推断对象的存储位置。所以 查找 和 存储 过程都存在 哈希碰撞 的可能性——“两个对象 算出了 相同的哈希值”,就像 person1.hashCode() == person3.hashCode() 为 true:

⭐ 所以,查找判断过程 不推荐 完全依赖hashcode❌ 它只能作为判断 对象相等性的一种参考,并不是绝对可靠。
person1.hashCode() == person3.hashCode() 为 true是 正确的判断,但是我们不能完全避免小概率事件的发生 —— 两个确实不相同的对象 也算出了 相同哈希值
典例:设哈希函数是 “number % 7”,显然 number为3和10的时候,哈希码相同。

不过,对于 对象 存储 到哈希表 的过程,我们有方法尽可能地优化哈希碰撞 👇

在这里插入图片描述
① 如果是对象值相同,那正中下怀,我们不会在哈希表中存储 值相同的对象节省空间,同时逻辑上也正确 —— 相同对象 存一份就够啦!(person3的内容不会再次存储);

② 如果是不同的对象,那就 必须另寻位置 存储后来者,我们有许多方法去处理这个问题,如链地址法、开放地址法、再哈希等等,在此不赘述。

⭐ 所以在哈希表存储时,我们依赖 hashcode 初步计算确定对象的哈希码,空缺即可存入;但如果哈希码位置被占用,就必须确定 当前对象是否真的相同,这其中就必须使用到 equals方法

所以,hashcode 和 equals方法 需要一起重写,追求性能提升的同时,尽可能保证存储逻辑的正确性。

我们用一个例子强化理解:上述代码中的 person1.hashCode() == person3.hashCode()

①【重写hashcode 重写equals】:true;对象作比较时 速度快❗ 哈希表不会存储多余数据,节省空间逻辑正确 👍

②【不重写hashcode 不重写equals】:false;在自定义类中,hashcode和equals就仅起 == 的作用,这样做的意义不大。

③【不重写hashcode 重写equals】:false;哈希值依旧是按照内存地址来计算,person1和person3都会存储到哈希表中,虽然使用equals判断为true,但是这起不到性能优化的效果。

【重写hashcode 不重写equals】:TRUE;⭐hashcode返回 依据固定逻辑计算出的 对象的 哈希码值,所以结果为TRUE没问题。但是哈希码值并不能代表对象在哈希表中的存储位置❌ 哈希碰撞的时候,默认的equals依据 对象的地址 进行判断,显然这无法有效确认 对象的值 是否相等,导致它们都会存储到哈希表中。节约空间的目的没有实现,判断逻辑的正确性也无从保证❌

以上是hashcode的基础知识,而hashcode 最常见、最常被提及 的场景应该是一些集合类的底层原理,例如 HashMap —— 依照key.hashcode(),不重复的存储对象(存key),随时快速获取对象(找key)……这是源码解析的知识点,此处就不做过多说明了。

四. 总结

【Ⅰ. ==】
① 基本数据类型的值,② 对象的地址;特殊的对象可判值——③ String常量池 + ④ 包装类缓存。

【Ⅱ. equals】
① 对象的值。

【Ⅲ. hashcode】
默认hashcode:对象的地址。
重写hashcode:对象的值 —— 高效但不可靠!合理的哈希函数 和 重写equals 尽量优化 哈希表存储,减少哈希碰撞与误判。

  • 18
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值