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,Object的clone()方法就会返回该对象的逐级拷贝,否则抛出异常.
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方法,防止子类修改其状态。
小结
- 所有实现了Cloneable接口的类,都应该用一个公有的方法覆盖 clone;
- 此方法首先调用 super.clone;
- 最好提供其他的途径来代替对象拷贝,或者干脆不提供这样的功能;
- 另一种实现对象拷贝的方法是提供一个拷贝构造函数,或者拷贝工厂方法,而且此种方法更加推荐;
- 设计用来被继承的类时,如果不实现一个正确高效的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来进行比较。所以如果希望自己的对象拥有排序的能力,可以考虑实现这个接口。

浙公网安备 33010602011771号