== 比较的是两个对象的地址是否相同(即两个引用是否指向同一个对象)。
equals()方法比较的是对象中的内容是否相同。(equals()是Object类里的一个方法,且所有的类都默认继承Object类)
以String类举例:
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
程序运行结果:
false;
true;
原因如下图:(s1与s2引用指向的对象不一样,但value的内容相同)
通过上面代码的运行结果,我们对== 和 equals()方法的比较方式已经有了一定的理解。那么以下代码,程序的运行结果又是什么呢?(大家可以先暂停思考一下)
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = "hello";
String s3 = "hello";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
System.out.println(s2 == s3);
System.out.println(s2.equals(s3));
}
程序运行结果:
false
true
true
true
我相信,代码运行的结果可能在一部分人的意料之外,那么原因是什么呢?
对于第2和第4个结果,大家应该没有什么疑问,毕竟每个String对象中的内容都为“hello”,程序打印的结果毫无疑问是true。
第3个比较的结果之所以为true,是因为在堆区中存在一个String常量池,所有非new出来的String对象,都会被存到常量池中,当使用另一个内容相同且非new出来的String对象时,其引用会指向常量池里的同一个对象,即常量池只保存一份值相同的String对象。
根据以上叙述,我们可以知道,s1所指向对象是通过new实例化的对象,在内存中与s2、s3的存储位置是不同的,因此它们的地址也一定不同。
假设有一个如下的自定义类和对应类的两个实例化对象。当它们使用 ==和equals()方法进行比较,那么结果会是什么样呢?
public class Demo {
public static void main(String[] args) {
Student s1 = new Student("lisi",12);
Student s2 = new Student("lisi",12);
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
}
class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void func() {}
}
程序运行结果:
false
false
这里代码运行的结果其实也不难理解,==比较的是引用所指对象的地址,s1和s2是两个不同的对象,因此地址肯定不一样;而equals()方法比较的是对象里的内容,因为Student类存在多个属性,所以他并不会直接去类中的属性,而是依然比较两个对象的地址。
由此我们不难推测,String类其实也存在多个属性,String对象之所以能够使用equals()方法进行比较,是因为String类已经重写了equals()方法。
因此,我们在使用equals()方法比较自定义类时,一定要重写该方法,对类中的成员变量进行依次比较。
重写equals()方法代码如下:
@Override
public boolean equals(Object obj) {
// 检查是否是同一个对象
if(this == obj) return true;
// 检查比较的对象是否为 null
if(obj == null) return false;
// 检查比较对象 是否为同类
if(this.getClass() != obj.getClass()) return false;
// 比较两个类中的所有属性
Student student = (Student) obj;
if(this.age != student.age) return false;
if(this.name == null && student.name == null) {
return true;
}else if(this.name == null || student.name == null) {
return false;
} else {
return this.name.equals(student.name);
}
}
重写equals()后程序的运行结果:
false
true
与equals()方法不同的是,equals()比较结果返回的参数是boolean类型,这个方法只能知道两个对象的内容是否相同。
而实现Comparable接口,重写compareTo()方法,比较结果返回的参数是int类型,它可以通过比较对象中的具体某个属性,而比较出两个对象的大小。
Comparable实现的模版如下:
class Student implments Comparable<E> {
//属性
//方法
// 返回值:
// < 0: 表示 this 指向的对象小于 o 指向的对象
// == 0: 表示 this 指向的对象等于 o 指向的对象
// > 0: 表示 this 指向的对象大于 o 指向的对象
int compareTo(E o) {
}
}
Student对象比较具体的实现代码如下:
public class Demo {
public static void main(String[] args) {
Student s1 = new Student("zhangsan",12);
Student s2 = new Student("lisi",18);
System.out.println(s1.compareTo(s2));
}
}
class Student implements Comparable<Student>{
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
public void func() {}
}
实现Comrable接口,重写compareTo()方法后程序的运行结果:
-6
若要通过比较name区分Student对象的大小,需要比较字符串String中每个字符的大小,大家可以自行实现。
优点:
1.对于自定义类对象的数组,若有对元素排序的需求,可以直接调用Arrays.sort()方法对数组进行排序。
2.若需要将自定义对象建成一个堆,只需在类中实现Comrable接口,重写compareTo()方法即可。
缺点:
一旦确定了比较的属性,后续如果比较需求有变动,就需要重新修改类中属性比较的代码,而一旦该类已被广泛使用,随意地修改类中的compraeTo()方法可能会使别人错误地使用此方法,从而引起某些代码上的逻辑错误。
使用Comparator接口对自定义对象进行比较,需要我们自定义一个比较器类,实现Comparator接口,重写compare()方法,对需要类中的属性进行比较,即可得到两个对象的大小关系。(可根据需要自定义多个不同的比较器类,对类中的不同属性进行比较)
比较器模版如下:
class cmp implements Comparator<T>{
// 返回值:
// < 0: 表示 o1 指向的对象小于 o2 指向的对象
// == 0: 表示 o1 指向的对象等于 o2 指向的对象
// > 0: 表示 o1 指向的对象等于 o2 指向的对象
int compare(T o1, T o2) {
// 方法体
}
Student对象比较具体的实现代码如下:
public class Demo {
public static void main(String[] args) {
Cmp_age cmp = new Cmp_age();
Student s1 = new Student("zhangsan",15);
Student s2 = new Student("lisi",18);
System.out.println(cmp.compare(s1, s2));
}
}
class Cmp_age implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
代码运行结果:
-3
1.比较的灵活性较强,可根据需要构造对应的比较器,不会影响自定义类。
2.若需要将自定义对象建成一个堆,只需实例化PriorityQueue类时,将自定义的比较器类作为参数传入即可。