Java中的Comparable接口和Comparator接口核心機(jī)制詳解
Java 中的Comparable和 Comparator接口是實現(xiàn)對象比較和排序的兩種核心機(jī)制。
一個生動的比喻
想象一下排序一班的學(xué)生:
- 使用
Comparable(自然排序):- 就像是規(guī)定:我們班默認(rèn)的、永久的排隊規(guī)則就是按學(xué)號排。每個學(xué)生(對象)的學(xué)號(自然屬性)是固定的。老師只需要喊一聲“按默認(rèn)規(guī)則排隊!”,學(xué)生A(
this)就知道自己應(yīng)該站在學(xué)生B(other)的前面還是后面,因為他們都清楚自己的學(xué)號。 - 結(jié)論:你只能有一種默認(rèn)規(guī)則。
- 就像是規(guī)定:我們班默認(rèn)的、永久的排隊規(guī)則就是按學(xué)號排。每個學(xué)生(對象)的學(xué)號(自然屬性)是固定的。老師只需要喊一聲“按默認(rèn)規(guī)則排隊!”,學(xué)生A(
- 使用
Comparator(定制排序):- 就像是老師今天說:“今天我們不按學(xué)號了,我們來按身高排”,于是老師(Comparator)拿著尺子(compare方法)來比較學(xué)生A(o1)和學(xué)生B(o2)的身高,然后決定他們的順序。明天老師可以說:“今天按上次考試成績排”,又拿出一個成績單比較器。
- 結(jié)論:你可以有無數(shù)種臨時規(guī)則。
一、Comparable接口(內(nèi)部比較器)
當(dāng)一個類實現(xiàn)了 Comparable接口,就表明它的實例具有一種天生的、默認(rèn)的比較順序。例如,String、Integer、Date等類都實現(xiàn)了 Comparable,所以我們可以直接對它們的列表進(jìn)行排序。
核心:重寫接口中唯一的 compareTo(T o)方法。

規(guī)則:this(當(dāng)前對象)與參數(shù)對象 o比較。
返回一個負(fù)整數(shù)、零或正整數(shù),分別表示 this小于、等于或大于 o。

下面我們舉一個例子分別實現(xiàn)對Student的年齡和姓名進(jìn)行比較:
年齡比較:
import java.util.Arrays;
//類實現(xiàn)接口,使其具備比較的功能
class Student implements Comparable<Student>{
private String name;
private int age;
public Student(String name,int age){
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public String getName(){
return name;
}
//年齡排序
public int compareTo(Student o){
// return this.age - o.age;
return Integer.compare(this.age,o.age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test01 {
public static void main(String[] args) {
//按照年齡從小到大排序
Student[] students = new Student[]{
new Student("xiaoming", 19),
new Student("xiaohong",20),
new Student("xiaohua",15),
new Student("xiaoshuai",24),
};
System.out.println("排序前"+ Arrays.toString(students));
//Arrays.sort()能夠根據(jù)我們定義的 compareTo方法排序
Arrays.sort(students);
System.out.println("排序后" + Arrays.toString(students));
//兩個對象進(jìn)行比較
// Student student1 = new Student("小明",28);
// Student student2 = new Student("小紅",19);
// System.out.println(student1.compareTo(student2)); //輸出9
// <0 則s1<s2 =0則s1=s2 >0則s1>s2
}
修改一下Student類中的compareTo方法可以實現(xiàn)對姓名進(jìn)行排序
//姓名排序
public int compareTo(Student o){
return this.name.compareTo(o.name);//在String中已經(jīng)實現(xiàn)了compareTo方法,這里可直接調(diào)用
}根據(jù)ASCII值進(jìn)行排序

為什么當(dāng)我們的類實現(xiàn)了comparable接口后,我們能夠直接通過Arrays.sort()方法對students數(shù)組按年齡或者姓名進(jìn)行排序?
這是因為Arrays.sort()依賴于我們通過Comparable接口提供的比較規(guī)則。排序算法本身不知道如何比較兩個Student對象,但是它知道可以調(diào)用我們實現(xiàn)的compareTo方法來獲得比較結(jié)果,從而完成排序。
二、Comparator接口(外部比較器)
Comparator允許在不修改原類的情況下定義多種排序規(guī)則,是一種獨立的比較器,更加靈活(定制排序)。
如何使用?
1.創(chuàng)建一個類實現(xiàn)Comparator<T>接口
2.重寫接口中的 compare(T o1, T o2)方法。
- 規(guī)則:比較兩個參數(shù)對象 o1和 o2。
- 返回一個負(fù)整數(shù)、零或正整數(shù),分別表示 o1小于、等于或大于 o2。
下面我們舉一個例子分別實現(xiàn)對Student的年齡和姓名進(jìn)行比較:
按年齡以及姓名長度比較:
import java.util.Arrays;
import java.util.Comparator;
class Teacher{
private String name;
private int age;
public Teacher(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test02 {
public static void main(String[] args) {
Teacher[] teachers = new Teacher[]{
new Teacher("Zhang",33),
new Teacher("Li",40),
new Teacher("Hen",28),
new Teacher("Tang",18)
};
//按照年齡大小比較
AgeComparator ageComparator = new AgeComparator();
System.out.println("排序前" + Arrays.toString(teachers));
Arrays.sort(teachers,ageComparator); //將實例化的比較器作為參數(shù)傳入sort
System.out.println("按年齡排序后" + Arrays.toString(teachers));
//按照姓名長度比較
NameLenComparator nameLenComparator = new NameLenComparator();
Arrays.sort(teachers,nameLenComparator); //將實例化的比較器作為參數(shù)傳入sort
System.out.println("按姓名長度排序后" + Arrays.toString(teachers));
}
//(靜態(tài)內(nèi)部類)自定義年齡比較器
static class AgeComparator implements Comparator<Teacher>{
public int compare(Teacher o1,Teacher o2){
return o1.getAge()- o2.getAge();
}
}
//(靜態(tài)內(nèi)部類)自定義姓名長度比較器
static class NameLenComparator implements Comparator<Teacher>{
public int compare(Teacher o1,Teacher o2){
return o1.getName().length() - o2.getName().length();
}
}
}
我們發(fā)現(xiàn),當(dāng)我們實現(xiàn)了Comparator接口,使用Arrays.sort()時,將我們自定義的比較器實例化的對象作為參數(shù)傳入sort中實現(xiàn)了自定義排序,這使我們能夠自定義多種比較器且不改變原有類進(jìn)行排序,非常靈活。
Java8+引入的Lambda表達(dá)式
在Java8+中我們可以通過Lambda表達(dá)式來簡化我們的比較器代碼:
// 使用Lambda表達(dá)式替代完整的比較器類
// 按年齡排序
Arrays.sort(teachers, (t1, t2) -> t1.getAge() - t2.getAge());
System.out.println("按年齡排序后: " + Arrays.toString(teachers));
// 按姓名長度排序
Arrays.sort(teachers, (t1, t2) -> t1.getName().length() - t2.getName().length());
System.out.println("按姓名長度排序后: " + Arrays.toString(teachers));
// 甚至可以更復(fù)雜:先按年齡,年齡相同按姓名長度
Arrays.sort(teachers, (t1, t2) -> {
int ageCompare = t1.getAge() - t2.getAge();
if (ageCompare != 0) {
return ageCompare;
}
return t1.getName().length() - t2.getName().length();
});
System.out.println("按年齡和姓名長度排序后: " + Arrays.toString(teachers));
}下面我們來詳解一下這串代碼中Lambda表達(dá)式的用法:

// 按年齡排序 Arrays.sort(teachers, (t1, t2) -> t1.getAge() - t2.getAge());
分解說明:
- (t1, t2):Lambda的參數(shù)列表,對應(yīng)Comparator接口的compare方法的兩個參數(shù)
- ->:Lambda操作符,分隔參數(shù)和實現(xiàn)體
t1.getAge() - t2.getAge():Lambda的實現(xiàn)體,只有一行表達(dá)式,自動返回結(jié)果
帶代碼塊的Lambda表達(dá)式
// 先按年齡,年齡相同按姓名長度
Arrays.sort(teachers, (t1, t2) -> {
int ageCompare = t1.getAge() - t2.getAge();
if (ageCompare != 0) {
return ageCompare;
}
return t1.getName().length() - t2.getName().length();
});分解說明:
- 當(dāng)實現(xiàn)邏輯需要多行代碼時,使用{}包裹代碼塊
- 代碼塊中需要顯式使用return語句返回值
- 這種多行Lambda適合處理復(fù)雜的比較邏輯
三、如何選擇兩個接口
Comparable接口和Comparator接口關(guān)鍵區(qū)別對比:

使用Comparable的情況
? 對象有明確的、唯一的自然排序規(guī)則
? 排序規(guī)則是對象固有的、不會改變的特性
? 你能夠修改類的源代碼
? 該排序規(guī)則會被頻繁使用
示例:String, Integer, Date, BigDecimal
使用Comparator的情況
? 需要多種不同的排序規(guī)則
? 不能或不想修改原類代碼
? 排序規(guī)則是臨時的或特定于某個業(yè)務(wù)場景
? 需要復(fù)雜的、組合的排序邏輯
示例:報表排序、UI表格列排序、特殊業(yè)務(wù)規(guī)則排序

到此這篇關(guān)于Java中的Comparable接口和Comparator接口的文章就介紹到這了,更多相關(guān)java comparable接口和comparator接口內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring的applicationContext.xml文件與NamespaceHandler解析
這篇文章主要介紹了spring的applicationContext.xml文件與NamespaceHandler解析,Spring容器啟動,在創(chuàng)建BeanFactory時,需要加載和解析當(dāng)前ApplicationContext對應(yīng)的配置文件applicationContext.xml,從而獲取bean相關(guān)的配置信息,需要的朋友可以參考下2023-12-12
Java斷點續(xù)傳(文件分塊)完整實現(xiàn)步驟
這篇文章主要介紹了Java斷點續(xù)傳(文件分塊)完整實現(xiàn)步驟,?斷點續(xù)傳通過分塊傳輸大文件,中斷后可續(xù)傳未完成部分,服務(wù)端合并分塊,避免重復(fù)上傳,提升用戶體驗并節(jié)省網(wǎng)絡(luò)資源,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-05-05
java.lang.NullPointerException異常的幾種原因及解決方案
本文主要介紹了java.lang.NullPointerException異常的幾種原因及解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04

