EffectiveJava——第三章 对于所有对象都通用的方法
作者:互联网
对于所有对象都通用的方法
Object类中有很多通用方法,比如equals
、toString
、hashCode
,还有实现了Comparable
的类,它们的方法都有明确的约定,如果你想你的类能与其他类良好的工作在一起,请遵守这些约定。
覆盖equals方法
其实很多时候equals
方法根本不需要被覆盖:
- 当类的每个实例都是唯一的 很多时候我们设计的类就是这样的,只有相同的实例才相等,而不是依赖某个属性来判等,比如Thread类。
- 类没有必要提供逻辑相等的测试功能 比如Pattern类,它没有实现equals,确实,仔细想想,判断两个正则表达式是否相等的需求真的没啥用。
- 超类的equals实现正适用于本类 Java的集合类中的equals方法基本都是继承自祖先的。
- 可以确保类的equals永远不会被调用 比如类是包级私有的,静态的等等。
只有当我们自己设计一个“值类”的时候,才需要实现equals方法。
equals方法规范
- 自反性 对于任何非null的引用值
x
,x.equals(x)==true
- 对称性 对于任何非null的引用值
x,y
,x.equals(y) == y.equals(x)
- 传递性 对于任何非null的引用值
x,y,z
,如果x.equals(y)==y.equals(z)==a
那么x.equals(z)==a
a是一个布尔值 - 一致性 对于任何非null的引用值
x,y
,只要多次调用过程中,equals使用的到的属性没有改变,那么多次调用的结果也不应该改变 - 对于任何一个非null的引用值
x
,x.equals(null)==false
这些规范看着有点让人感觉像是回到了数学课上,但是不遵循这些规范会带来一些潜在的后果。
违反自反性
对于自反性,如果一个类不能在equals中遵循自反性,那么Set的contains
方法就可能没法返回正常的值。集合中很可能包含很多完全相同的实例。
违反对称性
对于违反对称性,看下面的一个例子
class CaseInsensitiveString{
private final String s;
public CaseInsensitiveString(String s){
this.s = s;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof String)
return s.equalsIgnoreCase((String) o);
if (o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
return false;
}
}
public class EqualsTest {
public static void main(String[] args) {
String string = "HelloWorld";
CaseInsensitiveString ciString = new CaseInsensitiveString("helloworld");
System.out.println(ciString.equals(string));
System.out.println(string.equals(ciString));
}
}
CaseInsensitiveString
使用委托实现了一个对大小写不敏感的字符串类。如果你运行这段程序,你会发现,主函数中的第一条输出语句是true
,第二条是false
,这已经违反了对称性。
原因不难看出,CaseInsensitiveString
的equals
方法第二行做了一个画蛇添足的操作,如果你传入一个String
对象,它仍然会按照忽略大小写的模式进行对比,但如果你用String
的实例去和CaseInsensitiveString
对比,显然,String
肯定不知道它是个什么牛马,直接返回false。看似一个聪明的,使该类支持原生String
的做法,却可能会酿成大祸。
CaseInsensitiveString
这个不明智的做法可能使他在不同的集合中产生不同的效果,例如如下的代码,它返回什么呢?
List<CaseInsensitiveString> list = new ArrayList<>();
list.add(ciString);
System.out.println(list.contains(string));
完全取决于集合中contains
方法调用equals
的前后顺序。
解决问题很简单,别耍这种小聪明就行了。
违反传递性
违反传递性通常出现在子类和父类的比较中。
class Point{
private int x,y;
public Point(int x,int y){
this.x = x;this.y = y;
}
@Override public boolean equals(Object o){
if (!(o instanceof Point))
return false;
Point p = (Point) o;
return x == p.x && y == p.y;
}
}
class ColorPoint extends Point{
private int color;
public ColorPoint(int x, int y,int color) {
super(x, y);
this.color = color;
}
@Override public boolean equals(Object o){
if (o instanceof ColorPoint)
return super.equals(o) && color == ((ColorPoint)o).color;
if (o instanceof Point)
return super.equals(o);
return false;
}
}
public class EqualsTest {
public static void main(String[] args) {
ColorPoint colorPoint1 = new ColorPoint(1,2,0xff0000);
Point point = new Point(1,2);
ColorPoint colorPoint2 = new ColorPoint(1,2,0xffffff);
System.out.println(colorPoint1.equals(point));
System.out.println(point.equals(colorPoint2));
System.out.println(colorPoint1.equals(colorPoint2));
}
}
这段代码违反了传递性,造成问题的原因是ColorPoint
在和Point
类型比较的时候,忽略了颜色信息。
这个问题似乎无法解决,如果你想让Point
和Point
的子类能够判等的话,那就永远无法绕过Point
没有子类新增加的属性的问题。
一个可选的办法就是不适用继承,而采取组合,并提供一个父类对象的视图,如何判断,全凭用户取舍:
class ColorPoint2 {
private final Point point;
private final int color;
public ColorPoint2(Point point,int color){
this.point = point;
this.color = color;
}
public Point asPoint(){
return point;
}
@Override
public boolean equals(Object o){
if (!(o instanceof ColorPoint))
return false;
ColorPoint2 c = (ColorPoint2) o;
return point.equals(c.point) && color == c.color;
}
}
在一个抽象类的子类中增加新的属性就不会出现这种问题。因为你无法创建这个抽象的父类。
违反一致性
java类库中URL类的实现就没遵循一致性,因为它比较时依赖了网络资源。
// URL.equals中调用了handler.equals进行判断两个URL是否相等
public boolean equals(Object obj) {
if (!(obj instanceof URL))
return false;
URL u2 = (URL)obj;
return handler.equals(this, u2);
}
// handler.equals 中调用了sameFile判断了是否是同一个文件
protected boolean equals(URL u1, URL u2) {
String ref1 = u1.getRef();
String ref2 = u2.getRef();
return (ref1 == ref2 || (ref1 != null && ref1.equals(ref2))) &&
sameFile(u1, u2);
}
// handler.sameFile 做了很多确认操作,我这里省略了,最后它使用hostEquals判断了两个URL的主机是否一致
protected boolean sameFile(URL u1, URL u2) {
// ...省略不重要代码
// Compare the hosts.
if (!hostsEqual(u1, u2))
return false;
return true;
}
// handler.hostEqual 中进行了一些网络操作,将URL转换成host地址
protected boolean hostsEqual(URL u1, URL u2) {
InetAddress a1 = getHostAddress(u1);
InetAddress a2 = getHostAddress(u2);
// if we have internet address for both, compare them
if (a1 != null && a2 != null) {
return a1.equals(a2);
// else, if both have host names, compare them
} else if (u1.getHost() != null && u2.getHost() != null)
return u1.getHost().equalsIgnoreCase(u2.getHost());
else
return u1.getHost() == null && u2.getHost() == null;
}
问题在于,随着时间,这个URL很可能被绑定到其它的主机上,原来的u1.equals(u2)
可能和之后的u1.equals(u2)
产生不同的结果。
所以equals
中不要依赖不确定不可靠的资源进行判断。
保证非空性
很多时候我们为了保证非空性会写这样的代码:
@Override public boolean equals(Object o){
if(o==null)return;
if(o instanceof Clz){...}
return false;
}
其实这个方法的第一行是没用的,因为instanceof已经会帮助你判空了。它在o为null的时候会返回false。
推荐的写法
@Override public boolean equals(Object o){
if (this == o)return true;
if (!(o instanceof Point))
return false;
Point p = (Point) o;
return x == p.x && y == p.y;
}
- 判断this和传入类的引用是否一致,这对于大对象的比较将节省很多时间
- 判断是否是同类型
- 转换类型
- 将所有重要的属性比较
这也是很多IDE自带的生成工具的写法。
好习惯
- 覆盖equals时尽量覆盖hashCode
- 不要企图让equals过于智能,往往是负优化
- 不要将equals的参数改为其他类型,这样做是重载(Overload)不是重写(Override)。
- 尽可能使用ide自带的equals实现
- 如非必要请勿轻易覆盖equals
...未完
标签:return,Point,URL,equals,EffectiveJava,通用,null,public,第三章 来源: https://www.cnblogs.com/lilpig/p/14903286.html