• 《Effective Java》第2章 对所有对象都通用的方法


    第10条:覆盖equals时,请遵守通用约定

      1、使用==来比较两个对象的时候,比较的是两个对象在内存中的地址是否相同(两个引用指向的是否为同一个对象);Object中定义的equals方法也是这样比较的;

      2、当我们自定义类的时候,如果不覆盖equals方法,那么就会使用默认的equals方法(Object中已经定义了),这样的话,只有当对象与对象自身比较才会返回true(指定同一个对象);

    // Object中的equals,只有对象与对象自身比较才会返回true
    public boolean equals(Object obj) {
        return (this == obj);
    }
    

      3、非自定义类(Java内置类)中进行比较的时候,不一定会使用Object.equals方法,因为内置类可能覆盖了equals方法,比如Integer的equals方法:

    // Integer 中的equals方法,比较的两个对象中的“值”,而非原始的比较两个地址
    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }
    

      4、覆盖equals方法,是因为我们要进行判断“逻辑相等”,比如两个对象的哪些属性相等,而不是简单判断是不是同一个对象;

      5、覆盖equals时的规范:1、自反性;2、对称性;3、传递性;4、一致性;5、与null判断相等时,结果必需是false;

      6、当在扩展类的时候(比如创建子类,也许会增加新字段),那么这个时候很难保留上面的equals规范;

      7、实现equals方法的诀窍:

        a:使用“==”检查“参数是否为这个对象的引用”

        b:使用instance检查是否为正确的类型;

        c:instance检查通过后,将参数转换为正确的类型;

        d:根据业务需求检查参数中的某些字段是否与该对象中的字段相匹配,并且在比较的时候,优先匹配最有可能不一致的域;

      8、覆盖equals方法的时候,总是要覆盖hashCode方法;

      9、不要太过依赖equals方法进行比较,完全可以使用if elseif  else 来进行逐项匹配;

      10、推荐写法是x.equals(Object o),但是不要将equals方法的参数替换为其他类型,比如x.equals(X o),这样的做法并不是覆盖Object的equals方法,而是重载了Object.equals方法;为了防止这样的错误,可以使用@Override注解。

      11、推荐使用Google的AutoValue框架,或者IDEA来生成equals方法;

    第11条、覆盖equals时总要覆盖hashCode

      1、如果定义类的时候只是覆盖了equals方法,但是没有hashCode方法,那么类的对象在使用HashMap和HashSet的时候就会出现问题,至于为什么,可以看一下HashMap的底层原理;

      2、规范中对于equals和hashCode的描述:

      a:应用执行期间,只要调用equals进行比较的那些字段信息没有发生更改,那么对同一个对象调用多次equals方法,hashCode都必须返回同一个值;一个程序与另外一个程序的执行过程中,执行hashCode的返回值可以不一致;

      b:两个对象调用equals方法相等,那么hashCode也必须要相等;如果没有覆盖hashCode方法,那么就无法满足这一条;

      c:如果通过equals方法比较不相等,但是hashCode是可以相等的;

    3、覆盖hashCode的时候,应该尽量做到“为不相等的对象产生不相等的散列码”,但是不要为了提高性能,试图从散列码计算中排除某个对象关键域;

    4、推荐使用Google的AutoValue框架,或者IDEA来生成hashCode方法;

    下面是一个hashCode的示例:

    @Override
    public int hashCode() {
        // 类中有id,name,addr三个属性,equals中也是比较这3个属性值,下面是计算hashCode的方式
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + (addr != null ? addr.hashCode() : 0);
        
        // 如果增加了一个gender的属性,那么就加这么一行
        // result = 31 * result + (gender!= null ? gender.hashCode() : 0);
        
        return result;
    }
    

      5、如果懒得自己动手覆盖equals、hashCode方法,可以使用lombok的@EqualsAndHashCode注解。

    第12条:始终要覆盖toString

      1、不覆盖toString方法,在打印对象的时候,输出的是ClassName@Number格式,无法看出对象的具体信息,调试时麻烦;

      2、静态工具类、枚举类不需要覆盖toString方法,前者是因为没有意义,后者是因为Java已经提供了支持;

    第13条、谨慎使用clone

      实现Clonable接口,然后覆盖clone方法的例子

    // 方式1
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
    
    // 方式2
    @Override
    public Person clone() {
        try {
            return (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
    

      注意事项:

      1、不可变的类不应该提供clone方法;

      2、需要注意深拷贝与浅拷贝的情况:如果属性是基本数据类型就不存在这个问题,但如果是引用数据类型(包括String),调用clone方法只是单纯的浅拷贝,需要进行递归clone;深拷贝与浅拷贝的区别; 

      3、解决深拷贝与浅拷贝,还可以先调用super.clone,然后把结果对象中的所有域都设置为初始状态,然后调用高层方法进行设置对象属性;

      4、如果编写线程安全的类需要实现Clonable接口,那么clone方法也需要改成同步的,只需要在clone方法前加synchronized关键字即可;

      5、对象拷贝的最佳方式提供拷贝构造器和拷贝工厂,如下示例:

    package cn.ganlixin.effective_java;
    
    import lombok.Getter;
    import lombok.Setter;
    
    @Getter
    @Setter
    public class Person {
    
        int id;
        String name;
    
        public Person(int id, String name) {
            this.id = id;
            this.name = name;
        }
    
        // 拷贝构造器
        public Person(Person person) {
            this(person.id, person.name);
        }
    
        // 拷贝工厂,方法名随意
        public static Person newInstance(Person person) {
            return new Person(person);
        }
    
        public static void main(String[] args) {
            Person p1 = new Person(1,"hello");
    
            Person p2 = Person.newInstance(p1);
            System.out.println(p1 == p2);   // false
    
            p1.setName("world");
            System.out.println(p1.getName());   // world
            System.out.println(p2.getName());   // hello
        }
    }
    

      

    第14条:考虑实现Comparable接口

      1、实现Compareable接口后,可以覆盖compareTo方法,这个方法可以进行等同性比较(和equal功能相同),还可以指定比较的顺序(自己写equals时,也可以指定,只不过一般会优先比较最有可能不同的属性);

      2、实现了Compareable接口,并且覆盖compareTo方法后,可以进行元素的排序操作;

      3、实现Compareable接口时,推荐加上泛型的类型,可以免去compareTo中强制类型转换;另外compareTo方法中,数值类型(int、double..)不建议使用>或者<符号,建议使用装箱基本数据类型

    public class Person implements Comparable<Person> {
    
        int id;
    
        @Override
        public int compareTo(Person o) {
            // 不推荐使用> 或者 <符号进行比较,比如 return (x < y) ? -1 : ((x == y) ? 0 : 1);
            // 其他数值类型也是一样的
            return Integer.compare(id, o.id);
        }
    }
    

      4、比较时,value1 > value2 返回 正数;value1 < value2 返回负数;两者相等,返回0;不要使用两数相减的方式,因为可能会出现溢出:

    @Override
    public int compareTo(Person o) {
        // 不推荐使用两数相减,容易造成整数溢出
        return id - o.id;
    }
    

      

     

        

      

  • 相关阅读:
    新的开始 和一些总结
    常用物流快递单号查询API接口对接net源码示例_快递鸟
    从SpringBoot到SpringCloudAlibaba简明教程(一):初识SpringBoot及其基础项目构建
    Debug > python list.sort()食用方法
    Debug > python统计列表中元素个数
    Debug > python中的True False 0 1
    java获取本机外网ipV4
    aop统一处理修改请求参数
    [算法] 国家地区数组按照中文首字母排序
    js转换ASCII码
  • 原文地址:https://www.cnblogs.com/-beyond/p/11563404.html
Copyright © 2020-2023  润新知