Effective Java——对所有对象通用的方法
2016-03-17 18:04
519 查看
第8条:覆盖equals时请遵守通用约定
如果不对equals进行覆盖,那么类的每个实例都只与它自身相等。如果类具有自己特有的”逻辑相等”的概念,并且超类还没有覆盖equals以实现期望的行为,这时就可以进行覆盖。
高质量equals方法的诀窍
1、使用==操作符检查参数是否为这个对象的引用
2、使用instanceof操作符检查参数是否为正确的类型
3、把参数转换成正确的类型
4、当编写完成了equals方法之后,应该问自己三个问题,它是否是对称的、传递的、一致的。
例如下面实现的equals:
其他需要注意的:
1、覆盖equals时总要覆盖hashCode
2、不要企图让equals方法过于智能
3、不要将equals声明中的Object对象替换成其他的类型。
第9条:覆盖equals时总要覆盖hashCode
在每个覆盖了equals方法的类中,必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而也导致该类无法结合所有基于散列的结合一块正常运转,这样的结合包括HashMap、HashSet和Hashtable。
对于上面PhoneNumber的例子,它没有实现hashCode如果我们调用下面代码:
可以发现上面取到的值为null,而不是”hello”。
原因就是没有重写hashCode方法,每次hashCode返回的都是不同的值。
下面来看看HashMap的源码:
从上面可以看到传入的是key的hashCode,所以即使是相同的key,如果它的hashCode不同对应的index索引也不同。
如果我执行下面代码:
我们可以知道返回的就是hello,所以我们可以来看看String里面是怎么实现hashCode的。
它是根据字符串的内容来确定hashCode的,所以如果字符串的内容是相同的,那么hashCode也是相同的。所以我们可以类似来实现PhoneNumber的hashCode。
所以对应PhoneNumber中的hashCode,也可以有类似的实现:
这样得到的hashCode值跟里面的属性内容是有关系的,如果属性内容都相同,hashCode也就相同了。
第10条:始终要覆盖toString方法
在实际应用中,toString方法应用返回对象中含有的值得关注的信息。另外可以为toString指定返回的格式。
比较好的toString实现方法:
第11条:谨慎地覆盖clone方法
Cloneable接口的目的作为对象的mixin接口,表示该对象可以被克隆。但是它并没有提供clone方法,clone方法在Object中,但是这个方法是protected的,我们可以直接重写这个方法,但是也没办法直接进行调用,所以一般除了实现Cloneable之外,就是对Object中受保护的clone方法提供公有的访问途径。
既然Cloneable并没有包含任何方法,那么它到底有什么作用呢?它决定了Object中受保护的clone方法的行为,只有实现了Cloneable,Object的clone方法才能返回拷贝对象,否则会抛出异常。
拷贝对象往往会导致创建它的类的一个新实例,但它同时也会要求拷贝内部的数据结构。这个过程中没有调用构造器。
如果希望拷贝一个对象,超类提供这种功能的唯一途径是,返回一个通过调用super.clone而得到拷贝对象。但是我们一般在使用的时候需要特别注意浅拷贝和深拷贝。
简而言之,所有实现了Cloneable接口的类都必须用一个公有的方法覆盖clone,此公有方法首先调用super.clone,然后修正任何需要修正的域。一般情况下,就意味着要拷贝任何包含内部”深层结构”的可变对象,并用执行新对象的引用代替原来指向的这些对象的引用。
第12条:考虑实现Comparable接口
如果类实现了Comparable接口就表明它的实例具有内在的排序关系。
下面来看看Collections中的sort方法。它要求List中的元素对象继承自Comparable,这样它内部的元素对象就具有内部比较功能了。
上面代码都不完整,只把重点代码列举处理了,可以看到对象的比较使用的就是Comparable中的compareTo来进行比较。所以如果希望自己的对象拥有排序的能力,可以考虑实现这个接口。
如果不对equals进行覆盖,那么类的每个实例都只与它自身相等。如果类具有自己特有的”逻辑相等”的概念,并且超类还没有覆盖equals以实现期望的行为,这时就可以进行覆盖。
高质量equals方法的诀窍
1、使用==操作符检查参数是否为这个对象的引用
2、使用instanceof操作符检查参数是否为正确的类型
3、把参数转换成正确的类型
4、当编写完成了equals方法之后,应该问自己三个问题,它是否是对称的、传递的、一致的。
例如下面实现的equals:
class PhoneNumber { private final int areaCode; private final int prefix; private final int lineNumber; public PhoneNumber(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 PhoneNumber)) { return false; } // 3、把参数转换成正确的类型 PhoneNumber pn = (PhoneNumber)obj; // 4、应该问自己三个问题,它是否是对称的、传递的、一致的。 return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode; } }
其他需要注意的:
1、覆盖equals时总要覆盖hashCode
2、不要企图让equals方法过于智能
3、不要将equals声明中的Object对象替换成其他的类型。
第9条:覆盖equals时总要覆盖hashCode
在每个覆盖了equals方法的类中,必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而也导致该类无法结合所有基于散列的结合一块正常运转,这样的结合包括HashMap、HashSet和Hashtable。
对于上面PhoneNumber的例子,它没有实现hashCode如果我们调用下面代码:
Map<PhoneNumber, String> map = new HashMap<PhoneNumber, String>(); map.put(new PhoneNumber(408, 867, 5309), "hello"); map.get(new PhoneNumber(408, 867, 5309));
可以发现上面取到的值为null,而不是”hello”。
原因就是没有重写hashCode方法,每次hashCode返回的都是不同的值。
下面来看看HashMap的源码:
@Override public V put(K key, V value) { // 只展示关键代码 // 插入的位置索引值会对key取hansh int hash = Collections.secondaryHash(key); } public static int secondaryHash(Object key) { return secondaryHash(key.hashCode()); }
从上面可以看到传入的是key的hashCode,所以即使是相同的key,如果它的hashCode不同对应的index索引也不同。
如果我执行下面代码:
Map<String, String> map = new HashMap<String, String>(); map.put(new String("aaa"), "hello"); map.get(new String("aaa"));
我们可以知道返回的就是hello,所以我们可以来看看String里面是怎么实现hashCode的。
public int hashCode() { int h = hash;//hash默认为0,用来缓存String里面的hashCode if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
它是根据字符串的内容来确定hashCode的,所以如果字符串的内容是相同的,那么hashCode也是相同的。所以我们可以类似来实现PhoneNumber的hashCode。
所以对应PhoneNumber中的hashCode,也可以有类似的实现:
public int hashCode() { int result = 17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; return result; }
这样得到的hashCode值跟里面的属性内容是有关系的,如果属性内容都相同,hashCode也就相同了。
第10条:始终要覆盖toString方法
在实际应用中,toString方法应用返回对象中含有的值得关注的信息。另外可以为toString指定返回的格式。
比较好的toString实现方法:
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(); }
第11条:谨慎地覆盖clone方法
Cloneable接口的目的作为对象的mixin接口,表示该对象可以被克隆。但是它并没有提供clone方法,clone方法在Object中,但是这个方法是protected的,我们可以直接重写这个方法,但是也没办法直接进行调用,所以一般除了实现Cloneable之外,就是对Object中受保护的clone方法提供公有的访问途径。
既然Cloneable并没有包含任何方法,那么它到底有什么作用呢?它决定了Object中受保护的clone方法的行为,只有实现了Cloneable,Object的clone方法才能返回拷贝对象,否则会抛出异常。
拷贝对象往往会导致创建它的类的一个新实例,但它同时也会要求拷贝内部的数据结构。这个过程中没有调用构造器。
如果希望拷贝一个对象,超类提供这种功能的唯一途径是,返回一个通过调用super.clone而得到拷贝对象。但是我们一般在使用的时候需要特别注意浅拷贝和深拷贝。
简而言之,所有实现了Cloneable接口的类都必须用一个公有的方法覆盖clone,此公有方法首先调用super.clone,然后修正任何需要修正的域。一般情况下,就意味着要拷贝任何包含内部”深层结构”的可变对象,并用执行新对象的引用代替原来指向的这些对象的引用。
第12条:考虑实现Comparable接口
如果类实现了Comparable接口就表明它的实例具有内在的排序关系。
下面来看看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来进行比较。所以如果希望自己的对象拥有排序的能力,可以考虑实现这个接口。
相关文章推荐
- 我的头像——DIV+CSS3制作哆啦A梦头像
- 一言不合敲代码(1)——DIV+CSS3制作哆啦A梦头像
- 修改博客园博客样式
- HTML5自定义属性
- JS使用正则表达式
- Json转换利器Gson之实例一-简单对象转化和带泛型的List转化
- JavaScript学习笔记(13)——BOM
- CSS3 不定宽高水平居中
- 一般处理程序返回Json
- js代码片段【数制转换】【判断回文】
- jQuery基础知识整理(1)
- 『奇葩问题集锦』Cannot find module 'webpack/lib/node/NodeTemplatePlugin'
- HTML 5 画布(canvas)
- html5篇——音频/视频
- js 事件委托
- ES2015 - JavaScript (1)
- Jquery实现ajax三级联动
- JS控制图片显示的大小(图片等比例缩放)
- json跨域请求
- 一些JS特效