《Effective Java》第2章 对全部对象都通用的方法

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

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

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

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

  三、非自定义类(Java内置类)中进行比较的时候,不必定会使用Object.equals方法,由于内置类可能覆盖了equals方法,好比Integer的equals方法:框架

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

  四、覆盖equals方法,是由于咱们要进行判断“逻辑相等”,好比两个对象的哪些属性相等,而不是简单判断是否是同一个对象;ide

  五、覆盖equals时的规范:一、自反性;二、对称性;三、传递性;四、一致性;五、与null判断相等时,结果必需是false;工具

  六、当在扩展类的时候(好比建立子类,也许会增长新字段),那么这个时候很难保留上面的equals规范;性能

  七、实现equals方法的诀窍:this

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

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

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

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

  八、覆盖equals方法的时候,老是要覆盖hashCode方法;

  九、不要太过依赖equals方法进行比较,彻底可使用if elseif  else 来进行逐项匹配;

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

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

 

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

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

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

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

  b:两个对象调用equals方法相等,那么hashCode也必需要相等;若是没有覆盖hashCode方法,那么就没法知足这一条;

  c:若是经过equals方法比较不相等,可是hashCode是能够相等的;

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

四、推荐使用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;
}

  五、若是懒得本身动手覆盖equals、hashCode方法,可使用lombok的@EqualsAndHashCode注解。

 

第12条:始终要覆盖toString

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

  二、静态工具类、枚举类不须要覆盖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();
    }
}

  注意事项:

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

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

  三、解决深拷贝与浅拷贝,还能够先调用super.clone,而后把结果对象中的全部域都设置为初始状态,而后调用高层方法进行设置对象属性;

  四、若是编写线程安全的类须要实现Clonable接口,那么clone方法也须要改为同步的,只须要在clone方法前加synchronized关键字便可;

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

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接口

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

  二、实现了Compareable接口,而且覆盖compareTo方法后,能够进行元素的排序操做;

  三、实现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);
    }
}

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

@Override
public int compareTo(Person o) {
    // 不推荐使用两数相减,容易形成整数溢出
    return id - o.id;
}
相关文章
相关标签/搜索