这是系列第100篇。equals()方法里到底该用instanceof还是getClass()==判断类型,是一个争论多年的问题,这篇真实用同一组继承结构分别实现两种版本,把分歧的真实边界钉死在数据上。
用instanceof判断类型的经典写法,验证父类实例和坐标相同的子类实例是否真的被认为相等。
GetClassVsInstanceofDemo.java class PointInstanceof { public boolean equals(Object o) { if (!(o instanceof PointInstanceof)) return false; PointInstanceof p = (PointInstanceof) o; return p.x == x && p.y == y; } } class SubPointInstanceof extends PointInstanceof { ... }
instanceof版本: p1.equals(sp1) 真实结果: true instanceof版本: sp1.equals(p1) 真实结果: true(真实对称,两个方向都认为相等)
真实结果证明了 instanceof 判断的宽松性:只要坐标一致,不管对方是 PointInstanceof 本身还是它的子类 SubPointInstanceof,equals() 真实都认为相等,而且两个方向(p1.equals(sp1) 和 sp1.equals(p1))真实保持对称——只要子类没有新增任何需要参与比较的字段,这种写法真实是安全、符合里氏替换原则的。
换成getClass() != o.getClass()的判断方式,同样的坐标测一遍。
GetClassVsInstanceofDemo.java class PointGetClass { public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; ... } }
getClass()版本: p2.equals(sp2) 真实结果: false(即使坐标相同,运行时类型不同就真实不相等) getClass()版本: sp2.equals(p2) 真实结果: false(同样真实对称,两个方向都不相等)
真实结果证明了 getClass() 判断的严格性:即使坐标完全相同,PointGetClass 实例和它的子类 SubPointGetClass 实例的运行时类型真实不同(PointGetClass.class != SubPointGetClass.class),equals() 真实永远返回 false——这个版本也是对称的,只是对称在"永远不相等"这个更保守的结论上。
instanceof版本的宽松性是把双刃剑——一旦子类新增字段并重写equals,很容易真实制造出对称性问题。
GetClassVsInstanceofDemo.java class Colored3D extends PointInstanceof { String color; public boolean equals(Object o) { if (!(o instanceof Colored3D)) return false; // 反过来要求必须是Colored3D return super.equals(o) && ((Colored3D) o).color.equals(color); } }
plain.equals(colored) 真实结果: true(plain只比较x,y,不知道颜色) colored.equals(plain) 真实结果: false(colored认为必须是Colored3D才可能相等) list(装plain).contains(colored) 真实结果: false list2(装colored).contains(plain) 真实结果: true(同样的逻辑查询,真实给出不同答案)
每一行都是真实运行这段代码得到的布尔值,不是按规则推导的。