Java 中的 == 运算符、equals 方法和 hashCode 方法

简介: HashMap 中键是不可以重复的,因此它的键就必须是不同的对象,那么这个时候就先用计算速度快的 hashCode 进行比较,若哈希值都不相等,那么这两个对象必然不相等,若是相等的,那么这个就有两种可能出现,一种情况是这两个对象是真的相等,另外一种情况就是出现了罕见的“哈希冲突”现象,那么这个时候就轮到 equals 来进行判断了!在 HashMap 中,键是不可以重复的,也就是说,它们的键都是不相同的,因此就要判断不同对象是否为同一对象。因此,哈希值可以用于判断两个对象是否不等。

一、== 运算符

== 是 Java 中的一个运算符,用于比较两个对象,但在比较两个对象的时候需要根据比较类型分情况进行讨论。

1.1 基本数据类型与基本数据类型

基本数据类型之间通过 == 进行比较的时候,是直接比较它们的大小,而与它们的具体类型无关。

shortnum1=20000;
intnum2=20000;
System.out.println(num1==num2); // Output: true

image.gif

1.2 引用类型与引用类型

引用类型之间是比较它们的内存地址,因此两个同类型的对象通过 == 进行比较时的结果一般为 false。

publicclassTest {
publicstaticvoidmain(String[] args) {
Objo1=newObj();
Objo2=newObj();
System.out.println(o1==o2); // Output: false    }
}
classObj {}

image.gif

但也不是所有情况都会返回 false,基本数据类型的引用类型有个常量池的机制,使得它们就算是两个对象,依然会指向同一片内存空间,也就是说,它们依然是同一个对象。

Integernum1=100, num2=100;
System.out.println(num1==num2); // Output: true

image.gif

具体内容请见:八大基本类型及其包装类型

1.3 基本数据类型与引用类型

当基本数据类型与引用类型之间进行比较时,若引用类型是数字类的,那么就可以进行比较,其他类型的无法进行比较,会报错。

Integernum1=200;
intnum2=200;
System.out.println(num1==num2); // Output: trueintnum=200;
Stringstr="123";
System.out.println(str==num); // Error: 二元运算符 '==' 的操作数类型错误

image.gif

二、equals 方法

equals 是 Java 引用类型的一个基本方法,在没有对其进行重写(override)的情况下,equals 方法默认是比较两个对象的内存地址,重写之后视具体情况而定。

Stringstr1="1";
Stringstr2="2";
System.out.println(str1.equals(str2)); // Output: false

image.gif

2.1 equals 方法和 == 运算符的区别

    • == 是一个运算符,equals 是对象的方法(所以基本数据类型没有);
    • == 在比较基本数据类型时,是比较的值,比较对象的时候,是比较其内存地址;
    • equals 默认(没有被重写)是比较对象的内存地址,重写后是其他的内容(如 String 就是其对象所表示内容的值)

    三、hashCode 方法

    hashCode 是一个非常特殊的方法,它涉及到散列表的知识,哈希(hash)翻译过来就是散列的意思,它是一种算法,可以快速将任意对象转换为一个 16 进制的数值,而不同的对象之间,通过哈希算法得到的哈希值大概率是不会相同的,有极小概率会相同,此时就出现了一种被称为“哈希冲突”的情况,这里我们不对其做深究,具体内容请参见:哈希算法

    总之,在没有出现“哈希冲突”的情况下,我们就可以通过不同对象的哈希值不相同这一特性来判断两个对象是否为同一对象。

    Stringstr1="1";
    Stringstr2="2";
    System.out.println(str1.hashCode() ==str2.hashCode()); // Output: false

    image.gif

    当然,一般情况下我们是不会像上面这样用 hashCode 这一方法的,因为无法保证不会出现“哈希冲突”的情况。虽然不同对象的哈希值有可能相同,但是两个对象的哈希值的不同则说明它们一定不相同!因此,哈希值可以用于判断两个对象是否不等。

    四、区别和联系

    4.1 区别

    == 一般用来判断数字是否相等,equals 一般用来判断两个对象是否相等,而 hashCode 一般用来判断两个对象是否不等。

    == 只比较数字,判断速度最快,此外是 hashCode 方法,因为哈希值的计算十分快速,最后是 equals,默认情况下 equals 是比较两个对象的内存地址,这一过程虽然很快速,但更一般的情况是 equals 方法被重写了,比较的是两个非数字类型对象的属性值,因此它最慢。

    Stringstr1="1";
    Stringstr2="1";
    System.out.println(str1.equals(str2)); // Output: true

    image.gif

    上面的代码中,Java 内置引用类型 String 就重写了 equals 方法,它比较的是两个 String 对象的内容,而非内存地址。

    4.2 联系

    == 一般和 equals 和 hashCode 扯不上什么关系,从某种角度上来说,equals 和 hashCode 之间也没有必然联系。但是 Java 中存在一种数据结构,将 equals 和 hashCode 联系了起来,那就是 HashMap 及其子类。

    HashMap 中键是不可以重复的,因此它的键就必须是不同的对象,那么这个时候就先用计算速度快的 hashCode 进行比较,若哈希值都不相等,那么这两个对象必然不相等,若是相等的,那么这个就有两种可能出现,一种情况是这两个对象是真的相等,另外一种情况就是出现了罕见的“哈希冲突”现象,那么这个时候就轮到 equals 来进行判断了!这样一来,就高效且快速地解决了键不可重复的问题。

    从上面 HashMap 的比较中我们也可以看到,当我们自定义类型的时候,若要重写 equals 方法或者 hashCode 方法,请将它们两个同时进行重写,因为 Java 内置数据结构 HashMap 等判断键是否重复是需要同时调用它们两个。

    综上,我们可以总结为下表:

    比较方式 == 运算符 equals 方法 hashCode 方法
    比较速度 快速 默认情况下快速,一般情况下缓慢 中等
    比较值 数值 默认是内存地址,一般是对象属性 哈希值

    4.3 经典问题

    为什么重写了 hashCode 方法还要重写 equals 方法?如果只重写 equals 方法而不重写 hashCode 方法会有问题吗?在 HashMap 中怎样体现出来的?

    在 HashMap 中,键是不可以重复的,也就是说,它们的键都是不相同的,因此就要判断不同对象是否为同一对象。为了解决这个问题,HashMap 中同时调用了对象的 equals 方法和 hashCode 方法来进行判断。判断的部分源代码是这样的:

    if (e.hash==hash&&    ((k=e.key) ==key|| (key!=null&&key.equals(k))))
    returne;

    image.gif

    hashCode 方法和 equals 方法都可以用来比较两个对象,但是二者有一些区别:

    比较方式 equals 方法 hashCode 方法
    比较速度

    默认情况比较内存地址,速度快;

    重写后一般比较内容,速度慢

    计算哈希值,速度快
    比较值 内存地址或者内容(属性) 哈希值

    因此先用计算速度快的 hashCode 进行比较,这可以解决大部分问题,但由于可能出现“哈希冲突”,于是还需要 equals 方法解决。

    正因为在判断键是否重复这一问题需要同时调用 equals 方法和 hashCode 方法,故我们自己定义的类中重写它们时必须两个一起重写。若只重写 equals 方法,而不重写 hashCode 方法,那么创建两个内容一样但内存地址不同的对象并存储在 HashMap 中时,会被当成两个键,而不是一个键,进而引发其他错误。

    目录
    相关文章
    |
    1天前
    |
    存储 Java
    |
    2天前
    |
    存储 Java
    |
    4天前
    |
    Java API
    Java8 Lambda 设计和实现问题之在Java 8的Stream API中,parallel=false时collect方法是如何实现的
    Java8 Lambda 设计和实现问题之在Java 8的Stream API中,parallel=false时collect方法是如何实现的
    |
    4天前
    |
    Java Spring 容器
    Java SpringBoot 中,动态执行 bean 对象中的方法
    Java SpringBoot 中,动态执行 bean 对象中的方法
    14 0
    |
    存储 Java 程序员
    面试点:Java 中 hashCode() 和 equals() 的关系
    面试点:Java 中 hashCode() 和 equals() 的关系
    178 0
    |
    存储 Java 程序员
    面试点:Java 中 hashCode() 和 equals() 的关系
    面试点:Java 中 hashCode() 和 equals() 的关系
    139 0
    |
    存储 Java 程序员
    面试点:Java 中 hashCode() 和 equals() 的关系
    Java 中 hashCode() 和 equals() 的关系是面试中的常考点,如果没有深入思考过两者设计的初衷,这个问题将很难回答。除了应付面试,理解二者的关系更有助于我们写出高质量且准确的代码。
    |
    6天前
    |
    Java 开发者
    奇迹时刻!探索 Java 多线程的奇幻之旅:Thread 类和 Runnable 接口的惊人对决
    【8月更文挑战第13天】Java的多线程特性能显著提升程序性能与响应性。本文通过示例代码详细解析了两种核心实现方式:Thread类与Runnable接口。Thread类适用于简单场景,直接定义线程行为;Runnable接口则更适合复杂的项目结构,尤其在需要继承其他类时,能保持代码的清晰与模块化。理解两者差异有助于开发者在实际应用中做出合理选择,构建高效稳定的多线程程序。
    27 7
    |
    5天前
    |
    安全 Java 数据库
    一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
    这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
    |
    5天前
    |
    存储 监控 安全
    一天十道Java面试题----第三天(对线程安全的理解------>线程池中阻塞队列的作用)
    这篇文章是Java面试第三天的笔记,讨论了线程安全、Thread与Runnable的区别、守护线程、ThreadLocal原理及内存泄漏问题、并发并行串行的概念、并发三大特性、线程池的使用原因和解释、线程池处理流程,以及线程池中阻塞队列的作用和设计考虑。