Comparable和Comparator接口
问题提出
优先级队列也就是堆,在插入元素时有个要求:插入的元素不能是null或者元素之间必须能够进行比较,为了简单起见只是在优先级队列中插入Integer类型,那么当我们想插入自定义的对象时,能不能实现呢?
//定义扑克牌内部类
class Card{
public int rank; //代表牌的数值
public String suit; //代表牌的花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
}
public class TestDemo {
public static void main(String[] args) {
fun();
}
public static void fun(){
PriorityQueue<Card> priorityQueue = new PriorityQueue<>();
//将Card对象加入优先级队列
priorityQueue.offer(new Card(1,"♥"));
priorityQueue.offer(new Card(2,"♣"));
}
}
来看一下main执行结果:
程序抛出了类型不兼容引发的运行时异常,也就是说
这是无法实现的,为了满足堆的性质,必须进行元素的比较,而Card类型对象是无法进行比较的,因此抛出异常。
引用类型对象比较
基本数据类型比较
在Java语言中,基本数据类型可以直接比较大小
public static void main(String[] args) {
//整型
int a = 66;
int b = 88;
System.out.println(a > b);
System.out.println(a == b);
System.out.println(a < b);
//字符型
char c = 'A';
char d = 'B';
System.out.println(c > d);
System.out.println(c == d);
System.out.println(c < d);
//布尔型
boolean e = true;
boolean f = false;
System.out.println(e == f);
System.out.println(e != f);
}
对象的比较
先看一段代码
可以看出对Card类型对象按 < 或者 > 方式进行比较时,编译会出错,也就说明引用类型变量是无法进行大小比较的;
但是我们发现,使用==
却没有编译报错,这是因为Java中规定所有类都默认继承父类Object,而Object类提供了equals方法,默认情况下==调用的是equals方法,该方法的比较规则是:不比较对象的内容,直接比较对象的第地址。c1和c3指向的是同一对象,所以比较结果为true。
那么如何比较对象的内容?大致三种方式:重写equals、实现Comparable接口、实现Comparator接口
1.重写equals方法
@Override
public boolean equals(Object obj) {
//重写equals
if(this == obj){
return true;
}
//obj为null或者obj不是Card子类对象
if(obj == null || obj instanceof Card){
return false;
}
//基本数据类型可以直接比较,引用类型最好还是调用equals
Card c = (Card)obj;
return rank == c.rank && suit.equals(c.suit);
}
缺点:只能以相等的方式进行比较,不能按照大于、小于的方式对对象进行比较。
2.Comparable接口
看一下java.lang.Comparable接口源码,在这个接口中,只存在一个抽象方法compareTo:
定义方法返回值为int,源码定义的比较规则:
< 0:表示this指向的对象小于o指向的对象
== 0:表示this指向的对象等于o指向的对象
0:表示this指向的对象大于o指向的对象
知道了源码中怎么设置比较大小规则后,对于我们自定义的类型而言,如果要按照大小方式进行比较,让自定义的类实现Comparable接口并重写compareTo方法。
class Card implements Comparable<Card>{
public int rank; //代表牌的数值
public String suit; //代表牌的花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
//根据牌的数值比较,不管牌的花色,认为null对象是最小的
@Override
public int compareTo(Card o) {
if(o == null){
return 1;
}else{
return rank - o.rank;
}
}
}
public class TestDemo {
public static void main(String[] args) {
Card a = new Card(1,"♠");
Card b = new Card(2,"♦");
Card c = new Card(1,"♥");
System.out.println(a.compareTo(b));
System.out.println(a.compareTo(c));
System.out.println(b.compareTo(c));
}
}
从执行结果可以看出,此时我们就可以比较对象之间的大小了
3.Comparator接口
按照Comparator接口进行对象之间的比较,具体步骤如下:
先看一下java.util.Comparator接口源码
定义方法返回值为int,源码定义的比较规则:
< 0:表示o1指向的对象小于o2指向的对象
== 0:表示o1指向的对象等于o2指向的对象
0:表示o1指向的对象大于o2指向的对象
我们可以通过一个自定义类比较器去实现Comparator接口,通过比较器类中compare方法进行对象之间的大小比较。
/**
* 自定义类
*/
class Card{
public int rank; //代表牌的数值
public String suit; //代表牌的花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
}
/**
* 比较器类
*/
class CardComparator implements Comparator<Card>{
//我们根据牌的数值去比较大小,不管花色
@Override
public int compare(Card o1, Card o2) {
if(o1 == o2) return 0;
if(o1 == null) return -1;
if(o2 == null) return 1;
return o1.rank - o2.rank;
}
}
public class TestDemo {
public static void main(String[] args) {
Card p = new Card(1,"♣");
Card q = new Card(2,"♦");
Card o = new Card(1,"♥");
//定义比较器对象
CardComparator cardComparator = new CardComparator();
//使用比较器对象进行比较
System.out.println(cardComparator.compare(q,p));
System.out.println(cardComparator.compare(p,o));
System.out.println(cardComparator.compare(o,q));
}
}
通过实现Comparator接口也完成了对象之间大小比较。
三者的区别
equals
:默认所有类都是继承Object类的,因此直接在自定义类中重写该方法就行,但是只能比较两个对象相等与否,不能比较对象大小关系。
compareTo
:在自定义类手动实现Comparable接口,侵入性极强,一旦实现,后面使用该类都有顺序。
compare
:与实现Comparable接口不同的是,需要额外定义一个比较器类实现Comparator接口,在比较器类中重写compare方法,最后创建比较器类对象去实现自定义类对象之间的大小比较;对待自定义类的侵入性弱,但对算法代码实现侵入性强。