java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java == equals hashCode

Java中的==、equals与hashCode区别与联系最佳实践

作者:猿究院-陆昱泽

在Java开发中,==、equals和hashCode是三个高频出现的概念,也是初学者最容易混淆的知识点,本文将从底层原理出发,全面解析三者的区别、联系及最佳实践,帮你彻底理清它们的使用场景,感兴趣的朋友一起看看吧

在Java开发中,==equalshashCode是三个高频出现的概念,也是初学者最容易混淆的知识点。它们看似简单,却蕴含着Java对象模型和哈希表设计的深层逻辑。本文将从底层原理出发,全面解析三者的区别、联系及最佳实践,帮你彻底理清它们的使用场景。

一、==:比较的是"身份"还是"内容"?

==是Java中的运算符,用于比较两个变量的值。但它的行为会因比较的类型不同而产生差异,核心区别在于基本数据类型引用数据类型的比较逻辑。

1. 基本数据类型:比较"值"本身

Java中的基本数据类型(byteshortintlongfloatdoublecharboolean)直接存储值,不存在"引用"的概念。因此,==比较的是两个变量存储的实际值是否相等

int a = 10;
int b = 10;
System.out.println(a == b); // true(值相同)
char c1 = 'A';
char c2 = 65; // 'A'的ASCII码是65
System.out.println(c1 == c2); // true(值相同)

2. 引用数据类型:比较"内存地址"

引用数据类型(如StringObject、自定义类等)的变量存储的是对象在堆内存中的地址(引用)。因此,==比较的是两个变量是否指向同一个对象(即内存地址是否相同)。

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false(两个不同的对象,地址不同)
String s3 = s1;
System.out.println(s1 == s3); // true(s3和s1指向同一个对象)

关键结论
==对于基本类型是"值比较",对于引用类型是"地址比较"(判断是否为同一对象)。

二、equals:对象内容的比较器

equalsObject类定义的方法,用于判断两个对象是否"相等"。与==不同,equals的逻辑可以由开发者自定义,默认行为与==一致,但多数类会重写它以实现"内容比较"。

1. Object类的默认equals实现

Object类中equals的源码如下:

public boolean equals(Object obj) {
    return (this == obj); // 本质是用==比较,即比较内存地址
}

这意味着:如果一个类没有重写equals,那么它的equals方法与==完全等价,比较的是对象的内存地址。

class Person {
    private String name;
    // 省略构造方法和getter
}
Person p1 = new Person("张三");
Person p2 = new Person("张三");
System.out.println(p1.equals(p2)); // false(未重写equals,等价于==)

2. 重写equals:实现"内容相等"

实际开发中,我们通常认为"内容相同的对象应该相等"(如两个String的字符序列相同即为相等)。因此,许多Java内置类(如StringIntegerList)都重写了equals方法。

例1:String类的equals实现

String重写的equals用于比较字符序列是否相同:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true; // 同一对象,直接返回true
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) { // 逐个比较字符
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

使用示例:

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1.equals(s2)); // true(内容相同)
System.out.println(s1 == s2);      // false(地址不同)

例2:自定义类重写equals

重写equals需遵循等价关系的规则(否则会导致逻辑混乱):

正确重写Person类的equals示例:

class Person {
    private String name;
    private int age;
    // 构造方法、getter省略
    @Override
    public boolean equals(Object o) {
        if (this == o) return true; // 同一对象,直接返回true
        if (o == null || getClass() != o.getClass()) return false; // 类型不同或null,返回false
        Person person = (Person) o;
        // 比较关键属性(name和age都相同才认为相等)
        return age == person.age && Objects.equals(name, person.name);
    }
}

关键结论
equals的默认行为是比较对象地址(与==一致),但可通过重写实现"内容比较",其逻辑由开发者定义(通常基于对象的关键属性)。

三、hashCode:哈希表的"定位器"

hashCodeObject类的另一个方法,返回一个int类型的哈希码(散列值)。它的核心作用是辅助哈希表(如HashMapHashSet)快速定位对象,是哈希表高效运作的基础。

1. hashCode的本质与作用

哈希码是对象的"数字指纹",由对象的内部状态计算得出。在哈希表中,它的作用是:

Object类中hashCode的默认实现是根据对象的内存地址计算哈希码(不同对象的哈希码通常不同),但子类可以重写它。

2. 重写hashCode的规则

equals类似,hashCode也需要遵循一定的规则,尤其是当类重写了equals时:

  1. 一致性:同一对象多次调用hashCode(),必须返回相同的整数(对象状态未修改时);
  2. 等价性:若a.equals(b) == true,则a.hashCode()必须等于b.hashCode()
  3. 非必须等价:若a.equals(b) == falsea.hashCode()b.hashCode()可以相等(即允许哈希冲突)。

为什么规则2如此重要?
如果两个对象equals返回truehashCode不同,在哈希表中会被分配到不同的桶位,导致哈希表认为它们是不同的对象,从而破坏哈希表的逻辑(如HashSet中出现重复元素)。

3. 如何正确重写hashCode?

重写hashCode的核心原则是:根据equals中用于比较的所有属性计算哈希码,确保"相等的对象有相同的哈希码"。

Java提供了Objects.hash()工具方法,可便捷地生成哈希码(内部通过组合各属性的哈希值实现)。

为前面的Person类重写hashCode

@Override
public int hashCode() {
    // 基于name和age计算哈希码(与equals中比较的属性一致)
    return Objects.hash(name, age);
}

Objects.hash()的简化原理:

public static int hash(Object... values) {
    int result = 1;
    for (Object element : values) {
        result = 31 * result + (element == null ? 0 : element.hashCode());
    }
    return result;
}

选择31作为乘数的原因:31是质数,且31 * i = (i << 5) - i,可通过移位运算高效计算。

四、==、equals与hashCode的关联关系

三者并非孤立存在,尤其是equalshashCode,在哈希表场景中存在强关联,我们可以用一句话总结:

==判断是否为同一对象;equals判断内容是否相等;hashCode辅助哈希表快速查找,且必须与equals保持逻辑一致。

具体关联如下:

1. ==与equals的关系

例如:

String s1 = "hello";
String s2 = "hello"; // 常量池复用,s1和s2指向同一对象
System.out.println(s1 == s2);      // true
System.out.println(s1.equals(s2)); // true
String s3 = new String("hello");
System.out.println(s1 == s3);      // false(不同对象)
System.out.println(s1.equals(s3)); // true(内容相同)

2. equals与hashCode的强制约束

这是开发中最容易出错的点,必须牢记:

反例(违反约束会导致的问题):

class BadPerson {
    private String name;
    public BadPerson(String name) { this.name = name; }
    // 只重写equals,未重写hashCode
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BadPerson badPerson = (BadPerson) o;
        return Objects.equals(name, badPerson.name);
    }
}
// 测试代码
public class Test {
    public static void main(String[] args) {
        BadPerson p1 = new BadPerson("张三");
        BadPerson p2 = new BadPerson("张三");
        System.out.println(p1.equals(p2)); // true(内容相同)
        System.out.println(p1.hashCode() == p2.hashCode()); // false(哈希码不同,违反约束)
        // 放入HashSet
        Set<BadPerson> set = new HashSet<>();
        set.add(p1);
        set.add(p2);
        System.out.println(set.size()); // 2(错误!因为p1和p2应该被视为相同元素)
    }
}

原因:HashSet判断元素是否重复时,先通过hashCode定位桶位,再用equals比较。由于p1p2哈希码不同,会被放入不同桶位,equals即使返回true也不会被视为重复元素。

五、常见误区与最佳实践

了解了三者的原理后,我们需要规避一些常见错误,掌握实际开发中的最佳实践。

1. 误区1:用==比较字符串内容

很多初学者会犯这样的错误:

String s1 = "hello";
String s2 = new String("hello");
if (s1 == s2) { ... } // 错误!此处比较的是地址,而非内容

正确做法:字符串内容比较必须用equals

if (s1.equals(s2)) { ... } // 正确,比较内容
// 避免空指针异常的写法(当s1可能为null时)
if (Objects.equals(s1, s2)) { ... }

2. 误区2:重写equals时不重写hashCode

如前文反例所示,这会导致哈希表(HashMapHashSet等)工作异常。牢记:重写equals必须同时重写hashCode,且两者基于相同的属性计算。

3. 误区3:认为hashCode相等的对象一定相等

哈希码相等只是"可能相等",而非"一定相等"。例如:

// 两个不同的字符串,可能有相同的哈希码(哈希冲突)
String str1 = "Aa";
String str2 = "BB";
System.out.println(str1.hashCode()); // 2112
System.out.println(str2.hashCode()); // 2112
System.out.println(str1.equals(str2)); // false

因此,在哈希表中,hashCode仅用于初步筛选,最终必须通过equals确认是否相等。

4. 最佳实践总结

场景

正确做法

错误做法

比较基本类型值

使用==

试图用equals(基本类型没有该方法)

比较引用类型内容

使用equals(需确保已重写)

使用==(比较地址而非内容)

重写equals

同时重写hashCode,基于相同属性计算

只重写equals,忽略hashCode

避免equals空指针异常

使用Objects.equals(a, b)

直接调用a.equals(b)(当a可能为null时)

判断对象是否为同一实例

使用==

使用equals(可能被重写,结果不可靠)

六、总结

==equalshashCode是Java对象比较的三大核心工具,它们的设计体现了Java对"身份"与"内容"的严格区分,以及哈希表高效运作的底层逻辑:

到此这篇关于Java中的==、equals与hashCode区别与联系最佳实践的文章就介绍到这了,更多相关java == equals hashCode内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文