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 中时,会被当成两个键,而不是一个键,进而引发其他错误。

    目录
    相关文章
    |
    28天前
    |
    消息中间件 Java Kafka
    在Java中实现分布式事务的常用框架和方法
    总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
    |
    15天前
    |
    存储 Java C++
    java中“==”和equals,究竟比的是什么
    java中“==”和equals,究竟比的是什么
    50 4
    |
    26天前
    |
    安全 Java 开发者
    Java中WAIT和NOTIFY方法必须在同步块中调用的原因
    在Java多线程编程中,`wait()`和`notify()`方法是实现线程间协作的关键。这两个方法必须在同步块或同步方法中调用,这一要求背后有着深刻的原因。本文将深入探讨为什么`wait()`和`notify()`方法必须在同步块中调用,以及这一机制如何确保线程安全和避免死锁。
    37 4
    |
    26天前
    |
    Java
    深入探讨Java中的中断机制:INTERRUPTED和ISINTERRUPTED方法详解
    在Java多线程编程中,中断机制是协调线程行为的重要手段。了解和正确使用中断机制对于编写高效、可靠的并发程序至关重要。本文将深入探讨Java中的`Thread.interrupted()`和`Thread.isInterrupted()`方法的区别及其应用场景。
    27 4
    |
    24天前
    |
    Java 数据处理 数据安全/隐私保护
    Java处理数据接口方法
    Java处理数据接口方法
    25 1
    |
    Java
    JAVA方法的定义
    JAVA方法的定义
    96 0
    |
    6月前
    |
    安全 Java 编译器
    杭州 【Java基础知识 11】java泛型方法的定义和使用(学习+改进+自己理解,想法) (借鉴-侵-删)
    杭州 【Java基础知识 11】java泛型方法的定义和使用(学习+改进+自己理解,想法) (借鉴-侵-删)
    43 1
    |
    7月前
    |
    存储 Java
    Java数组与带参数方法:定义、调用及实践
    Java数组与带参数方法:定义、调用及实践
    80 1
    |
    7月前
    |
    存储 Java
    Java中带返回值方法的定义与调用技术
    Java中带返回值方法的定义与调用技术
    107 1
    |
    7月前
    |
    Java
    Java一分钟之-方法定义与调用基础
    【5月更文挑战第8天】本文介绍了Java编程中的方法定义和调用,包括基本结构、常见问题和避免策略。方法定义涉及返回类型、参数列表和方法体,易错点有返回类型不匹配、参数错误和忘记返回值。在方法调用时,要注意参数传递、静态与非静态方法的区分,以及重载方法的调用。避免错误的策略包括明确返回类型、参数校验、理解值传递、区分静态和非静态方法以及合理利用重载。通过学习和实践,可以提升编写清晰、可维护代码的能力。
    238 0