引子
首先,理解什么是比较器?说白了,就是用来比较两个元素的工具,这个工具在Java中很灵活,可以自定义比较规则,你可以想怎么比较就怎么比较。1都可以小于0,0也可以大于1。当然,这些都取决于业务场景。
Comparable
Comparable,它是一个接口。这个接口中,仅仅只有一个方法,就是 compareTo() 方法。
public interface Comparable<T> {
public int compareTo(T o);
}
Comparable 可以认为是一个内比较器,实现了 Comparable 接口的类有一个特点,就是这些类是可以和自己比较的,至于具体和另一个实现了 Comparable 接口的类如何比较,则依赖 compareTo 方法的实现,compareTo 方法也被称为自然比较方法。如果开发者add进入一个 Collection 的对象想要 Collections 的 sort 方法帮你自动进行排序的话,那么这个对象必须实现 Comparable 接口。compareTo 方法的返回值是 int ,有三种情况:
- 比较者大于被比较者(也就是 compareTo 方法里面的对象),那么返回正整数;
- 比较者等于被比较者,那么返回0;
- 比较者小于被比较者,那么返回负整数。
下面举个栗子:
public class RainBow implements Comparable {
private int age;
private String name;
public RainBow(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
@Override
public int compareTo(Object o) {
RainBow rainBow = (RainBow) o;
if (this.age > rainBow.age) {
return 1;
} else if (this.age < rainBow.age) {
return -1;
} else {
return 0;
}
}
}
测试:
public class TestComparable {
public static void main(String[] args) {
RainBow r1 = new RainBow(66, "a");
RainBow r2 = new RainBow(77, "b");
RainBow r3 = new RainBow(66, "c");
RainBow r4 = new RainBow(55, "d");
System.out.println(r1.compareTo(r2) + " " + r1.getAge() + r2.getAge());
System.out.println(r1.compareTo(r3) + " " + r1.getAge() + r2.getAge());
System.out.println(r2.compareTo(r4) + " " + r2.getAge() + r4.getAge());
}
}
输出:
注意一下,前面说实现 Comparable 接口的类是可以支持和自己比较的,但是其实代码里面 Comparable 的泛型未必就一定要是自定义类,将泛型指定为 String 或者指定为其他任何任何类型都可以----只要开发者指定了具体的比较算法就行。
Comparator
Comparator,同样的,这也是一个接口。但是它的方法很多,像是一个升级版 Comparable ,其中也有一个 compare() 方法。
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
//其余方法略
}
注意,这里比上面的 compareTo 多了一个 @FunctionalInterface 注解,说明,此接口可以使用 Lambda 表达式。
Comparator 可以认为是是一个外比较器,个人认为有两种情况可以使用实现 Comparator 接口的方式:
- 一个对象不支持自己和自己比较(没有实现 Comparable 接口),但是又想对两个对象进行比较。
- 一个对象实现了 Comparable 接口,但是开发者认为 compareTo 方法中的比较方式并不是自己想要的那种比较方式。
Comparator 接口里面有一个 compare 方法,方法有两个参数 T o1和 T o2,是泛型的表示方式,分别表示待比较的两个对象,方法返回值和 Comparable 接口一样是 int ,有三种情况:
- o1大于o2,返回正整数;
- o1等于o2,返回0;
- o1小于o3,返回负整数。
再举个栗子:
实体类不改变,测试:
public class TestComparator {
public static void main(String[] args) {
Comparator<RainBow> comparator = (RainBow r1,RainBow r2)->{
if (r1.compareTo(r2)>0) {
return 1;
} else if (r1.compareTo(r2)==0) {
return 0;
}else {
return -1;
}
};
RainBow r1 = new RainBow(66, "a");
RainBow r2 = new RainBow(77, "b");
RainBow r3 = new RainBow(66, "c");
RainBow r4 = new RainBow(55, "d");
System.out.println(comparator.compare(r1, r2));
System.out.println(comparator.compare(r1, r3));
System.out.println(comparator.compare(r3, r4));
}
}
输出:
当然因为泛型指定死了,所以比较对象必须是同一类型的对象。
总结
总结一下,两种比较器 Comparable 和 Comparator ,后者相比前者有如下优点:
- 如果实现类没有实现 Comparable 接口,又想对两个类进行比较(或者实现类实现了 Comparable 接口,但是对 compareTo 方法内的比较算法不满意),那么可以实现 Comparator 接口,自定义一个比较器,写比较算法。
- 实现 Comparable 接口的方式比实现 Comparator 接口的耦合性 要强一些,如果要修改比较算法,要修改 Comparable 接口的实现类,而实现 Comparator 的类是在外部进行比较的,不需要对实现类有任何修 改。从这个角度说,其实有些不太好,尤其在我们将实现类的 .class 文件打成一个 .jar 文件提供给开发者使用的时候。实际上实现 Comparator 接口的方式后面会写到就是一种典型的策略模式。
当然,这不是鼓励用 Comparator ,意思是开发者还是要在具体场景下选择最合适的那种比较器而已。