effective java学习之对于所有对象都通用的方法——始终覆盖toString(),谨慎的覆盖clone,考虑实现Comparable接口

始终覆盖toString

  1、提供好的toString使类用起来更加舒服

  2、toString应该包含所有值得关注的信息

  3无论是否指定格式,都应该为toString返回值中包含的所有信息,提供一种编程式的访问路径.

 代码:

import java.lang.reflect.Field;
public class PhoneNumber2 {
    private final int areaCode;
    private final int prefix;
    private final int lineNumber;

    public PhoneNumber2(int areaCode, int prefix, int lineNumber) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
    }
    @Override
    public boolean equals(Object obj) {
        // 使用==操作符检查参数是否为这个对象的引用
        if (obj == this) {
            return true;
        }
        // 2、使用instanceof操作符检查参数是否为正确的类型
        if (!(obj instanceof PhoneNumber2)) {
            return false;
        }
        // 3、把参数转换成正确的类型
        PhoneNumber2 pn = (PhoneNumber2)obj;
        // 4、应该问自己三个问题,它是否是对称的、传递的、一致的。
        return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode;
    }
    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + areaCode;
        result = 31 * result + prefix;
        result = 31 * result + lineNumber;
        return result;
    }
    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();  
        Class c = this.getClass();  
        Field[] f = c.getDeclaredFields();  
        for (int i = 0; i < f.length; i++) {
            f[i].setAccessible(true);  
            String fieldName = f[i].getName();  
            sb.append(fieldName);  
            sb.append("=");  
            try {  
                sb.append(f[i].get(this));  
            } catch (IllegalArgumentException e) {  
                e.printStackTrace();  
            } catch (IllegalAccessException e) {  
                e.printStackTrace();  
            }  
            sb.append("\n");  
        }  
        return sb.toString(); 
    }
}

测试代码:

    public static void main(String[] args) {
        Map<PhoneNumber2, String> map2 = new HashMap<PhoneNumber2, String>();
        PhoneNumber2 phoneNumber2 = new PhoneNumber2(707, 867, 9876);  
        map2.put(phoneNumber2, "lowi");
        System.out.println(map2.get(new PhoneNumber2(707, 867, 9876)));  
        System.out.println(map2.get(phoneNumber2));
        System.out.println(map2.get(new PhoneNumber2(707, 867, 2211)));
        /**
         * lowi
         * lowi
         */
        System.out.println(phoneNumber2.toString());
        /**
         * areaCode=707
         * prefix=867
         * lineNumber=9876
         */
    }

谨慎覆盖clone

概述

  如果一个类实现了Cloneable,Objectclone()方法就会返回该对象的逐级拷贝,否则抛出异常.

clone 通用协议

  对于任何对象 x:

    1.x.clone() != x

    2.x.clone().getClass() == x.getClass(),但这不是绝对要求

    3.x.clone().equals(x),也不是一个绝对要求

提醒:

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

@Override 
protected Object clone() {
    try {
      return (PhoneNumber)super.clone();//不要让客户做转换
    } catch (CloneNotSupportedException _e) {
      throw new AssertionError();
    }
  }

  如果对象中的域引用了可变的对象。仅仅super.clone,新clone的对象的引用域将引用原来的可变对象,破坏了对象克隆的约束条件。

@Override 
public Stack clone() {//将protect--> public try { Stack result = (Stack) super.clone(); result.elements = elements.clone();//不伤害原始对象 return result; } catch (CloneNotSupportedException _e) { throw new AssertionError(); } }

  1如果elements是final的将会导致失败,因为是被禁止给fianl域重新赋值;

  2要实现clone方法的类,都应该实现Cloneable接口,同时把clone方法可见性设为public;

  3同构造器一样,clone方法不能在构造过程中调用新对象的非final方法,防止子类修改其状态。

小结

  1. 所有实现了Cloneable接口的类,都应该用一个公有的方法覆盖 clone;
  2. 此方法首先调用 super.clone;
  3. 最好提供其他的途径来代替对象拷贝,或者干脆不提供这样的功能;
  4. 另一种实现对象拷贝的方法是提供一个拷贝构造函数,或者拷贝工厂方法,而且此种方法更加推荐;
  5. 设计用来被继承的类时,如果不实现一个正确高效的clone重写,其子类也将无法实现正确高效的clone功能。

Comparable接口

概述

  当需要对象自然有序时,实现Comparable接口

通用协议

  1.sgn(x.compareTo(y)) == - sgn(y.compareTo(x));当类型不对时,应该抛出ClassCastException

  2.x.compareTo(y) > 0 && y.compareTo(z) > 0 ,x.compareTo(z) > 0

  3.x.compareTo(y) == 0,sgn(x.compareTo(z)) == sgn(y.compareTo(z))

  4.建议与equals保持一致,但非必须. x.compareTo(y) == 0, x.equals(y).

  依赖于比较关系的类TreeSet, TreeMap,Arrays等,而HashSet,HashMap则是通过equals+ hashCode保存

实现细节

  1.从最关键的域开始,逐步进行到所有的重要域.

  2.谨慎使用返回差值的方式,有可能会溢出(最大值和最小值之差 小于等于Integer.MAX_VALUE)

import java.util.NavigableSet;
import java.util.Random;
import java.util.TreeSet;
public final class PhoneNumber implements Cloneable, Comparable<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;
    }
    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + areaCode;
        result = 31 * result + prefix;
        result = 31 * result + lineNumber;
        return result;
    }
    /**
     * 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);
    }
    @Override
    public PhoneNumber clone() {
        try {
            return (PhoneNumber) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(); // Can't happen
        }
    }
    // Works fine, but can be made faster
    // public int compareTo(PhoneNumber pn) {
    // // Compare area codes
    // if (areaCode < pn.areaCode)
    // return -1;
    // if (areaCode > pn.areaCode)
    // return 1;
    //
    // // Area codes are equal, compare prefixes
    // if (prefix < pn.prefix)
    // return -1;
    // if (prefix > pn.prefix)
    // return 1;
    //
    // // Area codes and prefixes are equal, compare line numbers
    // if (lineNumber < pn.lineNumber)
    // return -1;
    // if (lineNumber > pn.lineNumber)
    // return 1;
    //
    // return 0; // All fields are equal
    // }
    public int compareTo(PhoneNumber pn) {
        // Compare area codes
        int areaCodeDiff = areaCode - pn.areaCode;
        if (areaCodeDiff != 0)
            return areaCodeDiff;
        // Area codes are equal, compare prefixes
        int prefixDiff = prefix - pn.prefix;
        if (prefixDiff != 0)
            return prefixDiff;
        // Area codes and prefixes are equal, compare line numbers
        return lineNumber - pn.lineNumber;
    }
    public static void main(String[] args) {
        NavigableSet<PhoneNumber> s = new TreeSet<PhoneNumber>();
        for (int i = 0; i < 10; i++)
            s.add(randomPhoneNumber());
        System.out.println(s);
    }
    private static final Random rnd = new Random();

    private static PhoneNumber randomPhoneNumber() {
        return new PhoneNumber((short) rnd.nextInt(1000),
                (short) rnd.nextInt(1000), (short) rnd.nextInt(10000));
    }
}

扩展:

  常用的List等等可以通过实现Comparable接口进行排序:

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

  当对象小于,等于或者大于时,应该返回一个负整数,零或者正整数。

  下面来看看Collections中的sort方法。它要求List中的元素对象继承自Comparable,这样它内部的元素对象就具有内部比较功能了。

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}
default void sort(Comparator<? super E> c) {
    // 传进来的c为null
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ......
}
public static <T> void sort(T[] a, Comparator<? super T> c) {
    // 传进来的c为null
    if (c == null) {
        sort(a);
    }
    ......
}
public static void sort(Object[] a) {
    ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}
static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {
    if (nRemaining < MIN_MERGE) {
        int initRunLen = countRunAndMakeAscending(a, lo, hi);
        binarySort(a, lo, hi, lo + initRunLen);
        return;
    }
}
private static void binarySort(Object[] a, int lo, int hi, int start) {
    ......
    for ( ; start < hi; start++) {
        Comparable pivot = (Comparable) a[start];
       ......
        while (left < right) {
            int mid = (left + right) >>> 1;
            if (pivot.compareTo(a[mid]) < 0)
                right = mid;
            else
                left = mid + 1;
        }
    }
}

  只把重点代码列举处理了,可以看到对象的比较使用的就是Comparable中的compareTo来进行比较。所以如果希望自己的对象拥有排序的能力,可以考虑实现这个接口。

posted @ 2018-04-22 21:31  小码农成长记  阅读(181)  评论(0)    收藏  举报