1.前言
最近在上软件构造课过程中老师讲到了利用双等号“==”和利用equals方法判断相等时候的区别,两者还是有很大的区别的,在之前的编程过程中我也一定程度上地混用过,于是就想发一篇总结出来避免大家再次出现类似的错误。
2.结论
上来先讲干货
1.双等号“==”:双等号描述的是引用等价性,故只有在两个变量的引用指向同一块物理地址空间的时候,结果才为真,其他任何情况结果都为假。
2.equals方法:equals方法描述的是对象等价性,在这个类(equals方法)的设计者认为两个对象应该相等的时候,就会返回真。在大多数合理设计的情况下,一般当这两个对象在客户端的视角里实质性相同的时候,就会返回真。
3.具体使用方式
1.在判断基本数据类型是否相等的时候,选择双等号“==”进行判定,因为java的基本数据类型采用的是存储真值+引用的方式,意即在栈区中存储一个这个基本数据类型的值,然后将所有赋值为相同值的同一类型变量的引用指向这一块内存空间,所以这时候使用==是安全的。
2.在判断其他类的对象是否相等的时候,尽量使用equals方法,因为即使对于immutable的类型的对象,即使这两个对象实质性相同,但是(对于其中的大部分)java并没有真值+引用的机制对他们进行处理,所以这时尽量使用equals方法。
针对第二点的后面有争议的说明,这里举例来说明:例如对于String类型,java就提供了上述的2种机制,即既有真值+引用的方式,又有直接作拷贝的方式,例如对于如下的代码:
public static void main(String[] args) {
String a = "123";
String a1 = "123";
String b = new String("123");
String b1 = new String("123");
System.out.println("==:");
System.out.println(a==a1);
System.out.println(a==b);
System.out.println(b==b1);
System.out.println("equals:");
System.out.println(b.equals(b1));
}
其执行结果如图所示:
可见,当String采用等号赋初值的时候采用的是基本数据类型的真值+引用模式,而当实例化一个String对象的时候,采用的就不是这种模式了。
4.重写equals方法
(1)为什么要重写equals方法:
因为我们写的任何类都是object类的子类,所以原始的equals方法是在Object类中定义的,而Object类并不知道我们之后的具体类的设计模式,所以Object类中equals方法采用的就是双等号==的判断,但是要是这样往往不是程序员想要的结果!所以需要重写equals方法。
(2)equal方法的设计原则
①equals方法必须满足自反、传递、对称的原则。
②不改不变性:除非一个对象被修改了,否则多次调用equals应有同样的结果。
③相等的对象,其hashCode()必须一致。这是因为在很多种容器类中,对象是按照散列(hash)的方式进行存储的,而散列的键就是hashcode,如果两个相同的对象具有不同的hashcode,他们就会被存储到散列桶中不同的节上,这时如果使用一个与待存储对象实质性相同的对象的hashcode去查询有可能会造成失败,所以一般要求重写equals方法的时候必须重写hashcode方法,除非你能保证你的ADT不会被放入hash类型的集合中。
(3)重写的一般过程
基本过程就是①判断是否是相同的类的对象②判断hashcode是否相等③按照设计原则,判断两个对象是否是实质性相同的(这里可以利用java中两相同类私有成员变量可以互访的原则比对成员变量)。下面举例说明,例如我写的一个immutable的边类,各个域定义如下:
class Edge<L> {
private final int weight;
private final L source;
private final L target;
// Abstraction function:
// AF(weight) is the weight of the edge
// AF(source) is the source vertex of the edge
// AF(target) is the target vertex of the edge
//other methods
}
然后对于这个类的equals方法,我就可以这样重写:
@Override
public boolean equals(Object obj) {
if(obj instanceof Edge<?>) {
Edge<?> tmp = (Edge<?>)obj;
if(tmp.hashCode()==this.hashCode()) {
return this.weight==tmp.weight&&this.target.equals(tmp.target)
&&this.source.equals(tmp.source);
}
}
return false;
}
就可以实现判断两个对象的相等了。
但是这里需要注意的一点是,我在第一步判断是否是同一个类的对象的时候使用了instanceof关键词,但是这个关键词有一个特性就是不仅相同类的对象会被判定为真,而且本类的父类和子类的对象同样会判定为真,故可能会造成一些误解。所以这里也可以使用getClass()方法来进行比对,这就不会有类似的问题了。
(4)重写的注意事项
①注意重写的返回值,函数名和参数一定要和Object类中定义的equals方法相同,若参数不同会变成重载,而不是重写。所以在重写时加上@Override标签,编译器会强制检查这一点。
②并不是重写equals就一定要重写hashcode方法,比如基本数据类型就不用,或者像之前提到的那样如果保证从不使用散列,可以不重写。
5.小结
在Java中双等号“==”和equals方法还是有实质性的区别的,忽略这一点常常会造成意想不到的错误。“==”判断的是引用等价性,只有当两个对象引用同一块内存空间时才会为真,而equals往往是由类设计者设计的判断两个对象是否是实质性相同的,可用来判断两个对象的观察等价性。而在我们自行设计类的时候,也要及时重写equals方法,遵循重写的原则和Object的contrast进行重写,究竟什么样的类需要重写,什么样的不需要,我将在下篇文章中进行总结。