EffectiveJava——第三章 对于所有对象都通用的方法
2021/6/19 17:26:59
本文主要是介绍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
...未完
这篇关于EffectiveJava——第三章 对于所有对象都通用的方法的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-06-26结对编程到底难不难?答案在这里
- 2024-06-19《2023版Java工程师》课程升级公告
- 2024-06-15matplotlib作图不显示3D图,怎么办?
- 2024-06-1503-Loki 日志监控
- 2024-06-1504-让LLM理解知识 -Prompt
- 2024-06-05做软件测试需要懂代码吗?
- 2024-06-0514-ShardingSphere的分布式主键实现
- 2024-06-03为什么以及如何要进行架构设计权衡?
- 2024-05-31全网首发第二弹!软考2024年5月《软件设计师》真题+解析+答案!(11-20题)
- 2024-05-31全网首发!软考2024年5月《软件设计师》真题+解析+答案!(21-30题)