看完《Effective Java》感觉写的相当好,中间大部分都以作为参考手册,故整理出来。结合框架源码,jdk源码,自己的demo,给大家更多的示例,便于大家理解。每一条的介绍为个人理解,如果有任何不对,希望指出。对于博客有任何建议也希望给出建议。
10.覆盖 equals 时请遵守通用约定
equals设计的初衷时满足以下4点
- 每个实例唯一
- 类没有必要提供逻辑相等的测试功能,Object的equals已经足够
- 超类已经覆盖了equals,对于子类同样适用
- 类如果是私有的,确保它的equals永远不会被调用 如果你确实需要覆盖equals,需要满足5个属性
- 自反性:a=a
- 对称型:a=b => b=a
- 传递性:a=b,b=c => a=c
- 一致性:多次调用返回结果一致
- 非空性:任何值不等于null
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof Point)){
return false;
}
Point p = (Point)obj;
return p.x == x && p.y == y;
}
}
public class ColorPoint extends Point {
private final Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof ColorPoint)){
return false;
}
return super.equals(obj) && ((ColorPoint) obj).color == color;
}
}
public class TestDemo {
public static void main(String[] args) {
Point p = new Point(1,2);
ColorPoint cp = new ColorPoint(1,2, Color.RED);
//true
System.out.println(p.equals(cp));
//false
System.out.println(cp.equals(p));
}
}
书中举了如上例子,说明了自己编写equals的过程中可能导致的传递型问题,这个在jdk中也有对应的例子java.util.Date java.sql.Timestamp这两个类就有类似的问题。可以用下面这段代替instanceof,效果会好点。
if(obj == null || obj.getClass() != getClass()){
return false;
}
作者给出了几个提高equals的诀窍
- 使用==检测是否为这个引用(double,float不适用,这两个使用Float.compare,Double.compare,但是保险起见还是推荐BigDecimal)
- 使用instanceof 检测是否参数是否为正确类型
- 把参数转换成正确类型
- 对关键变量查看是否匹配 最后作者给出了几个忠告
- 覆盖equals时总要覆盖hashcode
- 不要让equals过于智能
- 不要让参数Object替换成其他类型 个人总结:尽量不要自己乱写equals,尽量使用ide自动生成的,各个方面都替你考虑到了。不过应该也没人会自己手写equals吧
public class EqualsDemo {
private Integer val1;
private Integer val2;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EqualsDemo that = (EqualsDemo) o;
return Objects.equals(val1, that.val1) &&
Objects.equals(val2, that.val2);
}
@Override
public int hashCode() {
return Objects.hash(val1, val2);
}
}
11.覆盖 equals 时总要覆盖 hash Code
这个不会有人不知道吧。网上博客应该也有一堆了。大致是如下这几个原则。
- 如果
a和b相等,那么a.equals(b)一定为true,则a.hashCode()必须等于b.hashCode()(来自于廖雪峰) - 如果
a和b不相等,那么a.equals(b)一定为false,则a.hashCode()和b.hashCode()尽量不要相等。(来自于廖雪峰) hashCode相等对象不一定相等。 因为hashcode其实是个映射函数,所以是有可能不同对象得到同一个值的,这时候就会用到equals进行比较。
hashcode还是非常重要的,最著名的应该就是HashMap里面对hash的用法,简直牛逼。 这里,
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//低16与高16位异或,增加复杂度
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//主要就是这里,n是2倍数,-1,就是从右向左全是1,再进行上面混淆过的hash值与运算,自动落入范围内
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
主要就是这里tab[i = (n - 1) & hash]主要就是这里,n是2倍数,-1,就是从右向左全是1高位全是0,再进行上面混淆过的hash值与运算,由于高位全是0,与的结果全是0,低位全是1,进行与操作能保证在范围内。
由于扩容全是2的倍数,此时再进行比较只要把链表遍历,比较高位那个值的不同。这hashcode运用的,简直了。所以,hashcode十分重要,不光是对象的标识,而且会参与运算。
12.始终要覆盖 toString
这个就没啥好说的了,基本现在生成一个类,必须带上toString,因为,大部分都是错误都是通过日志的方式,如果不覆盖就是com.mountain.monk.chapter3.Point@300ffa5d这种形式,完全无法调试,哪个参数有问题都不知道。
13.谨慎覆盖clone
Cloneable中没有任何方法,如果一个类实现了Cloneable接口,Object的clone方法返回该对象的逐域拷贝,如果一个类未实现Cloneable接口,则该对象就会抛出CloneNotSupportedException异常。
/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
* <p>
* Invoking Object's clone method on an instance that does not implement the
* <code>Cloneable</code> interface results in the exception
* <code>CloneNotSupportedException</code> being thrown.
* <p>
* By convention, classes that implement this interface should override
* <tt>Object.clone</tt> (which is protected) with a public method.
* See {@link java.lang.Object#clone()} for details on overriding this
* method.
* <p>
* Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
* Therefore, it is not possible to clone an object merely by virtue of the
* fact that it implements this interface. Even if the clone method is invoked
* reflectively, there is no guarantee that it will succeed.
*
* @author unascribed
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since JDK1.0
*/
public interface Cloneable {
}
clone主要目的就是会获取一个对象的拷贝,但是和原对象不同。相当于省略了new一个对象,然后一个一个成员变量的set了。但记住它是个浅拷贝,之前有过一次经历,我想要值相同但是引用完全不同的一个对象,由于clone是浅拷贝,引用始终是同一个。
public class CloneA {
}
@Data
public class CloneB implements Cloneable, Serializable {
private CloneA cloneA;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
CloneB b=new CloneB();
CloneA a=new CloneA();
b.setCloneA(a);
CloneB b1=(CloneB) b.clone();
System.out.println(b1.getCloneA()==a);//true
String s=JSON.toJSONString(b);
CloneB b2= JSON.parseObject(s,CloneB.class);
System.out.println(b2.getCloneA()==a);//false
CloneB b3=new CloneB();
BeanUtils.copyProperties(b,b3);
System.out.println(b3.getCloneA()==a);//true
}
由于clone是浅拷贝,后面也慢慢的通过BeanUtils.copyProperties(Object,Object);代替了,如果硬是想要clones深拷贝需要,内部引用对象也实现Cloneable,然后改写下clone方法如下所示
public class CloneA implements Cloneable{
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Data
public class CloneB implements Cloneable, Serializable {
private CloneA cloneA;
@Override
protected Object clone() throws CloneNotSupportedException {
CloneB cloneB=(CloneB) super.clone();
cloneB.setCloneA((CloneA) cloneB.getCloneA().clone());
return cloneB;
}
// @Override
// protected Object clone() throws CloneNotSupportedException {
// return super.clone();
// }
}
但是如果这么麻烦,还不如JSON序列化,反序列话了。 总的来说,clone我平常开发中基本没用到过,感觉十分鸡肋
14.考虑实现Comparable接口
基本上和Arrays.sort连用,要满足对称性和传递性,和equals一致,比较常用,到了java8可以和lambda表达式连用,简化写法。
十分简单常用,就不写例子了。