zoukankan      html  css  js  c++  java
  • Effective Java 2

    Item 10 遵守覆盖equals的约定

    1、当类需要一个 逻辑相等 的功能时 覆盖equals()。

    2、需要满足的性质: 自反性、对称性、传递性、一致性,参数为null时返回False。

    3、没有办法在不违反equals约定的情况下,去通过添加新的值域来扩展一个实体类(子类化)。

    4、使用复合来代替继承。

    // Adds a value component without violating the equals contract
    public class ColorPoint {
    private final Point point;
    private final Color color;
    public ColorPoint(int x, int y, Color color) {
    point = new Point(x, y);
    this.color = Objects.requireNonNull(color);
    }
    /**
    * Returns the point-view of this color point.
    */
    public Point asPoint() {
    return point;
    }
    @Override public boolean equals(Object o) {
    if (!(o instanceof ColorPoint))
    return false;
    ColorPoint cp = (ColorPoint) o;
    return cp.point.equals(point) && cp.color.equals(color);
    }
    ... // Remainder omitted
    }

    5、不要使equals()依赖不可靠的资源。

    6、写一个高质量equals方法的步骤:

      1)使用 == 操作符检查参数是否是本对象的一个引用。

      2)使用 instanceof 操作符检查参数是否是正确类型(因为这里是覆盖,方法的接收参数类型与Object中的一样,为Object,因此需要在方法体中检查参数类型)。

      3)将参数类型强制成正确类型。

      4)对于类中每个重要的域,依次对照检查。(对于非float和double的基础类型,使用 == 操作符。Float.compare(float,float),double同理)。

    7、该方法的性能和比较顺序有关,尽可能的从最可能不同的域开始比较。

    8、每次写完这个方法,最好进行单元测试。

    // Class with a typical equals method
    public final class PhoneNumber {
      private final short areaCode, prefix, lineNum;
      public PhoneNumber(int areaCode, int prefix, int lineNum) {
        this.areaCode = rangeCheck(areaCode, 999, "area code");
        this.prefix = rangeCheck(prefix, 999, "prefix");
        this.lineNum = rangeCheck(lineNum, 9999, "line num");
      }
    private static short rangeCheck(int val, int max, String arg) {
      if (val < 0 || val > max) throw new IllegalArgumentException(arg + ": " + val);
      return (short) val;
      }
    @Override public boolean equals(Object o) {
      if (o == this)
        return true;
      if (!(o instanceof PhoneNumber))
        return false;
      PhoneNumber pn = (PhoneNumber)o;
      return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode;
      }
    ... // Remainder omitted
    }

    9、当覆盖了equals()方法时必须同时覆盖hashCode()方法。

    10、不要使equals()太复杂。

    Item 11 当覆盖了equals()方法时必须同时覆盖hashCode()方法

    1、hashCode()的约定:

      1)当在一个没有变化的类中多次调用hashCode()需要保证返回值相同。

      2)当两个对象equals()返回true时,他们的hashCode()应该相同。

      3)当两个对象equals()返回false时,他们的hashCode()尽量满足不同·(即需要一个好的散列函数)。

    2、覆盖hashCode()的步骤:

      1)声明一个 int 型的变量 result,将它初始化为对应第一个重要域的hashcode c。

      2)对于每一个重要域 f ,计算其对应的hashcode。

        ① 如果是基础类型,计算Type.hashCode(f),其中 Type 是 f 的包装起类型。

        ② 如果是对象引用,并且这个类的equals()方法是通过递归的对重要域进行比较。那么也递归的调用hashCode()。

        ③ 如果是null,通常使用0。

        ④ 如果是数组,如果其中的元素都是重要的,使用Arrays.hashCode;如果部分重要对每个元素进行hashCode的递归;如果没有重要的,通常是一个非0常数。

      3)使用 result = 31 * result + f;进行处理,并作为最终结果返回。

    3、应当忽略那些间接计算出来的域。

    4、该方法对域的顺序是敏感的,否则同字异序列String 将返回相同的值

    // Typical hashCode method
    @Override public int hashCode() {
        int result = Short.hashCode(areaCode);
        result = 31 * result + Short.hashCode(prefix);
        result = 31 * result + Short.hashCode(lineNum);
        return result;
    }

    5、对于一些比较简单的类,可以直接使用以下语句,虽然性能会比较差:

    // One-line hashCode method - mediocre performance
    @Override public int hashCode() {
        return Objects.hash(lineNum, prefix, areaCode);
    }

    6、对于可能经常需要使用此方法,对其进行缓存,并在第一次调用时才进行计算。

    // hashCode method with lazily initialized cached hash code
    private int hashCode; // Automatically initialized to 0
    
    @Override public int hashCode() {
        int result = hashCode;
        if (result == 0) {
            result = Short.hashCode(areaCode);
            result = 31 * result + Short.hashCode(prefix);
            result = 31 * result + Short.hashCode(lineNum);
            hashCode = result;
        }
        return result;
    }                

    7、不要为了方便试图故意遗漏某些重要域。

     Item 12 总是覆盖toString()

    1、如果你不覆盖该方法,它将返回类似 PhoneNumber@163b91 @之后的为其hash值,其可读性很差。

    2、即使没有显式调用toString方法,在使用 println、printf、字符串拼接、断言或者debug的信息打印都会自动调用。

    3、toString()方法需要包含类中所有重要的信息。

    4、通过注释具体说明toString()的输出格式。

    5、在静态工具类与绝大多数枚举类型中使用toString()是没有意义的,但是在任何子类含有相同字符串的抽象类中需要。

    /**
    * Returns a brief description of this potion. The exact details
    * of the representation are unspecified and subject to change,
    * but the following may be regarded as typical:
    *
    * "[Potion #9: type=love, smell=turpentine, look=india ink]"
    */
    @Override public String toString() { ... }

    以上三种方法 在AutoValue 或者IDE中都有相应的自动生成与支持。

    Item 13 谨慎地覆盖clone

    1、Cloneable接口用来被声明该类允许被克隆,但它不包含任何方法。它用于决定 Object's protected clone方法的实现行为。如果一个类实现了该接口。这个方法返回一个copy.

    2、实际中,一个实现了Cloneable接口的方法,会被期望提供一个合适的public clone 方法,而要达到这点,这个类及其超类需要遵守一个复杂,脆弱的协议。主要原因是,它没有调用构造器而创建了一个对象。

    3、其约定包括:

      

    x.clone() != x                                    //true
    x.clone().getClass() == x.getClass()   //true, but these are not absolute requirements
    x.clone().equals(x)                            //true, this is not an absolute requirement
    x.clone().getClass() == x.getClass()  //the object returned by this method should be obtained by calling
    //super.clone. If a class and all of its superclasses (except Object) obey
    //this convention,

     省略部分

    4、Object‘clone 是不线程同步的。

    5、任何实现 Cloneable 的类应该使用一个public方法覆盖clone,并且返回类型是这个类本身。这个方法必须先调用super.clone,然后对某些域进行修正。

    6、如果这个类只包含基本类型或者一些不可变对象的引用,通常是不需要被修正的。而类似唯一的ID之类的信息例外。

    7、一个更好的方法对对象进行复制,是使用一个复制构造器或者复制工厂。

    // Copy constructor
    public Yum(Yum yum) { ... };
    
    // Copy factory
    public static Yum newInstance(Yum yum) { ... };

    8、除了数组使用clone方法比较适合,其他类型都最好使用复制构造器或者复制工厂。

    Item 14 考虑实现Comparable

    1、compareTo方法是Comparable接口中的唯一方法。

    2、如果创建的一个值类有明显的自然顺序,应该考虑实现Comparable接口。

    public interface Comparable<T> {
    int compareTo(T t);
    }

    3、compareTo方法的约定包括:根据大于小于和等于返回1、-1、0;交换性(添加负号);传递性;替换性。

    4、强烈建议使其结果与equals方法逻辑相同,如果不同请明显的注释出来。

    5、通常情况下该方法使用在普通类型应抛出  ClassCastException。

    6、一个类违反compareTo约定,可能导致其他依赖此方法的类放生错误。例如 TreeSet、TreeMap、Collections、Arrays。

    7、和equals一样,没有办法在保证compareTo的约定情况下,通过添加一个值域来扩展一个实体类。

    8、解决办法同样是使用复合而非继承。使其包含第一个类的一个实体,并提供一个视图方法返回第一个类。

    9、与equals不同,其参数是有类型的,因此不需要检查类型。

    10、如果一个域没有实现Comparable接口或是使用非标准排序,使用 Comparator。

    11、一般形式:

    // Single-field Comparable with object reference field
    public final class CaseInsensitiveString implements Comparable<CaseInsensitiveString> {
        public int compareTo(CaseInsensitiveString cis) {
        return String.CASE_INSENSITIVE_ORDER.compare(s, cis.s);
        }
    ... // Remainder omitted
    }

    12、比较次序是关键的,请从最重要的域开始比较:

    // Multiple-field Comparable with primitive fields
    public int compareTo(PhoneNumber pn) {
        int result = Short.compare(areaCode, pn.areaCode);
        if (result == 0) {
            result = Short.compare(prefix, pn.prefix);
            if (result == 0)
                result = Short.compare(lineNum, pn.lineNum);
        }
        return result;
    }            

    13、Java 8开始,comparator 可以用来实现compareTo方法:

    // Comparable with comparator construction methods
    private static final Comparator<PhoneNumber> COMPARATOR =comparingInt((PhoneNumber pn) -> pn.areaCode).thenComparingInt(pn -> pn.prefix).thenComparingInt(pn -> pn.lineNum);
    
    public int compareTo(PhoneNumber pn) {
        return COMPARATOR.compare(this, pn);
    }
    // Comparator based on static compare method
    static Comparator<Object> hashCodeOrder = new Comparator<>() {
        public int compare(Object o1, Object o2) {
            return Integer.compare(o1.hashCode(), o2.hashCode());
        }
    };
    // Comparator based on Comparator construction method
    static Comparator<Object> hashCodeOrder =Comparator.comparingInt(o -> o.hashCode());
  • 相关阅读:
    猪猪的机器学习笔记(八)聚类
    猪猪的机器学习笔记(七)最大熵模型
    猪猪的机器学习笔记(九)推荐系统
    标签button:点击button按钮时,出现了页面自动刷新的情况
    SQL案例
    SQL学习记录:函数(二)
    SQL学习记录:定义(一)
    C# 后台报错输出到日志
    DateTime 时间类型总结(前端)
    笛卡尔积的使用
  • 原文地址:https://www.cnblogs.com/WutingjiaWill/p/9180600.html
Copyright © 2011-2022 走看看