本文译自StackOverflow上对此问题的讨论。
在阅读Joshua Bloch的《Effective Java(第二版)》第8条“覆盖equals时请遵守通用约定”时对如下论述有疑问:
“不要将equals声明中的Object对象替换为其他的类型。程序员编写出下面这样的equals方法并不鲜见,这会使程序员花上数个小时都搞不清它为什么不能正常工作:”
public boolean equals(MyClass o) { //... }
“问题在于,这个方法并没有覆盖(override)Object.equals,因为它的参数应当是Object类型,相反,它重载(overload)了Object.equals。”
问题:
为何代码示例中的强类型的equals方法重载并不足够?书中提到重载而非覆盖会引起问题,但并未论述为何如此也没有说明在何种场景下会使得equals方法失败。
回答:
这是因为重载此方法并不会改变集合类或者其他地方显式调用equals(Object)的行为。例如:
public class MyClass { public boolean equals(MyClass m) { return true; } }
如果把它放到HashSet中:
public static void main(String[] args) { Set<MyClass> myClasses = new HashSet<>(); myClasses.add(new MyClass()); myClasses.add(new MyClass()); System.out.println(myClasses.size()); }
上面程序将会打印出2,而不是1。虽然你期望所有的MyClass实例经由重载方法判断都是相等,并且集合不会添加第二个实例。
所以基本上,即使下面表达式为true:
MyClass myClass = new MyClass(); new MyClass().equals(myClass);
下述表达式依然为false:
Object o = new MyClass(); new MyClass().equals(o);
后一个表达式是集合或其他类用于判断相等性的。事实上,只有当参数显式地为MyClass或其子类型的实例时,才会调用到重载方法并返回true。
关于覆盖还是重载的问题:
让我们从覆盖和重载的区别说起。通过覆盖,你事实上重新定义了这个方法。事实上相当于你删除了方法原始的实现并替换为自己的实现。所以当你这样做时:
@Override public boolean equals(Object o) { ... }
你事实上重新链接了你的equals实现以取代Object类(或者实现该方法的最后一个父类)中的实现。
另一方面,当你这样做:
public boolean equals(MyClass m) { ... }
你定义了一个全新的方法,因为你定义了一个拥有同样名字但是不同参数列表的方法。当HashSet调用equals时,它调用的是参数类型为Object的方法。
Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
(上述代码来自HashMap.put,被用作HashSet.add的底层实现。)
为了更清楚,MyClass中的equals方法只有当被覆盖时才会被调用,而不是被重载的时候。如果你试图在一个重载的equals方法上添加@Override注解,它将会产生一个编译错误,指出它并没有覆盖一个方法。我们可以在一个类中声明两个equals方法,因为这是重载:
public class MyClass { @Override public boolean equals(Object o) { return false; } public boolean equals(MyClass m) { return true; } }
泛型
谈到泛型,equals方法并不是泛型。它显式地要求Object作为它的参数类型。当你试图这样做时:
public class MyGenericClass<T> { public boolean equals(T t) { return false; } }
它将不会编译,错误信息:命名冲突,MyGenericClass的equals(T)方法类型擦除后与Object类中equals(Object)相同,但并未覆盖它
Name clash: The method equals(T) of type MyGenericClass has the same erasure as equals(Object) of type Object but does not override it
当添加@Override注解时:
public class MyGenericClass<T> { @Override public boolean equals(T t) { return false; } }
错误信息变为:MyGenericClass的equals(T)方法必须覆盖或实现父类方法
The method equals(T) of type MyGenericClass must override or implement a supertype method
于是怎么做都会有问题。原因在于Java通过类型擦除实现泛型。当Java在编译阶段检查完所有的泛型类型,事实上的运行时对象都会被Object取代。无论何时你看到T类型,事实上的字节码都会包含Object。这就是为何反射不能用于泛型类型以及list instanceof List<String>将会出错的原因。
同样,这也使你无法重载泛型类型,如果有这样的类:
public class Example<T> { public void add(Object o) { ... } public void add(T t) { ... } }
add(T)方法将会产生编译错误,因为类完成编译时,两个方法将会有同样的签名,public void add(Object)。
相关推荐
本文描述重载equals方法的技术,这种技术即使是具现类的子类增加了字段也能保证equal语义的正确性。 在《Effective Java》的第8项中,Josh Bloch描述了当继承类作为面向对象语言中的等价关系的基础问题,要...
重载是方法的名称相同。参数或参数类型不同,进行多次重载以适应不同的需要 Override 是进行基类中函数的重写。为了适应需要。 6.如果在一个B/S结构的系统中需要传递变量值,但是又不能使用Session、Cookie、...
7.5 方法重载(overload):给汽车加速添个限制 168 7.5.1 什么是方法的签名 168 7.5.2 什么是重载?为什么要重载? 168 7.5.3 给汽车加个重载的方法 169 7.5.4 测试一下 169 7.5.5 重载容易引发误解的两个地方...
8.7.1 Object.Equals 210 8.7.2 Object.GetHashCode 212 8.7.3 Object.ToString 213 8.8 Uri 214 8.9 System.Xml的使用 216 8.10 相等性操作符 218 8.10.1 值类型的相等性操作符 218 8.10.2 引用类型...
8.7.1 Object.Equals 210 8.7.2 Object.GetHashCode 212 8.7.3 Object.ToString 213 8.8 Uri 214 8.9 System.Xml的使用 216 8.10 相等性操作符 218 8.10.1 值类型的相等性操作符 218 8.10.2 引用类型...
equals()方法和hashCode()方法 270 数据结构 273 Array方法类汇总 304 Java数组与集合小结 305 递归 309 对象的序列化 310 Java两种线程类:Thread和Runnable 315 Java锁小结 321 java.util.concurrent.locks包下...
7.5 方法重载(overload):给汽车加速添个限制 168 7.5.1 什么是方法的签名 168 7.5.2 什么是重载?为什么要重载? 168 7.5.3 给汽车加个重载的方法 169 7.5.4 测试一下 169 7.5.5 重载容易引发误解的两个地方...
Override [java] 方法的覆盖(覆盖父类的方法) [,әuvә'raid] polymiorphism[java] 多态 (polymorphism 多形性[,pɒli'mɒ:fizm]) allowing a single object to be seen as having many types. principle n.原则,...
7.Java多态的实现(继承、重载、覆盖) 8.编码转换,怎样实现将GB2312编码的字符串转换为ISO-8859-1编码的字符串。 9.Java中访问数据库的步骤,Statement和PreparedStatement之间的区别。 10.找出下列代码可能...
就需要重载很多方法进行排序操作。而在Java类库中有一个Arrays类的sort方法已经实现各种数据类型的排序算法。程序员只需要调用该类的方法即可。 代码演示:Arrays实现排序 public static void main(String[] args) ...
4.5 继承中的方法的覆盖和重载 65 4.6 多态和动态绑定 66 4.7 动态绑定 69 4.8 instanceof 运算符 70 4.9 多态对象的类型转换 71 4.10 Java static关键字以及Java静态变量和静态方法 72 4.11 static 的内存分配 73 ...
9.3.3 静态方法覆盖 9.3.4 覆盖与异常抛出 9.3.5 抽象方法覆盖 9.3.6 覆盖与重载 9.4 静态绑定与动态绑定 9.4.1 静态绑定 9.4.2 动态绑定 9.5 抽象类 9.5.1 抽象类概念 9.5.2 抽象类应用 9.5.3 抽象类注意...
Object类的equals()方法 ArrayList类 关于列表的一些有用的方法 自定义栈类 Protected数据和方法 防止继承和重写;利用继承性由父类创建子类 使用super调用父类构造方法和方法 在子类中覆盖父类的方法 描述类型转换和...
•Object类提供的equals方法判断两个对象相等的标准与==完全相同。因此开发者通常需要重写equals方法。 类成员 •在java类里只能包含Field,方法,构造器,初始化块,内部类(接口、枚举)等5种成员。 用...