概述
学习Java少不了对Object的认知,所有类都会继承它的属性,真正的超类。这一个系列,我会对Object中的几个方法,也就是我们自定义类的时候需要重写的几个方法做一个介绍。下面是这一个系列的主要内容:
- equals方法
- hashCode方法
- toString方法
- clone方法
- 自定义类时考虑实现Comparable接口
本系列内容源于对《Effective Java》中文第二版第8条到第12条的学习记录。所有内容的准确性均以原书为准。
1,引言
我想很多刚刚参加工作的java程序员在面试中都被问到过“==”和equals的区别,说实话,我以前不仅被问到过,而且还没有很好的答上来,现在如果被问到,我想下面的答案应该是可以满足基本要求的:
(1) 在不重写equals方法的时候,其和“==”功能是一样的;
(2)在重写了之后,它们的区别在于你是如何重写equals方法的,通常的做法是用于比较两个对象的值是否相同;我们重写equals方法需要遵循以下几条规则:
- 自反性:对于任何非空引用值
x
,x.equals(x)
都应返回true
。 - 对称性:对于任何非空引用值
x
和y
,当且仅当y.equals(x)
返回true
时,x.equals(y)
才应返回true
。 - 传递性:对于任何非空引用值
x
、y
和z
,如果x.equals(y)
返回true
,并且y.equals(z)
返回true
,那么x.equals(z)
应返回true
。 - 一致性:对于任何非空引用值
x
和y
,多次调用 x.equals(y) 始终返回true
或始终返回false
,前提是对象上equals
比较中所用的信息没有被修改。 - 对于任何非空引用值
x
,x.equals(null)
都应返回false
。
(3)扩展:如果对象的hashCode值计算方法足够优秀,我们可以直接通过比较对象的hashCode值作为equals方法的比较结果)
2,分析
上面简单的介绍了一下equals方法和“==”的区别,那我们就来看看每一条的分析:
(1)针对第一条,我们看一看Object中equals方法的源码:
public boolean equals(Object obj) {
return (this == obj);
}
我想也就不用再多说上面了,他们比较的也是两个非空对象的引用
(2)重写了equals方法之后,区别自然是你的equals方法是如何定义的,那下面就来仔细探讨一下equals方法该如何定义:
- 自反性 对于任何非空引用值
x
,x.equals(x)
都应返回true
- 对称性 对于任何非空引用值
x
和y
,当且仅当y.equals(x)
返回true
时,x.equals(y)
才应返回true
这里需要注意在出现继承关系时的处理,比如一个Person类:
package hfut.edu;
/**
* Date:2018年10月1日 上午11:05:45 Author:why
*/
public class Person {
int age;
String name;
String sex;
public Person(int age, String name, String sex) {
super();
this.age = age;
this.name = name;
this.sex = sex;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
if (!(obj instanceof Person))
return false;
Person p = (Person) obj;
return this.age == p.age && this.name.equals(p.name) && this.sex.equals(p.sex);
}
}
这里面我们定义了三个成员变量并且重写了equals方法,如果一个Student类继承Person类并且新增加了两个成员变量如下:
package hfut.edu;
/**
* Date:2018年10月1日 上午11:15:07
* Author:why
*/
public class Student extends Person {
int studentID;
String schoolName;
public Student(int age, String name, String sex, int studentID,String SchoolName) {
super(age, name, sex);
this.studentID=studentID;
this.schoolName=schoolName;
}
}
现在,我想通过equals方法比较两个学生,如果直接只用继承Person类的,则新的成员变量比较不了,显然不合适,如果重写添加新的属性比如:
@Override
public boolean equals(Object obj) {
if(!(obj instanceof Student))
return false;
Student stu=(Student)obj;
return super.equals(obj)&&stu.schoolName.equals(this.schoolName)&&stu.studentID==this.studentID;
}
使用测试程序有:
public class TestClass {
public static void main(String[] args) {
Person p=new Person(26,"why","male");
Student stu=new Student(26,"why","male",2020,"hfut");
System.out.println("p.equals(stu)="+p.equals(stu));
System.out.println("stu.equals(p)="+stu.equals(p));
}
结果:
很显然是违背了对称性的,下面来看一下解决办法,把Student类中的equals方法改成:
@Override
public boolean equals(Object obj) {
if(!(obj instanceof Person))
return false;
if(!(obj instanceof Student))
return obj.equals(this);
Student stu=(Student)obj;
return super.equals(obj)&&stu.schoolName.equals(this.schoolName)&&stu.studentID==this.studentID;
}
结果:
这样,对称性的问题算是解决了。那么,在Java的API里面有没有这样错误示例了,下面我们就来看一个:
所以,在Java平台类库中是有违背equals约定的示例的。参考:
Date中equals源码:
public boolean equals(Object obj) {
return obj instanceof Date && getTime() == ((Date) obj).getTime();
}
TimeStamp中equals源码:
public boolean equals(Timestamp ts) {
if (super.equals(ts)) {
if (nanos == ts.nanos) {
return true;
} else {
return false;
}
} else {
return false;
}
}
注:它们都是package java.sql包下面的
- 传递性 对于任何非空引用值
x
、y
和z
,如果x.equals(y)
返回true
,并且y.equals(z)
返回true
,那么x.equals(z)
应返回true
。
就上面的例子,我们在测试一下这个特性是否满足,测试程序和结果如下图:
很显然,在这里传递性失效了;所以有这么一句话:
我们无法在扩展可实例化类的同时,既增加新的值组件,同时又保留equals约定,除非愿意放弃面向对象的抽象所带来的优势。
对于上面的错误,我们可以使用复合的方式代替继承的方式,也就是说吧Person类作为Student类的一个成员来操作,具体的实现我就不多说了;也比较简单,主要是这种思想。
- 一致性:对于任何非空引用值
x
和y
,多次调用 x.equals(y) 始终返回true
或始终返回false
,前提是对象上equals
比较中所用的信息没有被修改
建议:不要使equals方法依赖于不可靠的资源。
由此可见,想重写好Object中的equals方法还是不简单的,比我们平时认为的肯定要复杂一些,下面是在重写equals方法的时候的一些建议:
- 使用“==”检查参数是否为这个对象的引用
- 使用instanceof 检查参数是否为正确的类型
- 把参数转化为正确的类型
- 检查类中的每个关键域
对于float:使用Float.compare方法比较
对于double:使用Double.compare方法比较
对于其他基本类型:使用“”比较
对于引用类型域:使用equals方法(前提是引用指向的对象的类重写了)
- 覆盖equals方法通常也需要覆盖hashCode方法
- 不要在equals中做太多额外的逻辑业务判断,得不偿失
- 不要将equals中的参数Object换成其他的类型
说到这里,其实关于Object中的equals方法的内容基本介绍完了,这里面很多内容都没有展开介绍。希望看到有模糊地方的朋友可以自己多展开一点。
最后
以上就是受伤雨为你收集整理的认识Object中的几个经常需要覆盖的方法——equals方法1,引言2,分析的全部内容,希望文章能够帮你解决认识Object中的几个经常需要覆盖的方法——equals方法1,引言2,分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复