Java深入理解==和equals和hashcode的用法和区别

在查看本文章之前先做个小练习

问题1:

查看程序, s1 == s2 的值为true还是fasle?

 String s1 = "abc";
 String s2 = "abc";
 System.out.println(s1 == s2);

答案为:true

问题2:

查看一下程序, s1 == s2 的值为true还是fasle?

 String s1 = "abc";
 String s2 = "ab"+"c";
 System.out.println(s1 == s2);

答案为:true

问题3:

查看以下程序, s1 == s2 的值为true还是fasle?

 String s1 = "abc";
 String s2 = "ab";
 s2 = s2+"c";
 System.out.println(s1 == s2);

答案是:false

问题4:

查看以下程序,s1.equals(s2)的值为true还是false?

 String s1 = "abc";
 String s2 = "ab";
 s2 = s2+"c";
 System.out.println(s1.equals(s2));

答案为:true

剖析:

单个"abc"和"ab"+"c"常亮运算都会在常亮池里面存储和创建一个引用对象,然后s1和s2都指向常亮池里面变量的地址而已.

而 s2+"c" 是对象和常亮的运算,是因为什么呢?

在我们使用 javap -v xxx.class 查看字节码的时候发现

         13: invokespecial #14                 // Method java/lang/StringBuilder."<init>":()V
         16: ldc           #15                 // String s1 =
         18: invokevirtual #16                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
         21: aload_1
 ​

当我们的对象和常亮进行运算时,会先创建一个StringBuilder,此时s2的地址肯定会发生改变,以及 == 是比较对象的地址,则问题3的值为false.

equals后值为true是因为,String底层重写了Object的equals()方法.

扩展:

   String a = new String("ab"); // a 为一个引用
   String b = new String("ab"); // b为另一个引用,对象的内容一样
   System.out.println(a==b);

自己可以试着分析一波!

Java中 ==

==

于基本数值来说: 比较的是值

于引用对象来说: 比较的是对象地址

因为 Java 只有传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

equals()方法

equals()方法在Object类中,而Object类是所有类的直接或间接父类,故所有的类都拥有equals()方法.

equals()方法不能作用在基本数据类型的变量,只能用来判断俩个对象是否相等,可以自定义equals()方法.

Object中的equals()方法

     public boolean equals(Object obj) {
         return (this == obj);
     }

默认比较的是地址,类似于==.

hashCode()方法

介绍: hashCode()方法的作用是返回对象哈希码,也称为散列码;他实际上是返回一个int正整数,支持此方法是为了有利于哈希表,例如java.util.HashMap(来自于源码注释),哈希码确定该对象在哈希表中的索引位置.

和equals()方法一样,都来自Object类,故所有的类都拥有此方法!!

查看Object类对hashCode()的定义:

     public native int hashCode();

可以发现他是一个本地方法,也就是用C语言或者C++实现的.

要求(来源于jdk源码):

  1. 如果根据equals()方法俩个对象相等,则对俩个对象中的每一个调用hashCode()方法必须产生相同的整数结果

  2. 在Java应用程序执行期间,只有在同一个对象上多次调用它,hashCode()方法必须始终返回相同的整数,前提是在对象的equals()比较中使用的信息没有被修改.该整数不需要从应用程序的一次执行到同一应用的另一次执行保持一致.

  3. 此条不是必须的:如果俩个对象根据不相等的equals(Object)方法,然后调用hashCode()在各俩个对象的方法必须产生不同的整数结果,(不同对象的hashCode值可以相同),但是,程序员应该意识到为不相等的对象生成不同的整数结果可能会提高哈希表的性能(尽可能的保证每个对象的hashCode值不同,因为可以提升hash表的性能)

扩展:

散列表存储的是键值对(key-value),他的特点是:能根据'键'快速的检索出对应的值.这其中就利用了散列码!(可以快速找到所需要的的对象)

为什么重写 equals 时必须重写 hashCode 方法?

如果两个对象相等,则 hashcode 一定也是相同的。两个对象相等,对两个对象分别调用 equals 方法都返回 true。但是,两个对象有相同的 hashcode 值,它们也不一定是相等的 。因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖。

hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?

因为 hashCode() 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode

我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。

Java对象的内存地址与hashcode值关系

记住一个观点: 内存地址与hashcode完全不同,没有可比性

为了比较Java对象的内存地址与hashcode的关系,必须想办法获取这两个值

Java不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe类提供了硬件级别的原子操作,在java中内存中的对象地址是可变的,所以获得的内存地址有可能会变化。要获得内存地址也只能通过Unsafe的方法来获得,具体下看的代码

 package com.fagejiang.test;
 ​
 import sun.misc.Unsafe;
 ​
 import java.lang.reflect.Field;
 ​
 /**
  * 内存地址获取
  *
  * @author 发哥讲Java
  * @version 1.0
  * @date 2021-06-08 18:01
  */
 public class MemoryAddressTest {
     private static Unsafe unsafe;
 ​
     static {
         try {
             Field field = Unsafe.class.getDeclaredField("theUnsafe");
             field.setAccessible(true);
             unsafe = (Unsafe) field.get(null);
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 ​
     public static long addressOf(Object o) throws Exception {
         Object[] array = new Object[]{o};
         long baseOffset = unsafe.arrayBaseOffset(Object[].class);
         //arrayBaseOffset方法是一个本地方法,可以获取数组第一个元素的偏移地址
         int addressSize = unsafe.addressSize();
         long objectAddress;
         switch (addressSize) {
             case 4:
                 objectAddress = unsafe.getInt(array, baseOffset);
                 //getInt方法获取对象中offset偏移地址对应的int型field的值
                 break;
             case 8:
                 objectAddress = unsafe.getLong(array, baseOffset);
                 //getLong方法获取对象中offset偏移地址对应的long型field的值
                 break;
             default:
                 throw new Error("unsupported address size: " + addressSize);
         }
         return (objectAddress);
     }
 ​
     public static void main(String... args) throws Exception {
         String str = "Hello world";
         Object mine = str.toCharArray(); //先把字符串转化为数组对象
         long address = addressOf(mine);
         System.out.println("str的内存地址: " + address);
         System.out.println("str的hashcode: " + str.hashCode());
         // Verify address works - should see the characters in the array in the output
         printBytes(address, 27);
     }
 ​
     public static void printBytes(long objectAddress, int num) {
         for (long i = 0; i < num; i++) {
             int cur = unsafe.getByte(objectAddress + i);
             System.out.print((char) cur);
         }
         System.out.println();
     }
 }
 ​

输出如下:

 已连接到目标 VM, 地址: ''127.0.0.1:61904',传输: '套接字''
 str的内存地址: 3983691075
 str的hashcode: -832992604
 #
 # A fatal error has been detected by the Java Runtime Environment:
 #
 #  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000006fa1781f, pid=20360, tid=0x00000000000055d4
 #
 # JRE version: Java(TM) SE Runtime Environment (8.0_201-b09) (build 1.8.0_201-b09)
 # Java VM: Java HotSpot(TM) 64-Bit Server VM (25.201-b09 mixed mode windows-amd64 compressed oops)
 # Problematic frame:
 # V  [jvm.dll+0x1e781f]
 #
 # Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
 #
 # An error report file with more information is saved as:
 # D:\development\workspace\fagejiang\talk_about_fage\hs_err_pid20360.log
 #
 # If you would like to submit a bug report, please visit:
 #   http://bugreport.java.com/bugreport/crash.jsp
 #
 与目标 VM 断开连接, 地址为: ''127.0.0.1:61904',传输: '套接字''
 ​
 进程已结束,退出代码为 1
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

航迹者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值