Java 排序之 Comparable 与 Comparator

1、Collections.sort() 为何 ClassCastException?

在收集物件之後,對物件進行排序是常用的動作,你不用親自實作排序演算法,java.util.Collections提供有sort()方法,由於必須有索引才能進行排序,因此Collectionssort()方法接受List實作物件。例如:

package cc.openhome;
 
import java.util.*;
 
public class Sort {
    public static void main(String[] args) {
        List numbers = Arrays.asList(10, 2, 3, 1, 9, 15, 4);
        Collections.sort(numbers);
        System.out.println(numbers);
    }
}

執行結果是顯示由小至大的號碼:

[1, 2, 3, 4, 9, 10, 15]

如果是以下的範例呢?

package cc.openhome;
 
import java.util.*;
 
class Account {
    private String name;
    private String number;
    private int balance;
 
    Account(String name, String number, int balance) {
        this.name = name;
        this.number = number;
        this.balance = balance;
    }
 
    @Override
    public String toString() {
        return String.format("Account(%s, %s, %d)", name, number, balance);
    } 
}
 
public class Sort2 {
    public static void main(String[] args) {
        List accounts = Arrays.asList(
                new Account("Justin", "X1234", 1000),
                new Account("Monica", "X5678", 500),
                new Account("Irene", "X2468", 200)
        );
        Collections.sort(accounts);
        System.out.println(accounts);
    }
}

執行結果將會很奇怪地拋出ClassCastException?

Exception in thread "main" java.lang.ClassCastException: cc.openhome.Account cannot be cast to java.lang.Comparable
...略

如果你直接將範例的accounts宣告為List<Account>,更會直接發生編輯錯誤?

2、实现 Comparable

要說原因,是因為你根本沒告訴Collectionssort()方法,到底要根據Accountnamenumberbalance進行排序,那它要怎麼排?Collectionssort()方法要求被排序的物件,必須實作java.lang.Comparable介面,這個介面有個compareTo()方法必須傳回大於0、等於0或小於0的數,作用為何?直接來看如何針對帳戶餘額進行排序就可以瞭解:

package cc.openhome;
 
import java.util.*;
 
class Account2 implements Comparable<Account2> {
    private String name;
    private String number;
    private int balance;
 
    Account2(String name, String number, int balance) {
        this.name = name;
        this.number = number;
        this.balance = balance;
    }
 
    @Override
    public String toString() {
        return String.format("Account2(%s, %s, %d)", name, number, balance);
    }
 
    @Override
    public int compareTo(Account2 other) {
        return this.balance - other.balance;
    }
}
 
public class Sort3 {
    public static void main(String[] args) {
        List<Account2> accounts = Arrays.asList(
                new Account2("Justin", "X1234", 1000),
                new Account2("Monica", "X5678", 500),
                new Account2("Irene", "X2468", 200)
        );
        Collections.sort(accounts);
        System.out.println(accounts);
    }
}

Collections的sort()方法在取得a物件跟b物件進行比較時,會先a物件扮演(Cast)為Comparable(也因此若物件沒實作Comparable,將會拋出ClassCastException,若使用了泛型宣告,編譯器會檢查出型態不符),然後呼叫a.compareTo(b),如果a物件順序上小於b物件,必須傳回小於0,若順序上相等則傳回0,若順序上a大於b則傳回大於0的值。因此,上面的範例,將會依餘額從小到大排列帳戶物件:

[Account2(Irene, X2468, 200), Account2(Monica, X5678, 500), Account2(Justin, X1234, 1000)]

為何先前的Sort類別中,可以直接對Integer進行排序呢?若你查看API文件,將可以發現Integer就有實作Comparable介面。

3、实现 Comparator

實際開發總是不斷有意外,如果你的物件無法實作Comparable呢?也許你拿不到原始碼!也許你不能修改原始碼!舉個例子來說,String本身有實作Comparable,所以你可以如下排序:

package cc.openhome;
 
import java.util.*;
 
public class Sort4 {
    public static void main(String[] args) {
        List words = Arrays.asList("B", "X", "A", "M", "F", "W", "O");
        Collections.sort(words);
        System.out.println(words);
    }
}

String實作的Comparable,將會有以下的執行結果:

[A, B, F, M, O, W, X]

如果今天你想要讓排序結果反過來呢?修改String.java?這個方法不可行吧!就算你修改後重新編譯為String.class放回rt.jar中,也只有你的JRE可以用,這已經不是標準API了。繼承String後再重新定義compareTo()方法?也不可能,因為String宣告為final,不能被繼承。

Collections的sort()方法有另一個重載版本,可接受java.util.Comparator介面的實作物件,如果你使用這個版本,排序方式將根據Comparator的compare()定義來決定。例如:

import java.util.*;
 
class StringComparator implements Comparator<String> {
    @Override
    public int compare(String s1, String s2) {
        return -s1.compareTo(s2);
    }
}
 
public class Sort5 {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("B", "X", "A", "M", "F", "W", "O");
        Collections.sort(words, new StringComparator());
        System.out.println(words);
    }
}

Comparator的compare()會傳入兩個物件,如果o1順序上小於o2,必須傳回小於0的值,順序相等則傳回0,順序上o1大於o2則傳回大於0的值。在這個範例中,由於String本身就是Comparable,所以將compareTo()傳回的值乘上-1,就可以調換排列順序。執行結果如下:

[X, W, O, M, F, B, A]

在Java的規範中,跟順序有關的行為,通常要不物件本身是Comparable,要不就是另行指定Comparator物件告知如何排序。

例如,如果想針對陣列進行排序,可以使用java.util.Arrays的sort()方法,如果你查詢API文件,會發現該方法針對物件排序時有兩個版本,一個是你收集在陣列中的物件必須是Comparable(否則會拋出ClassCastException),另一個版本則可以傳入Comparator指定排序方式。

Set的實作類別之一java.util.TreeSet,不僅擁有收集不重複物件之能力,還可用紅黑樹方式排序收集的物件,條件就是收集的物件必須是Comparable(否則會拋出ClassCastException),或者是在建構TreeSet時指定Comparator物件。

Queue的實作類別之一java.util.PriorityQueue也是,收集至PriorityQueue的物件,會根據你指定的優先權來決定物件在佇列中的順序,優先權的告知,要不就是物件必須是Comparable(否則會拋出ClassCastException),或者是建構PriorityQueue時指定Comparator物件。

4、java8 中的 Comparator

對了!現在我們使用的是JDK8耶!上面的範例可以更簡潔一些:

List<String> words = Arrays.asList("B", "X", "A", "M", "F", "W", "O");
Collections.sort(words, String::compareTo);

會想到這點還不錯,不過,如果你知道,JDK8在List上增加了sort()方法,可接受Comparator實例來指定排序方式,那麼你還可以寫成:

List<String> words = Arrays.asList("B", "X", "A", "M", "F", "W", "O");
words.sort(String::compareTo);

來考慮一個更複雜的情況,如果有個List中某些索引處包括null,現在你打算讓那些null排在最前頭,之後依字串的長度由大到小排序,那會怎麼寫?這樣嗎?

class StringLengthInverseNullFirstComparator implements Comparator < String > {
        @Override
	public int compare(String s1, String s2) {
		if (s1 == s2) {
			return 0;
		}
		if (s1 == null) {
			return -1;
		}
		if (s2 == null) {
			return 1;
		}
		if (s1.length() == s2.length()) {
			return 0;
		}
		if (s1.length() > s2.length()) {
			return -1;
		}
		return 1;
	}
}

不怎麼好讀,對吧!更別說為了表示這個比較器的目的,必須取個又臭又長的類別名稱!其實排序會有各式各樣的組合需求,JDK8考量到這點,為排序加入了一些高階API,除了在Comparator上定義了一些預設方法之外,還新增一些靜態方法,結合這些方法,可以讓程式碼寫來具有較高的可讀性。

以方才的需求為例,在JDK8中要建立對應的Comparator實例,可以如下撰寫:

package cc.openhome;
 
import java.util.*;
import static java.util.Comparator.*;
 
public class Sort6 {
    public static void main(String[] args) {
        List words = Arrays.asList("B", "X", "A", "M", null ,"F", "W", "O", null);
        // 以下也可以寫為 words.sort(nullsFirst(naturalOrder().reversed()));
        words.sort(nullsFirst(reverseOrder()));
        System.out.println(words);
    }
}

執行結果如下:

[null, null, X, W, O, M, F, B, A]

5、N 次排序的 Comparator

你也可能想要排序時先依某人的姓來排,如果姓相同再依名來排,如果姓名都相同,再依他們居住地的郵遞區號來排,那麼你可以如下建立Comparator:

package cc.openhome;
 
import java.util.*;
import static java.util.Comparator.*;
 
public class Person {
    private String firstName;
    private String lastName;
    private Integer zipCode;
 
    public Person(String firstName, String lastName, Integer zipCode) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.zipCode = zipCode;
    }
 
    public String toString() {
        return String.format("Person(%s %s, %d)", firstName, lastName, zipCode);
    }
 
    public static void main(String[] args) {
        List persons = Arrays.asList(
            new Person("Justin", "Lin", 804),
            new Person("Monica", "Huang", 804),
            new Person("Irene", "Lin", 804)
        );
 
        persons.sort(
            Comparator.<Person, String>comparing(p -> p.lastName)
               .thenComparing(p -> p.firstName)
               .thenComparing(p -> p.zipCode)
        );
 
        System.out.println(persons);
    }
}

執行結果如下:

[Person(Monica Huang, 804), Person(Irene Lin, 804), Person(Justin Lin, 804)]

單就這邊範例的閱讀來說,sort()時比較的是lastName、接著是firstName,接著是zipCode,目的一目瞭然,至於comparing()方法接受的型態是什麼?答案是Function型態!關於這類型態的細節,將留在後面的篇幅說明。

时间: 2025-01-07 06:33:03

Java 排序之 Comparable 与 Comparator的相关文章

Java 集合类(1)--Comparable 和 Comparator的比较

Comparable 是在集合内部定义的方法实现的排序: Comparator 是在集合外部实现的排序 Comparator位于包java.util下,而Comparable位于包java.lang下 Comparable 是一个对象本身就已经支持自比较所需要实现的接口,如 String.Integer 自己就可以完成比较大小操作,已经实现了Comparable接口 Comparator 是一个专用的比较器,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象

java集合中Comparable和Comparator辨析

一.Comparable和Comparator简介 在对集合元素进行比较时一般使用TreeSet.对于简单的数据类型,TreeSet可以直接进行比较.但是对于复杂的数据类型,比如自己定义的数据类型或者类,就需要自己设置比较方法与比较规则了,这时就需要使用Comparable和Comparator. Comparable和Comparator都是用来实现集合中的排序的,只是Comparable是在集合内图定义的方法实现排序,而Comparator是在集合外部实现的排序.所以如果想对结合排序,需要在

Java学习之Comparable与Comparator的区别

Comparable & Comparator 都是用来实现集合中元素的比较.排序的,只是 Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序,所以,如想实现排序,就需要在集合外定义 Comparator 接口的方法或在集合内实现 Comparable 接口的方法.Comparator位于包java.util下,而Comparable位于包   java.lang下Comparable 是一个对象本身就已经支持自比较所需要实现的接口(如 Stri

Java 解惑:Comparable 和 Comparator 的区别

读完本文你将了解到: Comparable 自然排序 Comparator 定制排序 总结 Java 中为我们提供了两种比较机制:Comparable 和 Comparator,他们之间有什么区别呢?今天来了解一下. Comparable 自然排序 Comparable 在 java.lang 包下,是一个接口,内部只有一个方法 compareTo(): public interface Comparable<T> { public int compareTo(T o); } Comparab

黑马程序员-JAVA学习之Comparable与Comparator接口

--------android培训.java培训.期待与你交流!-------- public interface Comparator<T> 此接口提供对某个collection集合对象进行强行整体排序的比较函数.可以将 Comparator 传递给 sort() 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现自定义精确控制.还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的co

JAVA比较器:Comparable和Comparator

一.Comparable接口 1.public interface Comparable{ public int compareTo(Object other); } 2.当本对象小于.等于或大于other对象时,相应返回一个小于.等于或大于0的值. 3.若对象不可比较,抛出ClassCastException 4.compareTo()定义的顺序是类的自然顺序,即此排序对类的对象来说是最自然的. 5.equals()定义一种自然相等关系,两个对象相等,返回ture. 6.许多类:String.

Comparable 和Comparator

comparable在java.lang下 comparator在java.util下 Comparable 和Comparator详解及 区别 comparator用法 原文地址:https://www.cnblogs.com/10zhang/p/8940709.html

java 排序的几篇好文章

Java8:Lambda表达式增强版Comparator和排序(这篇文章写的不错,各种花式排序) Comparable与Comparator浅析 (基本功) 原文地址:https://www.cnblogs.com/xiaoxi666/p/10329713.html

JAVA中Arrays.sort()使用两种方式(Comparable和Comparator接口)对对象或者引用进行排序

一.描述 自定义的类要按照一定的方式进行排序,比如一个Person类要按照年龄进行从小到大排序,比如一个Student类要按照成绩进行由高到低排序. 这里我们采用两种方式,一种是使用Comparable接口:让待排序对象所在的类实现Comparable接口,并重写Comparable接口中的compareTo()方法,缺点是只能按照一种规则排序. 另一种方式是使用Comparator接口:编写多个排序方式类实现Comparator接口,并重写新Comparator接口中的compare()方法,