美文网首页
Effective Java:对于所有对象都通用的方法

Effective Java:对于所有对象都通用的方法

作者: Gino_Gu | 来源:发表于2017-02-17 21:31 被阅读0次

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

    (Item 8: Obey the general contract when overriding equals)

    如果类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法。
    在覆盖equals方法的时候,你必须要遵守它的通用约定。

    • 自反性:x.equals(x)必须返回true。
    • 对称性:当且仅当y.equals(x)返回true时,x.equals(y)必须返回true.
      -传递性:如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
    • 一致性:多次调用x.equals(y)就会一致地返回true,或者一致地返回false。
    • 对于任何非null的引用值x, x.equals(null)必须返回false。

    一旦违反了equals约定,当其他对象面对你的对象时,你完全不知道这些对象的行为会怎么样。

    我们无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定。

    无论类是否是不可变的,都不要使equals方法依赖于不可靠的资源。如果违反了这条禁令,要想满足一致性的要求就十分困难了。

    最后的告诫

    • 覆盖equals时总要覆盖hashCode
    • 不要企图让equals方法过于智能。例如, File类不应该试图把指向同一个文件的符号链接(symbolic link)当作相等的对象来看待。
    • 不要将equals声明中的Object对象替换为其他的类型。

    第9条:覆盖equals时总要覆盖hashCode

    (Item 9: Always override hashCode when you override equals)

    违反Object.hashCode的通用约定,会导致该类无法结合所有基于散列的集合类一起工作,如HashMap, HashSet和HashTable。
    下面是约定的内容:

    • 在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次, hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。
    • 如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
    • 如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table) 的性能。

    一个好的散列函数通常倾向于“为不相等的对象产生不相等的散列码”。
    示例代码:

    // Shows the need for overriding hashcode when you override equals - Pages 45-46
    
    import java.util.*;
    
    public final class PhoneNumber {
        private final short areaCode;
        private final short prefix;
        private final short lineNumber;
    
        public PhoneNumber(int areaCode, int prefix,
                           int lineNumber) {
            rangeCheck(areaCode,    999, "area code");
            rangeCheck(prefix,      999, "prefix");
            rangeCheck(lineNumber, 9999, "line number");
            this.areaCode  = (short) areaCode;
            this.prefix  = (short) prefix;
            this.lineNumber = (short) lineNumber;
        }
    
        private static void rangeCheck(int arg, int max,
                                       String name) {
            if (arg < 0 || arg > max)
               throw new IllegalArgumentException(name +": " + arg);
        }
    
        @Override public boolean equals(Object o) {
            if (o == this)
                return true;
            if (!(o instanceof PhoneNumber))
                return false;
            PhoneNumber pn = (PhoneNumber)o;
            return pn.lineNumber == lineNumber
                && pn.prefix  == prefix
                && pn.areaCode  == areaCode;
        }
    
        // Broken - no hashCode method!
    
        // A decent hashCode method - Page 48
    //  @Override public int hashCode() {
    //      int result = 17;
    //      result = 31 * result + areaCode;
    //      result = 31 * result + prefix;
    //      result = 31 * result + lineNumber;
    //      return result;
    //  }
    
    
        // Lazily initialized, cached hashCode - Page 49
    //  private volatile int hashCode;  // (See Item 71)
    //
    //  @Override public int hashCode() {
    //      int result = hashCode;
    //      if (result == 0) {
    //          result = 17;
    //          result = 31 * result + areaCode;
    //          result = 31 * result + prefix;
    //          result = 31 * result + lineNumber;
    //          hashCode = result;
    //      }
    //      return result;
    //  }
    
        public static void main(String[] args) {
            Map<PhoneNumber, String> m
                = new HashMap<PhoneNumber, String>();
            m.put(new PhoneNumber(707, 867, 5309), "Jenny");
            System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
        }
    }
    

    第10条:始终要覆盖toString

    (Item 10: Always override toString)

    提供好的toString实观可以使类用起来更加舒适。
    在实际应用中, toString方法应该追回对象中包含的所有值得关注的信息。
    最好给toString加注释标明返回值的格式,例如:

        /**
         * Returns the string representation of this phone number.
         * The string consists of fourteen characters whose format
         * is "(XXX) YYY-ZZZZ", where XXX is the area code, YYY is
         * the prefix, and ZZZZ is the line number.  (Each of the
         * capital letters represents a single decimal digit.)
         *
         * If any of the three parts of this phone number is too small
         * to fill up its field, the field is padded with leading zeros.
         * For example, if the value of the line number is 123, the last
         * four characters of the string representation will be "0123".
         *
         * Note that there is a single space separating the closing
         * parenthesis after the area code from the first digit of the
         * prefix.
         */
        @Override public String toString() {
            return String.format("(%03d) %03d-%04d",
                                 areaCode, prefix, lineNumber);
        }
    

    第11条:谨慎地覆盖clone

    如果你覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone而得到的对象。

    clone方法就是另一个构造器;你必须确保它不会伤害到原始的对象,并确保正确地创建被克隆对象中的约束条件(invariant)。

    另一个实现对象拷贝的好办法是提供一个拷贝构造器 (copy constructor) 或拷贝工厂(copy factory) 。
    以下例子假如不写result.elements = elements.clone();,当客户端代码执行Stack stack2 = stack1.clone()时,stack2里的element数组里存放的是stack1里element数组的引用,也就是说当调用stack1.pop()后stack2里element数组元素个数会减少。

    // A cloneable version of Stack - Pages 56-57
    
    import java.util.Arrays;
    
    public class Stack implements Cloneable {
        private Object[] elements;
        private int size = 0;
        private static final int DEFAULT_INITIAL_CAPACITY = 16;
    
        public Stack() {
            this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
        }
    
        public void push(Object e) {
            ensureCapacity();
            elements[size++] = e;
        }
    
        public Object pop() {
            if (size == 0)
                throw new EmptyStackException();
            Object result = elements[--size];
            elements[size] = null; // Eliminate obsolete reference
            return result;
        }
    
        public boolean isEmpty() {
            return size == 0;
        }
    
        @Override public Stack clone() {
            try {
                Stack result = (Stack) super.clone();
                result.elements = elements.clone();
                return result;
            } catch (CloneNotSupportedException e) {
                throw new AssertionError();
            }
        }
    
        // Ensure space for at least one more element.
        private void ensureCapacity() {
            if (elements.length == size)
                elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    
        // To see that clone works, call with several command line arguments
        public static void main(String[] args) {
            Stack stack = new Stack();
            for (String arg : args)
                stack.push(arg);
            Stack copy = stack.clone();
            while (!stack.isEmpty())
                System.out.print(stack.pop() + " ");
            System.out.println();
            while (!copy.isEmpty())
                System.out.print(copy.pop() + " ");
        }
    }
    

    第12条:考虑实现Comparable接口

    如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母顺序,按数值顺序或者按年代顺序,那你就应该坚决考虑实现这个接口

    违反compareTo约定的类也会破坏其他依赖于比较关系的类。

    包括有序集合类TreeSet和TreeMap, 以及工具类Collections和Arrays,它们内部包含有搜索和排序算法。

    如果一个类有多个关键域,那么,按什么样的顺序来比较这些域是非常关键的。你必须从最关键的域开始,逐步进行到所有的重要域。’

    如果称想为一个实现了Comparable接口的类增加值组件,请不要扩展这个类,而是要编写一个不相关的类,其中包含第一个类的一个实例。然后提供一个“视图(view)”方法返回这个实例。

    相关文章

      网友评论

          本文标题:Effective Java:对于所有对象都通用的方法

          本文链接:https://www.haomeiwen.com/subject/zralwttx.html