Java==equals()和hashCode()的区别

Posted remo0x

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java==equals()和hashCode()的区别相关的知识,希望对你有一定的参考价值。

==

1.用==进行比较的时候,比较的是变量的值。变量有两种类型:

  • 基本类型(int、double等)
  • 引用类型(类、数组等)

2.对于基本类型,比较的是字面量

int a = 12;
int b = 12;
System.out.println(a == b);
# 输出:true

3.对于引用类型,比较的是栈中reference的值

int[] a = 1, 2;
int[] b = 1, 2;
System.out.println(a == b);
# 输出:false

4.对于String的比较有特例,JVM会在常量池(包括静态常量池和运行时常量池)中创建第一次出现的字符串字面量,以后所有使用该字面量的String实际上都是常量池中的引用。而用new创建String的实例时,则变量值是堆上的引用

String a1 = "a";
String a2 = "a";
System.out.println(a1 == a2);
# 输出:true

String a3 = new String("a");
System.out.println(a1 == a3);
# 输出:false

5.对于Integer的比较也有特例。对Integer赋字面量时,编译器会调用valueOf()方法

Integer a = 12;
# 被编译成
Integer a = Integer.valueOf(12);

valueOf()方法的源码如下:

public static Integer valueOf(int i) 
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);

IntegerCache.low=-128,IntegerCache.high>=127(具体值由sun.misc.VM.getSavedProperty(“java.lang.Integer.IntegerCache.high”)决定),所以[-128,127]范围内的Integer值一定会缓存。即[-128,127]范围内的Integer字面量是引用的缓存中的值,如下所示

Integer a1 = 127;
Integer a2 = 127;
System.out.println(a1 == a2);
# 输出:true

Integer a3 = 128;
Integer a4 = 128;
System.out.println(a3 == a4);
# 输出:false

另外把int和Integer进行比较时会对Integer进行拆箱,比如

int a1 = 12;
Integer a2 = new Integer(12);
System.out.println(a1 == a2);
# 输出:true

equals()

equals()方法是Object类中的,所有对象都有这个方法,默认的equals()方法和==是同义的,即判断是否是同一个内存地址

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

源码的注释中对equals()有五点特性要求:

  1. 自反性:对任意引用值x,x.equals(x)的返回值一定为true
  2. 对称性:对于任何引用值x、y,当且仅当y.equals(x)返回值为true时,x.equals(y)的返回值一定为true
  3. 传递性:如果x.equals(y)=true,y.equals(z)=true,则x.equals(z)=true
  4. 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变
  5. 非空性:任何非空的引用值x,x.equals(null)的返回值一定为false

可以覆盖equals()实现自己的相等逻辑,比如常用的String的equals方法:

public boolean equals(Object anObject) 
    if (this == anObject) 
        return 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中equals()方法判断相等的步骤是:

  1. 若指向同一个内存地址,即为同一个String实例,返回true
  2. 如果对象不是String实例,返回false
  3. 如果value长度不一样,返回false
  4. 逐个字符比较,若有不相等字符返回false
  5. 所有字符都相同,返回true

可以从String的equals()方法中总结出覆盖equals()方法的一般形式:

  1. 使用==检查是否指向同一内存地址,如果是则返回true。这是一种性能优化,如果比较操作有可能很昂贵,就值得这么做
  2. 使用instanceof检查对象是否是正确类型,如果不是则返回false。一般来说,“正确类型”是指equals()方法所在的类
  3. 把参数转换成正确的类型。因为转换之前进行过instanceof测试,所以确保会成功
  4. 检查对象中的“关键”域是否对应“相等”。如果这些测试全部成功则返回true,否则返回false
  5. 当写完equals()方法后,检查是否符合上述五点特性

hashCode()

hashCode()方法是Object类中声明的一个native方法,交由C++实现,将对象的内存地址转为int值返回

public native int hashCode();

源码的注释中对hashCode()方法有三点规范:

  1. 在应用程序一次执行中,如果用于equals()方法中的域没有被修改,则无论何时调用hashCode(),对于同一个对象总是返回相同的hash值。从应用程序的一次执行到同一应用程序的另一次执行,该hash值不需要保持一致
  2. 用equals()方法判定相等的两个对象,它们的hashCode()方法也应该返回相同的hash值
  3. 用equals()方法判定不相等的两个对象,它们的hashCode()方法不必返回不同的hash值。不过不同的对象返回不同的hash值可以提高hash表的性能

可以覆盖Object中的hashCode()方法,用于根据对象的内容生成hash值。比如String类的hashCode()方法:

public int hashCode() 
    int h = hash;
    if (h == 0 && value.length > 0) 
        char val[] = value;

        for (int i = 0; i < value.length; i++) 
            h = 31 * h + val[i];
        
        hash = h;
    
    return h;

实现的计算表达式如下所示(s[i]是字符串的第i个字符,n是字符串的长度,^表示求幂。空字符串的hash值是0):

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

对于乘子31的选择,主要有两点原因:

  1. 31是一个不大不小的质数,是作为hashCode()乘子的优选质数之一,可以均匀分布hash值并减少信息丢失
  2. 31可以被JVM优化,31 * i = (i << 5) - i

String的hashCode()方法就是根据value生成hash值,这样不同value的字符串就容易生成不同的hash值,既能减少碰撞又能生成与内容相关的hash值

hashCode()方法主要用于hash表,当集合要添加新元素时,大致按如下步骤:

  1. 先调用该元素的hashCode()方法,直接定位到它应该放置的物理位置上
  2. 如果这个位置上没有元素,就直接存储在这个位置上
  3. 如果这个位置上已经有元素,就调用equals()方法进行比较,相同的话就不存,不相同就进行其它相关操作以存储新元素

所以重写equals()方法时,也必须重写hashCode()方法。如果不这样做,就会违反Object.hashCode()的规范,导致无法结合所有基于hash的集合一起正常运作,这样的集合包括HashMap、HashSet和Hashtable

参考文章

以上是关于Java==equals()和hashCode()的区别的主要内容,如果未能解决你的问题,请参考以下文章

在java中,关于equals(),和hashCode()的重写问题。

java 集合中重写hashCode方法和重写equals方法啥关系?

Java中equals和hashcode的区别?

Java ==,equals() 和hashCode

Java面试题:hashCode() 和 equals()

从语言设计的角度探究Java中hashCode()和equals()的关系