• equals方法与hashCode方法相关


    重写equals方法

    为什么要重写equals方法

    其底层实现:

    public boolean equals(Object obj) {
            return (this == obj);
        }
    
    • 1
    • 2
    • 3

    我们可以清楚地看到,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;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    Integer中的equals重写

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

    //Integer类的equals源代码:
     public boolean equals(Object obj) {
            if (obj instanceof Integer) {
                return value == ((Integer)obj).intValue();
            }
            return false;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如何重写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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    //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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    事实上两种方案都是有效的,区别就是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	
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    返回值两个都是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);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    可以看到,当我们重写了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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    今日推歌

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

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

  • 相关阅读:
    springboot集成swagger3+解决页面无法访问问题
    bat脚本字符串替换:路径中\需要替换,解决一些文件写入路径不对的问题
    FFplay文档解读-12-协议二
    窥一斑而知全豹,从五大厂商看MCU国产化的机遇和挑战
    全链路压测基础
    Linux: 特殊文件名文件删除
    【第五篇】- 深入学习Git 创建仓库
    react fiber架构【详细讲解,看这一篇就够了】
    如何对 Kubernetes 节点进行运维
    第十五章总结
  • 原文地址:https://blog.csdn.net/m0_51013067/article/details/126395911