Java比较器
- 在java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题。
java对象中,正常情况下,只能进行比较: == 或 != 。不能使用 > 或 < 的,但是在开发场景中,我们需要对多个对象进行排序,就是需要比较对象的大小。
要如何实现呢?
这就需要使用到这两个接口的任何一个:Comparable 或 Comparator 【比较对象的大小需要用到这两个接口,下面会进行讲解】
测试:
public class CompareTest {
public static void main(String[] args) {
//创建一个字符串数组
String[] arr = new String[]{"g","h","a","l","y","b"};
//使用Arrays.sort对arr进行排序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
//输出的结果为:[a, b, g, h, l, y] ,成功对字符进行排序
//为什么可以对String 进行排序呢?
//追加进入String 里面,可以看到String他实现了 Comparable接口
//public final class String implements java.io.Serializable, Comparable<String>, CharSequence { }
//实现了 Comparable接口就需要去重写他的方法compareTo,在String 类中找到compareTo方法
/**
* 可以看到已经对compareTo进行了重写,他呢就是比较两个字符串之间的大小,方法里面就是比较的方式
* public int compareTo(String anotherString) {
* int len1 = value.length;
* int len2 = anotherString.value.length;
* int lim = Math.min(len1, len2);
* char v1[] = value;
* char v2[] = anotherString.value;
*
* int k = 0;
* while (k < lim) {
* char c1 = v1[k];
* char c2 = v2[k];
* if (c1 != c2) {
* return c1 - c2;
* }
* k++;
* }
* return len1 - len2;
* }
*/
}
}
自然排序
- Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。
- 实现Comparable的类必须重写compareTo(Object obj) 方法,两个对象即通过compareTo(Object obj) 方法的返回值来比较大小。(如果当前对象this大于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回负整数,如果当前对象this等于形参对象obj,则返回零。)
- 实现Comparable接口的对象列表(和数组)可以通过Collections.sort 或 Arrays.sort 进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需制定比较器。
- 对于类C的每一个e1 和 e2 来说当 e1.compareTo(e2)==0 与 e1.equals(e2)具有相同的boolean值时,类C的自然排序才叫做与equals一致。
Comparable 接口的使用说明:
- 像String 和其他包装类(Integer…)等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。他们是进行了从小到大的排序。
- 比较的方式是什么呢?这时就需要了解重写compareTo(obj)方法的规则:
- 如果当前对象this大于形参对象obj,则返回正整数。
- 如果当前对象this小于形参对象obj,则返回负整数。
- 如果当前对象this等于形参对象obj,则返回零
- 对于自定义类来说,如果需要排序,可以让自定义类实现Comparable接口,重写compareTo(obj) 方法。在compareTo(obj) 方法中指明如何排序
测试:
往数组中加入对象
1、首先创建一个 User 类,有两个属性 name、age,并且写好get、set、toString方法
public class User implements Comparable{
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2、创建一个测试类,往数组中添加对象
import java.util.Arrays;
public class TestDemo {
public static void main(String[] args) {
//创建User对象的数组
User[] users = new User[4];
//往数组里面存放数据,放的是对象
users[0] = new User("xiaohong",19);
users[1] = new User("liming",16);
users[2] = new User("zhangsan",18);
users[3] = new User("wangwu",22);
// 通过Arrays.sort方法,对users数组里面的元素进行排序
Arrays.sort(users);
//输出排序后的数组
System.out.println(Arrays.toString(users));
/**
* 结果:
* 报了一个类转换异常 ClassCastException
* com.xun.pojo.User cannot be cast to java.lang.Comparable错误
* 说是 user不能转换为java.lang.Comparable
*
* 为什么会跟到Comparable打交道呢?因为在调用Arrays.sort这个方法的时候,
* 他涉及到跟user这个数组的元素进行排序,那就需要比较大小!而对象比较大小只能跟Comparable和Comparator打交道,
* 默认的是跟Comparable打交道,所以它就提示类型转换异常
*/
}
}
所以想对对象进行排序,就需要去实现Comparable接口,并且重写compareTo(obj) 方法指明如何排序
package com.xun.pojo;
//实现 Comparable接口
public class User implements Comparable{
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//重写compareTo方法,并且指明如何排序
@Override
public int compareTo(Object o) {
//判断o对象是否是 User对象的实例,是就返回true
if (o instanceof User){
//由于需要向下转型,所以需要强转才能获取到User里面的属性
User obj = (User) o;
//方式一:
//按照age进行排序
//如果当前的age大于形参的age,就返回1
if (this.age> obj.age){
return 1;
//如果当前的age小于形参的age,就返回-1
}else if(this.age < obj.age){
return -1;
//否则就是相等,就返回0
}else{
return 0;
}
//方式二:
//使用下面这种也行,包装类里面实现了Comparable接口,并且重写了compareTo方法,指明了如何排序
//可以追加进去看看,发现也跟方式一差不多,传递两个参数,让着两个参数进行比较,然后根据判断的结果返回 -1、0、1
//return Double.compare(this.age,obj.age);
}
//如果传入的形参不是User对象的实例,就抛出一个运行时提示
throw new RuntimeException("传入的数据类型不一致");
}
}
对象实现了Comparable接口并重写compareTo(obj)方法,指明了如何排序之后就可以重新再运行一下测试类了
import java.util.Arrays;
public class TestDemo {
public static void main(String[] args) {
//创建User对象的数组
User[] users = new User[4];
//往数组里面存放数据,放的是对象
users[0] = new User("xiaohong",19);
users[1] = new User("liming",16);
users[2] = new User("zhangsan",18);
users[3] = new User("wangwu",22);
// 通过Arrays.sort方法,对users数组里面的元素进行排序
Arrays.sort(users);
//输出排序后的数组
System.out.println(Arrays.toString(users));
/**
* 结果:
* [User{name='liming', age=16}, User{name='zhangsan', age=18}, User{name='xiaohong', age=19}, User{name='wangwu', age=22}]
* 发现数据按照我们所指定的age进行了排序
*/
}
}
如果比较的时候,比较的数据一样,那怎么样呢?那就接着再比, 这时就再去指定一下二级排序
package com.xun.pojo;
//实现 Comparable接口
public class User implements Comparable{
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//重写compareTo方法,并且指明如何排序
@Override
public int compareTo(Object o) {
//判断o对象是否是 User对象的实例,是就返回true
if (o instanceof User){
//由于需要向下转型,所以需要强转
User obj = (User) o;
//方式一:
//按照age进行排序
//如果当前的age大于形参的age,就返回1
if (this.age> obj.age){
return 1;
//如果当前的age小于形参的age,就返回-1
}else if(this.age < obj.age){
return -1;
//否则就是age的数据相等,就再按照name进行从低到高排序
}else{
//this.name他是String类型,而String里面已经重写过了compareTo方法了,
// 这时就可以直接调用String里面的compareTo方法了,传递形参进去
//默认是从低到高排序,如果想从高到低,就在this前面加-,变成-this即可
return this.name.compareTo(obj.name);
}
//方式二:
//使用下面这种也行,包装类里面实现了Comparable接口,并且重写了compareTo方法,指明了如何排序
//可以追加进去看看,发现也跟方式一差不多,传递两个参数,让着两个参数进行比较,然后根据判断的结果返回 -1、0、1
//return Double.compare(this.age,obj.age);
}
//如果传入的形参不是User对象的实例,就抛出一个运行时提示
throw new RuntimeException("传入的数据类型不一致");
}
}
总结:
- 如果像String、Integer…等包装类就不需要考虑去重写了,他已经重写过了,直接使用就可以了
- 如果是自定义类的话那就需要去实现Comparable接口,并且重写compareTo方法指定怎么排序
定制排序
在什么情况下使用Comparator:
- 当元素的类型没有实现 Comparable 接口而又不方便修改代码,或者实现了 Comparable 接口,但是排序的规则不适合当前的操作!那么可以考虑使用Comparator 的对象来排序,强行对多个对象进行整体排序的比较
使用Comparator接口规则:
- 使用Comparator接口的时候需要重写 compare(Object o1,Object o2)方法,比较o1 和 o2 的大小【跟上面的compareTo方法规则类似】:如果方法返回正整数,则表示o1 大于 o2 ;如果返回 0,表示相等;返回负整数,表示o1 小于 o2。
使用Comparator接口方式:
- 可以把Comparator传递给sort方法(如Collections.sort 或 Array.sort),从而允许在排序上实现精确控制
Comparator接口的使用场景:
- 可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然排序的对象collecton提供排序。
测试:
//对String数组进行降序测试
@Test
public void test1(){
String[] arr = new String[]{"aa","bb","ww","ff","ee","cc"};
//由于String他实现了 Comparable接口,重写compareTo方法指定排序的顺序是升序的,
//现在我们想给他降序又不能去修改String里面的compareTo方法,怎么办呢?
//这时就需要使用到Comparator方法,定制排序。
//这里使用Arrays.sort来实现定制排序,在sort()里丢一个数组,以及一个Comparator对象(这里使用的是匿名对象),需要重写
//Comparator对象的方法compare,并且给他指定排序方式
Arrays.sort(arr, new Comparator<String>() {
//重写
@Override
public int compare(String o1, String o2) {
//由于String已经重写好了compareTo,所以直接调用即可,然后在结果的前面加个负数 - ,
//这样就达到了降序排序
return -o1.compareTo(o2);
}
});
System.out.println(Arrays.toString(arr));
/**
* 结果:
* [ww, ff, ee, cc, bb, aa]
* 发现数据按照降序的顺序输出了
*/
}
//对对象数组进行指定排序
@Test
public void test2(){
//创建User对象的数组
User[] users = new User[6];
//往数组里面存放数据,放的是对象
users[0] = new User("xiaohong",19);
users[1] = new User("liming",16);
users[2] = new User("zhangsan",18);
users[3] = new User("zahangsan",18);
users[4] = new User("wangwu",22);
users[5] = new User("wangwu",25);
//使用Arrays.sort进行定制排序,传递当前要排序的对象,以及传递Comparator对象,并重写compare方法指定排序
Arrays.sort(users, new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
//判断如果name一样就根据age来排序
if (o1.getName().equals(o2.getName())){
//由于Double包装类里面实现了Comparable接口,并且重写了compareTo方法,指明了如何排序
//使用传递两个参数,让着两个参数进行比较,然后根据判断的结果返回 -1、0、1
return Double.compare(o1.getAge(),o2.getAge());
}
//如果不一样就根据name来进行排序
else{
//由于String已经重写好了compareTo,所以直接调用即可
return o1.getName().compareTo(o2.getName());
}
}
});
System.out.println(Arrays.toString(users));
/**
* 结果:
* [User{name='liming', age=16}, User{name='wangwu', age=22}, User{name='wangwu', age=25}, User{name='xiaohong', age=19}, User{name='zahangsan', age=18}, User{name='zhangsan', age=18}]
* 可以看到数组先按照name进行排序,相等的话又按照age来进行排序
*/
}
Comparable接口和Comparator接口的使用对比:
- Comparable接口方式:实现了Comparable接口的实现类,在任何位置都可以比较大小。
- Comparator接口方式:它属于临时性的比较,什么时候需要就去临时的指定一下。