当前位置:首页 > 开发 > 编程语言 > 编程 > 正文

Object.equals方法:重载还是覆盖

发表于: 2015-05-25   作者:Cwind   来源:转载   浏览:
摘要: 本文译自StackOverflow上对此问题的讨论。 原问题链接   在阅读Joshua Bloch的《Effective Java(第二版)》第8条“覆盖equals时请遵守通用约定”时对如下论述有疑问: “不要将equals声明中的Object对象替换为其他的类型。程序员编写出下面这样的equals方法并不鲜见,这会使程序员花上数个小时都搞不清它为什么不能正常工作:” pu

本文译自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)。

Object.equals方法:重载还是覆盖

  • 0

    开心

    开心

  • 0

    板砖

    板砖

  • 0

    感动

    感动

  • 0

    有用

    有用

  • 0

    疑问

    疑问

  • 0

    难过

    难过

  • 0

    无聊

    无聊

  • 0

    震惊

    震惊

编辑推荐
重载、重写、隐藏这三个概念对于很多人都不是很清晰,我也是差了很多资料又请教师哥才感觉能理解了
1.重载是一个类内部实现相同机理的操作,但是操作的对象不同。主要体现在: 方法在同一个类中 重载
所谓方法,就是给某个动作或功能取得名字,通过名字,你可以引用所有的对象和方法。不同的名字对应
一、前言: 接触面向对象的思想已经有一段时光了,为什么要学习面向对象呢?因为面向对象具有可复用
代码编译运行环境:VS2012+Debug+Win32 1.函数重载 1.1函数重载的定义 C++规定在同一作用域中,同名
C++在继承这一节涉及到函数覆盖 隐藏 重载等,如何区别它们呢? 首先函数覆盖的条件:<1>基类函
一、 C++成员函数的重载 C++中的成员函数有四种,分别是普通成员函数,virtual虚函数,const成员函
阅读目录   一:WebService中的方法是否允许重载?   二:为什么WebService中不支持方法的重载
方法的重载就是在同一个类中可以同时存在多个同名的方法,只要这些方法的参数或类型不同。方法重载
  阅读目录   一:WebService中的方法是否允许重载?   二:为什么WebService中不支持方法的
版权所有 IT知识库 CopyRight © 2009-2015 IT知识库 IT610.com , All Rights Reserved. 京ICP备09083238号