Java对象的比较——equals方法,Comparable接口,Comparator接口
- 1. equals方法
- 2. Comparable接口
- 3. Comparator接口
1. equals方法
在判断两个整数是否相同时,我们可以使用以下方式:
System.out.println(1 == 2);
System.out.println(1 == 1);
如果输出true,则说明这两个整数相同;如果输出false,则说明这两个整数不相同
那么,如果将==用于判断两个对象,又会是怎样的情况呢?我们直接敲代码来看看!
public class Dog {private String name;private int age;public Dog(String name, int age) {this.name = name;this.age = age;}
}
public class Test1 {public static void main(String[] args) {Dog dog1 = new Dog("zhangsan",1);Dog dog2 = new Dog("zhangsan",1);System.out.println(dog1 == dog2);}
}
运行结果:
当运行这段代码时,我们发现输出的是false。明明dog1和dog2的属性都一模一样,为什么输出false呢?莫慌,且听我慢慢道来!
画图来分析,dog1和dog2中存放的值并不相同,因此dog1 != dog2。而你之所以认为输出的应该是true,是因为你认为,两个对象的属性完全一致,所以dog1 == dog2.其实,并不是这样的,==判断的并不是对象的属性是否一致,而是判断两个引用指向的是否是同一个对象!
在Java中,一切皆对象。而dog1和dog2是对象的引用。==判断的并不是对象的属性是否一致,而是判断两个引用指向的是否是同一个对象!
因此,只有当两个引用指向同一对象才返回true;而如果两个引用指向不同的对象,即使两个对象的属性完全相同,返回依旧是false!
总结:
- 如果==左右两边是基本数据类型变量,比较的是变量中的值是否相同
- 如果==左右两边是引用数据类型变量,比较的是引用变量中的值是否相同,而引用变量中存放的是对象的地址,所以比较的就是引用变量中存放的地址是否相同(即判断两个引用指向的是否是同一个对象!)
理解了这一点,学习equals方法就简单多了!我们先来看看equals方法的原型,equals方法在Object类中
有没有发现,equals方法,返回正是刚刚所讲的内容,说明equals方法默认就是判断两个引用指向的是否是同一个对象!
注意: equals方法的返回值是boolean类型!
再来尝试运行这段代码:
public class Test1 {public static void main(String[] args) {Dog dog1 = new Dog("zhangsan",1);Dog dog2 = new Dog("zhangsan",1);System.out.println(dog1.equals(dog2));}
}
运行结果依旧是false
那么,问题来了,如果我们想通过比较两个对象的属性是否相等,如果相等,从逻辑上就说明他们就是相等的,该怎么办呢?
我们知道Object类是一切类的父类,而equals方法在Object类中,是不是就通过可以重写equals方法,从而达到自定义比较方式的目的!
下面就演示重写equals方法,规则:如果两个对象的属性完全一致,则返回true;否则放回false
public class Dog {private String name;private int age;public Dog(String name, int age) {this.name = name;this.age = age;}public boolean equals(Object obj) {Dog tmp = (Dog)obj;return this.name.equals(tmp.name) && this.age == tmp.age;}
}
public class Test1 {public static void main(String[] args) {Dog dog1 = new Dog("zhangsan",1);Dog dog2 = new Dog("zhangsan",1);System.out.println(dog1.equals(dog2));}
}
这时运行代码,输出的就是true!
这时,有人可能就有疑问了,为什么名字比较要用equals方法,其实这里的equals方法并非Dog类中的equals方法,而是String类中的equals方法,用来判断两个字符串是否相等
String类中的equals方法原型:
另外,编译器可以帮我们自动生成equals方法,第一步按住alt+insert
一路点下去,就可以生成equals方法,除此之外,还生成了hashCode方法
2. Comparable接口
通过重写equals方法,我们可以从逻辑上去判断两个对象是否相同。但是如果要去比较两个对象的大小,又该怎么去比较呢?对象的属性那么多,通过什么属性去比较呢?这时就需要讲到Comparable接口!
当我们在Student类后加上Comparable接口时,发现会报错,这是为什么呢?我们按住CTRL键,再鼠标左击Comparable,进入源码
我们发现,Comparable接口中还有个compareTo抽象方法,因此当我们在Student类要实现这个方法,除此之外,Comparable接口后还有个,这是泛型,我们需要比较什么类型的对象,就在实现接口时把T改成什么
由编译器自动生成实现compareTo方法的代码后:
我们就需要书写compareTo的方法体,那怎么书写呢?你想根据哪个对象的属性进行比较,就怎么书写。
比如,我现在想根据对象的年龄进行比较
下面进行测试:
public class Student implements Comparable<Student>{private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic int compareTo(Student o) {return this.age-o.age;}
}
public class Test {public static void main(String[] args) {Student student1 = new Student("zhangsan", 18);Student student2 = new Student("lisi", 10);System.out.println(student1.compareTo(student2));}
}
运行结果:
返回的是一个正数,说明根据年龄比较,student1大于student2
再比如,我不想根据年龄比较了,我想根据姓名比较,这时就不能用简单的this.name-o.name了,因为name是一个String类型,不能通过这样的方式进行比较
方法里的compareTo指并不是在Student类中具体实现的这个compareTo,而是String类中的conpareTo,用于字符串的比较,它的底层是和C语言的strcmp是一样的,返回的是两个字母的ASCII 码的差值
那么,问题来了,前面我们比较的只有两个对象,如果需要比较多个对象呢,改怎么办呢?这时候就需要用到数组来存放对象,用Arrays里的排序方法进行排序
当我们写下以下代码:
public class Student {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
public class Test {public static void main(String[] args) {Student[] students = new Student[]{new Student("zhangsan",18),new Student("lisi", 10),new Student("wangwu", 20)};System.out.println("排序前:" + Arrays.toString(students));Arrays.sort(students);System.out.println("排序后:" + Arrays.toString(students));}
}
当我们运行代码后,发现会出现异常
这里需要用到强转,而Student类并没有去实现Comparable接口,因此会导致强转失败!这就是异常所在!所以,我们需要在Student类中实现Comparable接口
public class Student implements Comparable<Student>{private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Student o) {return this.age-o.age;}
}
当我们再次去运行代码时,会发现排序后是根据年龄从小到大排序的,这和Student类中实现的compareTo难道有关系?答案是正确的
假如我们想按照年龄从大到小去排序,就做出如下更改
调换顺序即可,再次运行代码:
假如想根据年龄去比呢?
public class Student implements Comparable<Student>{private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Student o) {return this.name.compareTo(o.name);}
}
public class Test1 {public static void main(String[] args) {Student[] students = new Student[]{new Student("zhangsan",10),new Student("lisi", 18),new Student("wangwu", 9)};System.out.println("排序前:" + Arrays.toString(students));Arrays.sort(students);System.out.println("排序后:" + Arrays.toString(students));}
}
3. Comparator接口
在前面使用Comparable接口来实现对象的比较时,我们不难发现,这个比较方法并不灵活,只能固定地通过一种方式去比较,那么如果我们有的时候想通过年龄比较,有的时候想通过姓名比较,这个接口就无法实现了,这时地解决办法就是,换一个接口,用Comparator接口来实现!
这时,我们分别写两个类——NameComparator和AgeComparator,代表分别通过姓名比较和通过年龄比较。并且这两个类都要实现Comparator接口!
import java.util.Comparator;public class NameComparator implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o1.getName().compareTo(o2.getName());//姓名通过compareTo方法进行比较}
}
import java.util.Comparator;public class AgeComparator implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o1.getAge() - o2.getAge();}
}
注意: 实现Comparator接口需要重写的时compare方法,不是compareTo方法!实现Comparable接口重写的才是compareTo方法!
学生类如下:
public class Student {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
测试一下代码:
import java.util.Arrays;public class Test {public static void main(String[] args) {//两个对象的比较AgeComparator ageComparator = new AgeComparator();NameComparator nameComparator = new NameComparator();Student student1 = new Student("zhangsan", 20);Student student2 = new Student("lisi", 30);System.out.println("根据年龄比:" + ageComparator.compare(student1, student2));System.out.println("根据姓名比:" + nameComparator.compare(student1, student2));//一组对象的比较Student[] students = new Student[]{new Student("zhangsan", 10),new Student("lisi", 18),new Student("wangwu", 9)};System.out.println("排序前:" + Arrays.toString(students));Arrays.sort(students, ageComparator);System.out.println("根据年龄排序后:" + Arrays.toString(students));Arrays.sort(students, nameComparator);System.out.println("根据姓名排序后:" + Arrays.toString(students));}
}
分析两个对象的比较:
这里,我们创建了一个AgeComparator和一个NameComparator对象,用来表示是通过年龄比较还是通过姓名比较。注意最后两个Student类对象的比较方法
分析一组对象的比较:
我们发现,在用Arrays.sort排序时,里面还加上了一个ageComparator对象(或NameComparator对象),通过这个对象,可以控制通过什么方式去比较,当使用Arrays对数组进行排序时,就会调用ageComparator(或NameComparator)里的compare方法依次将数组里的对象进行比较
最后,运行结果: