equals和==的区别以及它的衍生

#记录

equals 和 ==

  • equals 与 == 的区别
  • String str1 = new String(“aaa”);
  • String str2 = new String(“aaa”);
  • System.out.println(str1.equals(str2));// 类覆盖了equals方法,返回为true
  • equals()通过其它方式比较两个对象是否相等。通常的做法是:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle
  • System.out.println(str1 == str2);// 若类没有覆盖equals方法,等价于“==”比较对象 ==:它的作用是判断两个对象的地址是不是相等返回 false

重写equals

`public boolean equals(Object obj) {
if (obj != null) {
  if (this == obj) {
    return true;
  }
  if (obj instanceof Person) {
    Person person = (Person) obj;
    if (person.getKeyId() == this.getKeyId() && person.getUserName() == this.getUserName()) {
      return true;
    }
  }
}
return false;}`

hashCode的作用

  hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
  hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
  仅仅当创建并某个“类的散列表”(关于“散列表”见下面说明)时,该类的hashCode() 才有用
  (作用是:确定该类的每一个对象在散列表中的位置;其它情况下(例如,创建类的单个对象,或者创建类的对象数组等等),类的hashCode() 没有作用。
  • hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置

  • 1、如果两个对象相等,那么它们的hashCode()值一定相同。 这里的相等是指,通过equals()比较两个对象时返回true

  • 2.、如果两个对象hashCode()相等,它们并不一定相等。 因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并 不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突。

  • 3.如果单单只是覆盖equals()方法。我们会发现,equals()方法没有达到我们想要的效果。试试啥效果呗
    Person p1 = new Person(1, "james"); Person p2 = new Person(1, "james"); HashSet<Person> set = new HashSet<>(); set.add(p1); set.add(p2); System.out.println(p1.hashCode() + "=====" + p2.hashCode());

  • 若不重写,同一个对象会在hash中占据两个位置哈希位置,然而对象内容一样,完全不合理。我们在使用hash取值时,会得得到两个一模一样的结果

  • equals相等,hashCode必定相等
    我们在hashMap,hashSet等哈希表中出入数据的时候
    首先通过hash算法算出当前数据的hashCode,如果算出当前hashCode位置上有值,我们需要使用
    equals方法比较内容是否一致,如果一致,无需保存,如果不一样,就覆盖
    因此我们可以导论出一个结果,如果两个对象equals相等,hashCode必定相等

  • 这也就解释了为什么equals()相等,则hashCode()必须相等。如果两个对象equals()相等,则它们在哈希表(如HashSet、HashMap等)中只应该出现一次;如果hashCode()不相等,那么它们会被散列到哈希表的不同位置,哈希表中出现了不止一次。

了解一波String 类中的equals 和 hashCode方法

先看代码
`private final char value[];
 private int hash; // Default to 0
 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;
}
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;
} `
  • String的数据是final的,即一个String对象一旦创建,便不能修改;形如String s = “hello”; s = “world”;的语句,当s = “world”执行时,并不是字符串对象的值变为了”world”,而是新建了一个String对象,s引用指向了新对象

  • String类将hashCode()的结果缓存为hash值,提高性能

  • String对象equals()相等的条件是二者同为String对象,长度相同,且字符串值完全相同;不要求二者是同一个对象。

  • String的hashCode()计算公式为:s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
    关于hashCode()计算过程中,为什么使用了数字31,主要有以下原因:

  • 使用质数计算哈希码,由于质数的特性,它与其他数字相乘之后,计算结果唯一的概率更大,哈希冲突的概率更小。

  • 使用的质数越大,哈希冲突的概率越小,但是计算的速度也越慢;31是哈希冲突和性能的折中,实际上是实验观测的结果。

  • JVM会自动对31进行优化:31 * i == (i << 5) – i

  • 这里我们看到31 = 1111…于是乎我们在做获取hashcode时,31是哈希冲突和性能的折中,实际上是实验观测的结果

java三种移位运算符

<<      :     左移运算符,num << 1,相当于num乘以2

>>      :     右移运算符,num >> 1,相当于num除以2

>>>     :     无符号右移,忽略符号位,空位都以0补齐

我们如何写hashCode,我想既然String的源码中是这样写的,我们不如模仿一波

  • 重写hashcode()的原则 所谓原则也就是怎样能达到不能触及它的最低和最高的效率问题
    通过前面的描述我们知道,重写hashCode需要遵守以下原则:

  • (1)如果重写了equals()方法,检查条件“两个对象使用equals()方法判断为相等,则hashCode()方法也应该相等”是否成立,如果不成立,则重写hashCode ()方法。 注:我是这样理解,如果equals相等,说明两个对象存在于一个hashCode地址的位置中 hashCode()的方法自然是相等的

  • (2)hashCode()方法不能太过简单,否则哈希冲突过多。

  • (3)hashCode()方法不能太过复杂,否则计算复杂度过高,影响性能。

《Effective Java》中提出了一种简单通用的hashCode算法

  • A、初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result = 17;

  • B、选取equals方法中用于比较的所有域(之所以只选择equals()中使用的域,是为了保证上述原则的第1条),然后针对每个域的属性进行计算:

  • (1) 如果是boolean值,则计算f ? 1:0

  • (2) 如果是byte\char\short\int,则计算(int)f

  • (3) 如果是long值,则计算(int)(f ^ (f >>> 32))

  • (4) 如果是float值,则计算Float.floatToIntBits(f)

  • (5) 如果是double值,则计算Double.doubleToLongBits(f),然后返回的结果是long,再用规则(3)去处理long,得到int

  • (6) 如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0

  • (7) 如果是数组,那么需要为每个元素当做单独的域来处理。java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上。

  • C、最后,把每个域的散列码合并到对象的哈希码中。

下面通过一个例子进行说明。在该例中,Person类重写了equals()方法和hashCode()方法。因为equals()方法中只使用了name域和age域,所以hashCode()方法中,也只计算name域和age域。

简便hashCode方法

 public int hashCode() {
    int hash = 17;
    hash = hash * 31 + getName().hashCode();
    hash = hash * 31 + getAge();
    return hash;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值