第十条:覆盖equals时请遵守通用的约定
类具有特有的逻辑相等的概念,且超类没有覆盖equals方法时应该覆盖equals方法,例如integer、String这种“值类”。
但是有一种值值类无需覆盖equals,即实例受控,每个值最多只存在一个对象的类,比如枚举类,这种类逻辑相同和对象相同是同一回事,所以Object的equals方法等她与逻辑的equals。
equals必须满足四种等价关系:自反性、对称性、传递性、一致性,并且对于非null的x,x.equals(null)永远返回false.
对称性:
CaseString类equals方法比较不区分大小写的字符串,问题在与CaseString的equals方法知道普通类型的字符串,但是String的equals方法并不知道CaseString,比较时违背了对称性。
public class EqualsDemo { public static void main(String[] args) { CaseString caseString = new CaseString("Case"); String string = "case"; System.out.println(caseString.equals(string)); System.out.println(string.equals(caseString)); } } final class CaseString { private final String s; public CaseString(String s) { this.s = Objects.requireNonNull(s); } @Override public boolean equals(Object obj) { if (obj instanceof CaseString) { return s.equalsIgnoreCase(((CaseString) obj).s); } if (obj instanceof String) { return s.equalsIgnoreCase((String) obj); } return false; } }
解决方式:equals方法中去除与String类型比较的部分。
@Override public boolean equals(Object obj) { return obj instanceof CaseString && s.equalsIgnoreCase(((CaseString) obj).s); }
传递性:
子类增加的信息会影响equals的比较结果,我们无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定。但是可以在抽象类的子类增加新的组件且不违反equals的规定。
一致性:
如果两个对象相等,他们就要一直保持相等,所以不要使equals方法依赖不可靠的资源。
非空性:
指所有的对象都不能等于null,大多数情况下一个非空对象equals null,都会返回false(返回true的情况我还没有想到),很多类的equals方法中为了避免抛出NPE,会先判读入参是否为null,其实这是没有必要的,因为equals方法在比较之前,必须是呀instanceof判断Object类型的入参是否为比较类型子类的对象,如果instanceof的第一个操作数是null,那么不管第二个操作数是什么类型,都会返回false,所以不需要显式的null检查。
ps:
1.对于非float和double类型的基本数据类型,可以使用==比较,对于float和double类型,可以使用Float.compare(x,y)和Double.compare(x,y),因为存在Float.NAN,-0.0f之类的常量,如果使用Float.equals()或Double.equals()会对基本类型进行装箱操作,降低性能.
2.不要将equals方法参数中Object对象替换成其他类型的对象,如果替换的话,它将不会重写Object.equals()而是重载。
第11条:覆盖equals时总要覆盖hashCode
在每个覆盖了equals方法的类中都要覆盖hashCode方法,否则这种类对象元素的散列集合(如HashMap、HashSet)将无法正常运行。
例:
public class EqualsDemo {
public static void main(String[] args) {
CaseString caseString = new CaseString("Case");
String string = "case";
CaseString caseString1 = new CaseString("Case");
System.out.println(caseString.equals(caseString1));
HashMap<CaseString, Integer> map = new HashMap<>();
map.put(caseString, 1);
map.put(caseString1, 2);
System.out.println(map);
CaseString caseString3 = new CaseString("test");
CaseString caseString4 = new CaseString("test");
map.put(caseString3, 3);
System.out.println(map.get(caseString4));
}
}
final class CaseString {
private final String s;
public CaseString(String s) {
this.s = Objects.requireNonNull(s);
}
@Override
public boolean equals(Object obj) {
return obj instanceof CaseString && s.equalsIgnoreCase(((CaseString) obj).s);
}
}
输出:
equals和HashCode比较规则:
equals相等->hashCode一定相等
hashCode相等->equals不一定相等
hashCode不等->equals一定不相等
所以在比较时,会优先比较hashCode,hashCode相等后再比较equals,所以不同的对象生成不同的hash值,会提高比较的性能。
第12条:始终要覆盖toString方法
返回值的关注的信息,易于调试。
第13条:谨慎地覆盖clone
Cloneable接口是一个标记接口,表示实现了这个接口的类可以被克隆,object的clone方法返回该对象的逐级拷贝,否则抛出CloneNotSupportedException异常。实现cloneable接口的类是为了提供一个功能适当的公有clone方法,clone方法无需调用构造器就可以创建对象。
clone方法约定:
1. x.clone != x
2. x.clone.getClass == x.getClass
3. x.clone.equals(x) == true
【未完待续】
原文:https://www.cnblogs.com/youtang/p/12152849.html