相等判断:“==”

对于基本类型等来说是值的比较,而对于引用类型来说是引用比较(也就是内存地址)

Java中的八大基本数据类型:byte、short、int、long、float、double、char、Boolean

基本类型的一大特点就是它们在栈中是有真实的值的,引用类型则是一串引用地址。

所以当使用“==”比较基本数据类型时比较的是他们在栈内存中的值。

需要注意的是Integer进行自动装箱时如果int的值在-128-127之间,返回的并不是一个新new出来的Integer而是已经缓存到堆内存中的Integer的引用地址。


   int a = 1;
   int b = 1;

   Integer c = 100;//c和c1引用地址相同
   Integer c1 = 100;

   Integer d = 1000;//d和d1引用地址不同
   Integer d1 = 1000;

   String s1 = "王大花";
   String s2 = new String("王大花");    

   System.out.println(a == b);//true
   System.out.println(c == c1);//true
   System.out.println(d == d1);//false
   System.out.println(s1==s2;//false

相等判断“equals”

equals()和 == 有着本质的区别,== 可以看作是对“操作系统比较数据手段”的封装,equals()是Object类中的方法,Object类的equals方法用于判断对象的引用地址是不是相同(即是否为同一对象)


public boolean equals(Object obj) {
    return (this == obj);//判断引用地址是否相同
}

但是所有类都可以重写equals方法,这样我们就可以自定义比较规则了,这也是“equals”和“==”最大的区别了,一个是“死”的一个是“活”的。

String s1 = "王大花";
String s2 = new String("王大花");
System.out.println(s1.equals(s2));//true

还记得之前使用“==”对s1和s2进行等值比较时返回的结果是false,而使用“equals”进行比较得出的结果是true。

要想知道为什么我们得看看String的equals方法的源代码。

public boolean equals(Object anObject) {
    if (this == anObject) {	//先比较两个字符串的引用是否相等(是否指向同一个对象), 是直接返回true
        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() 方法时,首先会判断两个字符串的引用是否相等,也就是说两个字符串引用是否指向同一个对象,若是则返回true。

如果不是指向同一个对象,则把两个字符串中的字符挨个进行比较。由于 s1 和 s2字符串都是 “王大花”,是可以匹配成功的,所以最终返回 true。

hashCode

hashcode方法是Java的java.lang.Object提供的本地方法,这个方法在jvm中实现,它能返回当前对象在内存中地址


Dog dog = new Dog();
System.out.println(dog);//Dog@3b764bce
System.out.println(dog.hashCode());//997608398

如果我们没有重写对象的toString方法的话“@”后面的就是对象的内存地址只不过是16进制的

什么时候需要重写hashCode?

相信大家在写代码的时候经常需要冲写hashCode方法,可为什么要重写hashCode方法呢?总结一句话就是:一个类要想重写equals方法就需要重写hashCode方法

当我们写的自定义类需要判断业务逻辑上是否相等而不是严格意义上的相等时需要重写equals和hashCode方法。

例如:

public class Dog {
    private String name;

    public Dog(String name) {
        this.name = name;
    }
}

Dog dog1 = new Dog("旺财");
Dog dog2 = new Dog("旺财");
System.out.println(dog1.equals(dog2));//false 原生的equals等同于dog1 == dog2

此时dog1和dog2的属性相同我们在逻辑上认为他们是相等,但返回结果是false,显然这时候原生的equals已经不能满足我们的业务需求了,此时我们就需要重写equals方法了,而重写equals方法必定要重写hashCode方法。

为什么重写equals方法时就要重写hashCode方法呢?

我们先来看一下Object.hashCode的通用约定

在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,那么,对该对象调用hashCode方法多次,它必须始终如一地返回 同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同,即这个应用程序这次执行返回的整数与下一次执行返回的整数可以不一致。如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象中任一个对象的hashCode方法必须产生同样的整数结果。

(摘自《Effective Java》第三版)

如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象中任一个对象的hashCode方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。
如果只重写了equals方法而没有重写hashCode方法的话,则会违反约定的第二条:相等的对象必须具有相等的散列码(hashCode)
同时对于HashSet和HashMap这些基于散列值(hash)实现的类。HashMap的底层处理机制是以数组的方法保存放入的数据的(Node<K,V>[] table),其中的关键是数组下标的处理。数组的下标是根据传入的元素hashCode方法的返回值再和特定的值异或决定的。如果该数组位置上已经有放入的值了,且传入的键值相等则不处理,若不相等则覆盖原来的值,如果数组位置没有条目,则插入,并加入到相应的链表中。检查键是否存在也是根据hashCode值来确定的。

equals和hashCode同时存在的意义

equalshashCode都是用来判断两个对象想不想等的,那么问题来了?

为什么需要两个呢?

  • equals – 保证比较对象是否是绝对相等的
  • hashCode – 保证在最快的时间内判断两个对象是否相等,可能有误差值

一个是保证可靠,一个是保证性能。也就是说:

  • 同一个对象的hashCode一定相等,不同对象的hashCode也可能相等,这是因为hashCode是根据地址hash出来的一个int 32 位的整型数字,相等是在所难免。
  • equals比较的是两个对象的地址,同一个对象地址肯定相同,不同的对象地址一定不同,可靠性是这么来的。

就像HashMap里面插入时判断:


if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))

判断两个key是否相同逻辑

  1. 先比较 hash (通过hashCode的高16位和低16位进行异或运算得出的) ,因为两个相同的对象hash值一定相等。
  2. 再比较两个对象的地址是否相同,**== 判断是否绝对相等,而equals判断是否客观相等**。

下面分析一下简单的不重写hashCode的后果和在存到HashMap中可能出现的后果

不重写会怎样?
  • 无论是Effective Java,还是阿里巴巴Java规范手册都是要求重写equals,必须重写hashCode。
  • 两个相等的对象必须具有相等的散列码(Java关键约定)

那么不重写的后果是什么呢?

举一个例子:

如果一个只重写了equals(比较所有属性是否相等)的类 new 出了两个属性相同的对象。这时可以得到的信息是这个属性相同的对象地址肯定不同,但是equals是true,hashCode返回的是不相等的(一般不会出现hash碰撞)。

也就是说这个类对象违背了Java对于两个对象相等的约定。违背约定的原因是 可靠的equals判断两个对象是相等的,但是他们两个的散列码确是不相等的。

总结来说:

  • equals 为 true , hashCode 必须相等
  • hashCode 相等时 , equals 可以不用为 true (也就是hash碰撞的时候)

所以如果不重写hashCode的话,可能导致HashSet、HashMap不能正常的运作。

如果我们将某个自定义对象存到HashMap或者HashSet及其类似实现类中的时候,如果该对象的属性参与了hashCode的计算,那么就不能修改该对象参数hashCode计算的属性了。有可能会移除不了元素,导致内存泄漏。

发表回复

后才能评论

评论(2)