Java技术分享

剖析equals方法

1、对于Object来说,其equals()方法底层实现就是"==",都是比较对象的引用是否相等,下为JDK源码。

java 复制代码
Object c = 1;
Object d = 1;
boolean equals = c.equals(d);

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

2、在JDK中其他类中通常会重写equals()方法,例如:Integer、String,源码如下。

Integer会先将Integer对象转换成基础类型int值来比较,所以此时就不再是比较两个对象引用,是比较两个对象的值是否相等。

java 复制代码
Integer a = 1;
Integer b = 1;
boolean equals = a.equals(b);

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

@Override
public int hashCode() {
    return Integer.hashCode(value); // 由于 Integer 是不可变类,其 hashCode 就是整数值本身。
}

String和Integer一样,引用比较重写成了值比较了。

java 复制代码
String a = "a";
String b = "a";
boolean equals = a.equals(b);

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true; // 引用相同返回 true,引用相同,那么值肯定相同了
    }
    return (anObject instanceof String aString)
            && (!COMPACT_STRINGS || this.coder == aString.coder)
            && StringLatin1.equals(value, aString.value); // equals 为下面的 equals 方法
}

@IntrinsicCandidate
public static boolean equals(byte[] value, byte[] other) {
    if (value.length == other.length) {
        for (int i = 0; i < value.length; i++) { // 循环每个字符对比,本质是值比较
            if (value[i] != other[i]) {
                return false;
            }
        }
        return true;
    }
    return false;
}

// JDK17
public int hashCode() {
    int h = hash;  // 缓存的 hash 值
    if (h == 0 && !hashIsZero) {
        h = isLatin1() ? StringLatin1.hashCode(value)
                                : StringUTF16.hashCode(value);
        if (h == 0) {
            hashIsZero = true; // 标记 hashCode 为 0 的情况
        } else {
            hash = h; // 缓存 hashCode
        }
    }
    return h;
}

static int hashCode(byte[] value) {
    int h = 0;
    for (int i = 0; i < value.length; i++) {
        h = 31 * h + (value[i] & 0xff);
    }
    return h;
}

static int hashCode(byte[] value) {
    int h = 0;
    for (int i = 0; i < value.length; i += 2) {
        h = 31 * h + getChar(value, i);
    }
    return h;
}

static char getChar(byte[] val, int index) {
    return (char) (((val[index] & 0xff) << 8) | (val[index + 1] & 0xff));
}

JDK17对String的hashcode()方法与早期版本相比进行了优化,主要是为了更好地适应 String 的内部存储机制的变化以及性能提升。从 JDK9开始,String 使用了一种称为 Compact Strings 的机制。即:String 内部不再总是使用 char[] 存储,而是根据字符内容选择 byte[] 存储。如果所有字符是 Latin-1(单字节字符,0~255),则使用 byte[],以节省内存。如果有非 Latin-1 字符(需要双字节存储),则仍使用 UTF-16 编码。

1、hash :用于缓存计算过的 hashCode,避免重复计算。

2、hashIsZero:标志变量,处理特殊情况:

  • 如果字符串的 hashCode 计算结果为 0,会将 hashIsZero 标记为 true,防止后续重复计算。
  • 区分未计算和计算后结果为 0 的情况。
  • isLatin1() :判断字符串是否为 Latin-1 编码。
    • 如果是 Latin-1,则调用 StringLatin1.hashCode(value)
      • Latin-1 的 hashCodeStringLatin1.hashCode): 遍历 byte[] 数组,使用与原始实现相同的算法(31 为乘数)。
    • 否则调用 StringUTF16.hashCode(value)
      • UTF-16 的 hashCodeStringUTF16.hashCode): 遍历 byte[] 数组(每两个字节表示一个 char),计算哈希值。

JDK17的优点

支持 Compact Strings

  • 根据字符编码选择不同的计算方式,提高内存效率和性能。

性能优化

  • 使用 hashIsZero 减少重复计算。
  • 针对 Latin-1 和 UTF-16 分开处理,减少不必要的判断和操作。

兼容性

  • 虽然内部实现发生了变化,但对外部来说行为与早期版本保持一致。

同时上述说了重写equals()方法,还需要重写hashCode 方法。

根据 Object 类的规范:

如果两个对象根据 equals 方法比较是相等的,那么它们的 hashCode 值也必须相等。

原因:

如果只重写了 equals 而没有重写 hashCode,会导致在使用哈希表(如 HashMapHashSet)时出现问题,因为这些集合依赖于对象的 hashCode 值来确定存储位置。

相关推荐
翀哥~2 分钟前
python VS c++
开发语言·c++·python
《源码好优多》8 分钟前
基于Java Springboot汽配销售管理系统
java·开发语言·spring boot
小林想被监督学习33 分钟前
Java后端如何进行文件上传和下载 —— 本地版
java·开发语言
Erosion202042 分钟前
SPI机制
java·java sec
猪猪虾的业余生活44 分钟前
matlab实现,数据曲线毛刺光滑
开发语言·matlab
HABuo1 小时前
【数据结构与算法】合并链表、链表分割、链表回文结构
c语言·开发语言·数据结构·c++·学习·算法·链表
逸风尊者1 小时前
开发也能看懂的大模型:RNN
java·后端·算法
望获linux1 小时前
在 ARM 平台上如何实现Linux系统的1秒启动
linux·服务器·开发语言·数据库·操作系统·嵌入式操作系统·arm平台
带多刺的玫瑰1 小时前
Leecode刷题C语言之网络延迟时间
c语言·开发语言·算法
尘浮生1 小时前
Java项目实战II基于Java+Spring Boot+MySQL的智能停车计费系统(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·mysql·微信小程序·maven