使用接口自定义排序的多种方法 + PriorityQueue案例详解!
如果指向同一个对象,返回 true, 如果传入的为 null,返回 false,如果传入的对象类型不是 Student,返回 false, 按照类的实现目标完成比较,例如这里只要姓名和年龄一样,就认为是相同的。因为:对于用户实现自定义类型,都默认继承自Object类,而Object类中提供了equal方法,而==默认情况下调 用的就是equal方法,但是该方法的比较规则是:没有比较引用变量引用对象
目录
一 Java对象的比较
1.1 基本类型的比较
在Java中,基本类型的对象(byte , short , int , long , float , double , char , boolean)可以直接比较大小。
int a1 = 11;
int a2 = 22;
System.out.println(a1 > a2);
System.out.println(a1 < a2);
System.out.println(a1 == a2);
char c1 = 'A';
char c2 = 'B';
System.out.println(c1 > c2);
System.out.println(c1 < c2);
System.out.println(c1 == c2);
boolean b1 = true;
boolean b2 = false;
System.out.println(b1 == b2);
System.out.println(b1 != b2);
1.2 引用类型的比较
在介绍引用类型比较之前,请大家先猜一猜下面这段代码的运行结果。
Student student1 = new Student("zhangsan", 13);
Student student2 = new Student("lisi", 12);
Student student3 = student1;
System.out.println(student1 > student2);
System.out.println(student1 < student2);
System.out.println(student1 == student2);
System.out.println(student1 == student3);
其中,第一行输出和第二行输出均报错,第三行输出的结果为false,因为student1和student2指向的是不同对象,第四行输出的结果为true,因为student1和student3指向的是同一个对象。
从编译结果可以看出,Java中引用类型的变量不能直接按照 > 或者 < 方式进行比较。 那为什么==可以比较?
因为:对于用户实现自定义类型,都默认继承自Object类,而Object类中提供了equal方法,而==默认情况下调 用的就是equal方法,但是该方法的比较规则是:没有比较引用变量引用对象的内容,而是直接比较引用变量的地 址,但有些情况下该种比较就不符合题意。
二 集合框架中PriorityQueue(堆)的比较方式
2.1 小根堆的实现
在默认情况下,Java中的PriorityQueue均为小根堆,这显然并不满足我们日常的使用需求。
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(5);
priorityQueue.offer(3);
priorityQueue.offer(2);
priorityQueue.offer(4);
System.out.println(priorityQueue.poll());
此时,打印结果为2。
2.2 大根堆的实现
集合框架中的PriorityQueue底层使用堆结构,因此其内部的元素必须要能够比大小,PriorityQueue采用了 Comparble和Comparator两种方式。
我们可以先看一下在JDK中的中PriorityQueue的实现:
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
// 默认容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 内部定义的比较器对象,用来接收用户实例化PriorityQueue对象时提供的比较器对象
private final Comparator<? super E> comparator;
// 用户如果没有提供比较器对象,使用默认的内部比较,将comparator置为null
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
// 如果用户提供了比较器,采用用户提供的比较器进行比较
public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
可以看出,在PriorityQueue的实现中,默认的初始大小为11,默认的比较方法是采用系统提供的比较方法,即建立小根堆。如果用户提供了比较器,采用用户提供的比较器进行比较。也就是说我们可以自定义一个比较器来进行元素的比较,从而可以实现大根堆。
2.2.1 使用Comparble接口的比较方式
Comparble是默认的内部比较方式,如果用户插入自定义类型对象时,该类对象必须要实现Comparble接口,并覆写compareTo方法。
使用lambda表达式写法:
PriorityQueue<Integer> priorityQueue1 = new PriorityQueue<>((o1,o2) -> {return o2.compareTo(o1);});
2.2.2 实现 Comparator接口的比较方式
用户也可以选择使用比较器对象,如果用户插入自定义类型对象时,必须要提供一个比较器类,让该类实现 Comparator接口并覆写compare方法。
写法1:传入比较器
class IntegerCmt implements Comparator<Integer>{
@Override
public int compare(Integer t1, Integer t2) {
return t2.compareTo(t1);
}
}
IntegerCmt integerCmt = new IntegerCmt();
PriorityQueue<Integer> priorityQueue2 = new PriorityQueue<>(integerCmt);
写法2:匿名内部类
PriorityQueue<Integer> priorityQueue2 = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer integer, Integer t1) {
return t1.compareTo(integer);
}
});
此时的priorityQueue2底层则为大根堆。
三 对象的比较
有些情况下,需要比较的是对象中的内容,比如:向优先级队列中插入某个对象时,需要对按照对象中内容来调整堆,那该如何处理呢?那么我们就以上述无法进行大小比较的Student对象为例。
3.1 覆写基类的equals
equals方法是用来比较两个对象是否相等的最常用方法之一。通常需要重写equals方法来根据类的需求来比较两个类的对象是否相等。需要注意的是,重写equals方法时需要遵守以下几点:
- 对称性:如果a.equals(b)返回true,则b.equals(a)也应该返回true。
- 自反性:a.equals(a)应该返回true。
- 传递性:如果a.equals(b)返回true,b.equals(c)也返回true,则a.equals(c)也应该返回true。
- 一致性:如果a.equals(b)返回true,那么在a和b之间的比较应该保持不变,直到a或b被修改。
- 非空性:a.equals(null)应该返回false。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
如果指向同一个对象,返回 true, 如果传入的为 null,返回 false,如果传入的对象类型不是 Student,返回 false, 按照类的实现目标完成比较,例如这里只要姓名和年龄一样,就认为是相同的。注意下调用其他引用类型的比较也需要 equals,例如这里的name的比较覆写基类equal的方式虽然可以比较,但缺陷是:equal只能按照相等进行比较,不能按照大于、小于的方式进行 比较。
3.2 基于Comparble接口类的比较
Comparable接口是Java中提供的一种比较机制,通过实现该接口,对象可以进行自然排序,即按照其属性的自然顺序进行排序。实现Comparable接口需要实现compareTo方法,该方法返回一个整数值,表示要比较的对象与当前对象的大小关系。compareTo方法需要遵守以下规则:
- 如果当前对象小于要比较的对象,则返回负整数;
- 如果当前对象等于要比较的对象,则返回0;
- 如果当前对象大于要比较的对象,则返回正整数。
class Student implements Comparable<Student>{
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student student) {
return this.age - student.age;
}
如下代码的结果为1。
Student student1 = new Student("zhangsan",13);
Student student2 = new Student("lisi",12);
System.out.println(student1.compareTo(student2));
3.3 基于比较器 Comparator接口比较
Comparator接口是Java中提供的一种比较机制,通过实现该接口,可以对不同的对象进行比较。Comparator接口需要实现compare方法,该方法返回一个整数值,表示要比较的两个对象之间的大小关系。compare方法需要遵守以下规则:
- 如果第一个对象小于第二个对象,则返回负整数;
- 如果第一个对象等于第二个对象,则返回0;
- 如果第一个对象大于第二个对象,则返回正整数。
Comparator接口适用于需要根据不同的属性进行比较的情况,可以灵活地在代码中进行调用。例如:
按照姓名进行比较:
class NameComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
按照年龄进行比较:
class AgeComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
如下代码的结果分别为1,14,结果返回值均大于0。
NameComparator nameComparator = new NameComparator();
AgeComparator ageComparator = new AgeComparator();
System.out.println(ageComparator.compare(student1, student2));
System.out.println(nameComparator.compare(student1, student2));
3.4 小结:三种方式对比
Object.equals: 因为所有类都是继承自 Object 的,所以直接覆写即可,不过只能比较相等与否。
Comparable.compareTo 需要手动实现接口,侵入性比较强,但一旦实现,每次用该类都有顺序,属于内部顺序。
Comparator.compare 需要实现一个比较器对象,对待比较类的侵入性弱,但对算法代码实现侵入性强。
更多推荐

所有评论(0)