equals方法与hashCode方法相关

重写equals方法

为什么要重写equals方法

其底层实现:

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

我们可以清楚地看到,Object类equals方法底层是用 == 来实现的,也就是说它的用法跟我们平常用来比较基本数据类型的 == 用法一致。我们首先来看一下 == 的语法:

  • == 只能用来比较基本数据类型是否相等,也就是单纯的值比较;
  • == 在比较浮点数的时候也可能存在失效的情况,这是因为浮点数的存储机制跟整型家族不一样,浮点数本身就不能表示一个精确的值(具体原因可自行查看IEEE 754规则,这里不再展开)

​ 所以我们在单纯的进行基本数据类型的值比较时可以用 == ,而比较引用数据类型就不能这么做,前面有提到,引用数据类型本质上是来引用/存储对象的地址的,所有你完全可以把它当做C/C++的指针来看待.
注: 不要把Java引用跟C++引用搞混了,C++引用其实是指针常量,即int* const,这也是C++的引用只能作为一个变量的别名的原因。

​ 因为java类默认的equals是比较内存地址是否一致,那么比较的将是两个对象是否为同一个。但是这并不符合我们现实比较逻辑,就比如判断学生是否为同一个,如果内存中存在两个变量完全一致(学号,姓名等等信息)的两个对象,这在现实逻辑中就是同一个学生,但是如果不重写equals,那么比较的对象在堆中的地址,因为为两个对象所以地址是不同的,就会造成认为这两个同学不是同一个,很显然有问题出现。

string 类重写equals方法

简单解读一下就是当对比的是同一个对象时,直接返回true,提高效率。当传进来的对象是当前类的实例时,进入进一步的判断,一个for循环依次遍历字符串每一个字符,只要有一个字符不同就返回false

//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;
    }

Integer中的equals重写

Integer类equals源码简单许多,只要传入的对象是当前类的实例,就进行进一步的判断:当它们的值相等时,就返回true,不相等就返回false

//Integer类的equals源代码:
 public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

如何重写equals方法

  1. 先用“==”判断是否相等。
  2. 判断equals()方法的参数是否为null,如果为null,则返回false;因为当前对象不可能为null,如果为null,则不能调用其equals()方法,否则抛java.lang.NullPointerException异常。
  3. 当参数不为null,则如果两个对象的运行时类(通过**getClass()**获取)不相等,返回false,否则继续判断。
  4. 判断类的成员是否对应相等。往下就随意发挥了。
@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;//向下转型
        return ID == student.ID && age == student.age && Objects.equals(name, student.name);//注意:浮点数的比较不能简单地用==,会有精度的误差,用Math.abs或者Double.compare
    }
 
    @Override
    public int hashCode() {
        //重写equals的同时也要重写hashCode
        return Objects.hash(name, ID, age);
    }

getClass()与instanceof方法比较

结论:getClass()instanceof更安全

重点看这段简单的代码:

//getClass()版本
public class Student {
	private String name;
	
    public void setName(String name) {
        this.name = name;
    }
	@Override
	public boolean equals(Object object){
		if (object == this)
			return true;
		// 使用getClass()判断对象是否属于该类
		if (object == null || object.getClass() != getClass())
			return false;
		Student student = (Student)object;
		return name != null && name.equals(student.name);
}
//instanceof版本
public class Student {
	private String name;
	
    public void setName(String name) {
       this.name = name;
   }
	@Override
	public boolean equals(Object object){
		if (object == this)
			return true;
		// 通过instanceof来判断对象是否属于类
		if (object == null || !(object instanceof Student))
			return false;
		Student student = (Student)object;
		return name!=null && name.equals(student.name);
	}
}

事实上两种方案都是有效的,区别就是getClass()限制了对象只能是同一个类,而instanceof却允许对象是同一个类或其子类,这样equals方法就变成了父类与子类也可进行equals操作了,这时候如果子类重定义了equals方法,那么就可能变成父类对象equlas子类对象为true,但是子类对象equlas父类对象就为false了,如下所示:

class GoodStudent extends Student {

    @Override
    public boolean equals(Object object) {
        return false;
    }

    public static void main(String[] args) {
        GoodStudent son = new GoodStudent();
        Student father = new Student();

        son.setName("test");
        father.setName("test");

		// 当使用instance of时
        System.out.println(son.equals(father)); // 这里为false
        System.out.println(father.equals(son)); // 这里为true

		// 当使用getClass()时
        System.out.println(son.equals(father)); // 这里为false
        System.out.println(father.equals(son)); // 这里为false	
    }
}

返回值两个都是false,符合我们的预期,(连类都不一样那肯定得为false啊)

这里的原因如下:
instanceof的语法是这样的:

当一个对象为一个类的实例时,结果才为true。但它还有一个特点就是,如果当这个对象时其子类的实例时,结果也会为true。这便导致了上述的bug。也就是说当比较的两个对象,他们的类是父子关系时,instanceof可能会出现问题。需要深究的小伙伴可以自己去了解一哈,所以在这里建议在实现重写equals方法时,尽量使用getClass来实现。

为什么重写equals后需要重写hashCode

HashMapkey进行hash后,得出一个整数值, 这个整数值就是存放到最终的存储数组中的下标, 当然这块后续还有一系列的处理, 可以查阅相关资料做深入的了解, 这里只做简单的描述,

我们接着上面的问题看,因为我们没有重写hashCode方法,虽然equals以两个对象的name值是否相同做对比,但是HashMap存值的时候,是通过hashCode进行计算,算出一个值存到相应的数组下标下去的呀。所以p1p2两个值返回的hashCode值是不同的,所以计算出来的下标也不同,导致他们被HashMap存到不同的数组下标下面去了~

这就是为什么没有去重成功的原因

两个对象在堆地址中, 名字是一样的,hashCode不同,如果是通过名字做比对,做hash,那么就是相等的,但是HashMap用的是hashCode,所以,我们需要重写hashCode方法,根据自身业务保证,相同含义在业务层面属于一个对象的hashCode也要保持一致。

public class TestDemo {

    public static void main(String[] args) {
        Person p1 = new Person("阿伦");
        Person p2 = new Person("阿伦");
        System.out.println(p1.hashCode());
        System.out.println(p2.hashCode());
        Map<Person, String> map = new HashMap<>();
        map.put(p1, p1.getName());
        map.put(p2, p2.getName());
        map.get(p1);
        System.out.println("map长度:" + map.size());
        map.forEach((key, value) -> {
            System.out.println(key.getName());
        });
    }

    static class Person {

        public Person(String name) {
            this.name = name;
        }
        private String name;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Person person = (Person) o;
            return Objects.equals(name, person.name);
        }
        @Override
        public int hashCode() {
            return Objects.hash(name);
        }
    }
}

可以看到,当我们重写了hashCode让对象的名字作为计算的值,用来产生最终的hash值,这样HashMap就可以帮我们把两个对象,路由到一个下标下面了,再通过equals比对,确定两个是同一个对象,从而达到去重的效果。

根据业务状况重写equals后,一定要将hashCode用一定相同的规则做hash,防止在一些需要用到对象hashCode的地方造成误会,引发问题

java中浮点数不精确问题

​ 计算机中使用二进制运算,程序中的十进制数转换为二进制数运算的时候,Float和Double小数点后的小数转换为二进制的时候会发生无限循环的情况,通常会取一个无限近似于原值的近似值,所以会发生失去精确度的情况。

​ 在金融,工程,科学等领域,对计算数值的精确度有很高的要求,我们采用String + BigDecimal来解决精确度丢失的情况。

BigDecimal d01 = new BigDecimal(“0.1”);
BigDecimal d02 = new BigDecimal(“0.2”);
//计算d01和d02的乘积
BigDecimal d03 = d01.multiply(d02);
//将d03转换为双精度浮点数
double d04 = d03.doubleValue();

今日推歌

-------《阿拉斯加海湾》 蓝心羽

上天啊
你是不是在偷偷看笑话
明知我还没能力保护她
让我们相遇啊
上天啊
她最近是否不再失眠啦
愿世间温情化作一缕风
代替我拥抱她

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星回昭以烂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值