目录
作为Java程序员,你真的了解equals方法吗
在Java
编程语言中,一切皆对象。而所有类的超类都是Object
对象,本文我们来了解一下Object
类中的equals
方法。
Object类
首先我们来看一下Object
类中equals
方法的定义:
public class Object {
public Object() {};
public boolean equals(Object obj) {
return (this == obj);
}
}
equals
方法用来表明其他对象是否和当前对象相等。
如果当前对象是非空对象,则equals
方法需要满足如下性质:
(1)自反性:对于任何非空对象x,x.equals(x)
应该返回true
,即一个对象应该和它本省相等。
(2)对称性:对于任何非空对象x和y,当且仅当y.equals(x)
返回true
时,x.equals(y)
返回true
。
(3)可传递性:对于任何非空对象x、y和z,如果x.equals(y)
返回true
,y.equals(z)
返回true
,那么x.equals(z)
应该返回true
。
(4)一致性:对于任何非空的对象x和y,x.equals(y)
的多次调用始终返回true
或false
,前提是没有修改对象上equals
比较中使用的信息。
(5)对于任何非空对象x,x.equals(null)
应返回false
,即任何非空对象都和空对象不相等。
对于Object
类实现的equals
方法来说,它肯定是满足上面五个条件的。那么,对于我们自定义的类来说,要不是对equals
方法进行重写?如果需要进行重写,应该如何进行重写来满足上面五个条件呢?
到底要不要重写equals方法?
不需要重写equals
方法的几种场景:
(1)类的每个实例本质上都是唯一的,比如Thread
,或枚举类;
(2)类没有必要提供“逻辑相等”的功能;
(3)超类已经实现了equals
方法,而且超类的行为对于子类也是合适的,比如Set
接口的大多数实现类直接继承AbstractSet
的equals
实现;
(4)对于private
或protected
的类,可以确保它的equals
方法永远不会被调用。
需要重写equals
方法的集中场景:
如果类具有自己特有的“逻辑相等”概念,而且超类还没有重写equals
方法。我们在调用equals
方法的时候,希望知道两个对象在“逻辑上”是否相等,而不是它们是否指向了同一个对象,这个时候就必须重写equals
方法,比如Integer
或String
类。
高质量重写equals方法的诀窍
(1)使用==
操作符首先检查“参数是否为这个对象的引用”,如果是,则返回true
,这是一种性能优化。
(2)使用instanceof
操作符检查“参数是否为正确的类型”,如果不是,则返回false
。一般而言,正确的类型指的是“equals
方法所在的类”。某些情况下,是指该类实现的某个接口。如果类实现的接口改进了equals
约定,允许在实现了该接口的类之间进行比较,那么就使用接口,比如Set
、Map
、List
。
(3)将参数转换成正确的类型。
(4)对于该类中的每个关键属性,检查参数中的值是否与该对象中的值匹配。如果这些测试全都成功,则返回true
;否则返回false
。
代码实战
定义一个Point
类,有两个属性x
和y
分别表示横坐标和纵坐标。两个点相等的条件是:横坐标相等&纵坐标相等。
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Point)) {
return false;
}
Point point = (Point) o;
return x == point.x && y == point.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
ColorPoint
类增加了Color
属性,但是不重写父类Point
的equals
方法,认为点的坐标只要相等,两个点(无论是Point
还是ColorPoint
)就是相等的。
public class ColorPoint extends Point{
private final Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
}
测试代码
class PointTest {
@Test
void should_return_true_when_compare_x_and_x() {
Point x = new Point(3, 5);
assertTrue(x.equals(x));
}
@Test
void should_return_false_when_compare_x_and_null() {
Point x = new Point(3, 5);
assertFalse(x.equals(null));
}
@Test
void should_return_false_compare_a_point_and_non_point_object() {
Point x = new Point(3, 5);
assertFalse(x.equals("(3, 4)"));
}
@Test
void should_return_true_when_compare_a_point_and_another_point_given_same_x_and_y() {
Point x = new Point(3, 5);
Point y = new Point(3, 5);
assertTrue(x.equals(y));
assertTrue(y.equals(x));
}
@Test
void should_return_true_when_compare_a_point_and_another_point_given_same_x_and_y() {
Point x = new Point(3, 5);
ColorPoint y = new ColorPoint(3, 5, new Color(1111));
assertTrue(x.equals(y));
assertTrue(y.equals(x));
}
}
不同数据类型的比较方法
(1)对于对象,可以递归地调用equals
方法
(2)对于非float
和double
的基本类型,使用==
操作符比较即可;对于float
和double
类型,分别使用静态方法Float.compare(float, float)
和Double.compare(double, double)
进行比较。
(3)对于数组,则要将上面原则应用到每一个元素上。如果要比较数组所有元素,可以使用Arrays.equals
方法。
equals方法相关的其他一些tips
(1)重写equals
方法时一定要同时重写hashCode
方法;
(2)在重写equals
方法的时候不要改变参数的类型
public class Point {
...
public boolean equals(Point p) {
...
}
上面这种写法是对equals
方法进行了重载(overload
),而不是重写(override
)。
总结
(1)除非确实有必要,否则不要重写equals
方法。
(2)重写equals
方法时,要注意是否满足了自反性、对称性、传递性、一致性。