Java 中类的比较与排序方法(应用Comparable接口与Comparator接口)通俗易懂
引言
在平時寫Java的程序的時候,如果要進行一些基本類型的變量的比較,可以很方便得調用Math.max()、Math.min()等方法,如果要對數組或者列表進行排序,也可以用Arrays.sort()和Collections.sort()等已經封裝好的方法來進行。但是,如果是一個自定義的類的對象呢?比如自定義的兩個圖形、兩個日期等,這時,應該怎么對這些對象進行大小比較乃至排序呢?
基本類型及其包裝類的排序
在介紹自定義的類的比較與排序之前,還是先簡單回顧一下Java中的基本類型的數據與其包裝類的元素所組成的數組的排序方式:
public static void primitiveDataTypeSort() {// 基本數據類型可以直接排序int[] ints = {3, 1, 5, 2, 4};java.util.Arrays.sort(ints);for(int number: ints)System.out.print(number + " ");System.out.println();// 包裝器類型(即對象形式)也可以直接進行排序Character[] characters = { new Character('a'), new Character('c'),new Character('d'), new Character('b'),};java.util.Arrays.sort(characters);for (Character character: characters) {System.out.print(character + " ");}}輸出:
1 2 3 4 5 a b c d對于List中的類:
public static void collectionsSortTest() {List<Double> doubleList = new ArrayList<>();doubleList.add(10.5);doubleList.add(-0.5);doubleList.add(1.5);for (double num: doubleList) System.out.print(num + " ");System.out.println();Collections.sort(doubleList);for (double num: doubleList) System.out.print(num + " ");}輸出:
10.5 -0.5 1.5 -0.5 1.5 10.5可以看到,對于基本類型與其包裝類的對象而言,應用Java自帶的Arrays.sort()與Collections.sort()可以很方便地對其進行排序操作。
使用Comparable接口
Comparable接口定義了compareTo方法,用于對象之間的比較
為了實現這樣自定義對象的排序,我們可以將這樣的類定義為“可比較”的,為了實現這樣的要求,應該使每個對象可以調用一個共同的比較方法。在Java中已經對于這樣的“可比較”做了定義,即規(guī)定了Comparable接口來對“可比較”進行了抽象,因此對于我們希望其實例之間可以相互比較的自定義的類,需要實現Comparable接口,使每個對象都有共同方法comparable。
Comparable接口的定義如下所示:
compareTo方法判斷這個對象相對于給定對象o的順序,當這個對象小于、等于或大于給定對象o時,分別返回負整數、0或正整數。
Comparable接口是一個反省接口。在實現該接口時,泛型類型E被替換成一種具體的類型。Java類庫中的許多類實現了Comparable接口以定義對象的自然順序。Byte、Short、Integer、Long、Float、Doule等基本類型的包裝類,Character、BigInteger、BigDecimal、Calendar、String以及Date這樣常用的類,都實現了Comparable接口。
如在Java API中,Integer類中有如下定義:
public final class Integer extends Number implements Comparable<Integer> {//... class body omitted..@Overridepublic int compareTo(Integer anotherInteger) {return compare(this.value, anotherInteger.value);} }因此,整型數字時可以比較的,類似的,其它類型的數字也是可以比較的,字符串是可以比較的,日期也是如此。若需要直接比較其大小,可以使用compareTo方法來進比較。
對于compareTo方法直接進行調用的效果如下:
public static void compareToTest() {System.out.println(new Integer(3).compareTo(new Integer(5)));System.out.println("ABC".compareTo("ABE"));java.util.Date date1 = new java.util.Date(2013, 1, 1);java.util.Date date2 = new java.util.Date(2012, 1, 1);System.out.println(date1.compareTo(date2)); }輸出:
-1 -2 1第一行顯示一個負數,因為3小于5,第二行i按時一個負數,因為ABC小于ABE,最后顯示一個正數,因為date1大于date2。
由于所有Comparable對象都有compareTo()方法,如果對象是Comparable接口類型的實例的話,Java API中的java.util.Arrays.sort(Object[])方法就可以使用compareTo()方法來對數組中的對象進行比較和排序。
下面,我們來自定義一個類來實驗這個接口的用法。首先,我們定義一個圓圈的類Circle:
public class Circle {private double radius;public Circle() { this.radius = 0; }public Circle(double radius) { this.radius = radius;}public double getArea() { return Math.PI * radius * radius; }public void getName() {System.out.print(" Circle:" + this.radius);} }這時,如果我們定義一個Circle類型的數組,然后直接對其進行排序,就會得到一個“未實現Comparable接口”的錯誤,如下:
public static void main(String[] args) {Circle[] circles = new Circle[] {new Circle(4), new Circle(3), new Circle(5)};for (Circle circle: circles)System.out.println(circle.getArea());Arrays.sort(circles); }輸出:
50.26548245743669 28.274333882308138 78.53981633974483 Exception in thread "main" java.lang.ClassCastException: learning_java.sortTry.Circle cannot be cast to java.lang.Comparable由輸出的前三行顯示可以看到我們已經成功地得到了三個圓圈的實例,但是由于Circle類并沒有實現Comparable接口,因此不能直接使用Arrays.sort()方法來對這個數組進行排序。
這時,我們重新定義一個實現了Comparable接口的圓圈的類CircleComparable:
public class CircleComparableextends Circleimplements Comparable<CircleComparable> {public CircleComparable(double radius) {super(radius);}public int compareTo(CircleComparable circleToCom) {if (this.getArea() > circleToCom.getArea())return 1;else if (this.getArea() < circleToCom.getArea())return -1;elsereturn 0;} }在上面的代碼中,定義了一個擴展自Circle的類CircleComparable,并在這個擴展類中實現了Comparable接口,用compareTo(CircleComparable circleToCom)方法來比較兩個圓的面積。因此,CircleComparable類的實例既是自己的一個實例,也是Circle和Comparable的實例,當然也是Object的一個實例。接下來再來對其進行一次測試:
public static void main(String[] args) {CircleComparable c = new CircleComparable(1);System.out.println("c instanceof CircleComparable:\t" + (c instanceof CircleComparable));System.out.println("c instanceof Circle:\t" + (c instanceof Circle));System.out.println("c instanceof Comparable:\t" + (c instanceof Comparable));System.out.println("c instanceof Object:\t" + (c instanceof Object));Circle[] circles = new CircleComparable[] {new CircleComparable(4), new CircleComparable(3), new CircleComparable(5)};for (Circle circle: circles)System.out.print(circle.getArea() + " ");System.out.println();Arrays.sort(circles);for (Circle circle: circles)System.out.print(circle.getArea() + " "); }輸出:
c instanceof CircleComparable: true c instanceof Circle: true c instanceof Comparable: true c instanceof Object: true 50.26548245743669 28.274333882308138 78.53981633974483 28.274333882308138 50.26548245743669 78.53981633974483可以看到,排序后我們得到了正確的升序輸出。
接口提供了通用程序設計的一種形式,在這個例子中,如果不同接口,很難使用通用的sort()方法來排序對象,因為必須使用多重繼承才能同時繼承Comparable和其基類。
在這一部分的最后再提一點,Object類包含equals方法,它的目的就是為了讓Object類的子類來覆蓋它,以比較對象的內容是否相同。假設Object類包含一個類似于Comparable接口中所定義的comnpareTo方法,那么sort方法可以用來比較一組任意的對象。Object類中是否應該包含一個compareTo方法尚有爭論,由于在Object類中沒有定義compareTo方法,所以Java中定義了Comparable接口,以便能夠對兩個Comparable接口的實例對象進行比較。在這里,雖然即使不遵守,編譯器也不會進行報錯,但是強烈建議compareTo應該與equals保持一致,即對于兩個對象o1與o2,o1.equals(o2)為true與o1.compareTo(o2)==0成立的條件應該相同。
使用Comparator接口
Comparator可以用于比較沒有實現Comparable的類的對象
在前一節(jié)中我們已經實現了使用Comparable接口來比較元素,Java API中的許多常用的類,比如Number的子類Integer、BigInteger、BigDecima等與String、Date等類,都實現了Comparable接口,因此,這些類可以直接用于比較。
但是,如果我們所要處理的元素的類沒有實現Comparable接口呢?此時這些元素可以進行比較么?在上面,我們舉了一個例子,一個沒有實現Comparable接口的類Circle,并創(chuàng)建了一個Circle類型的數組,實驗表明,并不能直接將其進行排序,那么是不是對于這些類就不能實現比較與排序了呢?
答案是我們可以通過定義一個比較器(comparator)來實現不同類的元素的比較。要做到這一點,需要創(chuàng)建一個實現java.util.Comparator接口的類并重寫它的cmopare方法。
在這里,我們先來看一下這個Comparator接口的定義:
public interface Comparator<T> {// ...int compare(T o1, T o2);// .. }之后,我們可以通過實現這個接口來寫一個Circle類的比較器CircleComparator:
import java.util.Comparator;public class CircleComparatorimplements Comparator<Circle> {public int compare(Circle o1, Circle o2) {if (o1.getArea() > o2.getArea())return 1;else if (o1.getArea() < o2.getArea())return -1;elsereturn 0;} }在這個類中,我們通過覆蓋compare方法來實現了Comparator接口,完成了一個Circle的比較器。在compare()方法中,對輸入的兩個Circle的對象的面積進行判斷,若第一個圓的面積大于第二個圓的面積,則返回1,反之則返回-1,而若兩個圓的面積相同,則返回0。
若簡單地調用這個比較器:
Circle circleOne = new Circle(3); Circle circleTwo = new Circle(5); CircleComparator comparator = new CircleComparator(); System.out.println(comparator.compare(circleOne, circleTwo)); //的一個圓比第二個圓的面積小,則會返回-1則輸出:
-1而若我們想要像前面的例子一樣,實現數組的排序,我們可以先看一下`Arrays’類的部分源碼:
public static <T> void sort(T[] a, Comparator<? super T> c) {if (c == null) {sort(a);} else {if (LegacyMergeSort.userRequested)legacyMergeSort(a, c);elseTimSort.sort(a, 0, a.length, c, null, 0, 0);} }可以看到上面這段代碼中是’Arrays.sort()'的一種實現,在這個方法中,輸入的第二個參數就是我們上面所構造的比較器。而這種數組的排序方法,其實就是按照我們所寫的對于對象的比較方法,來對數組進行排序。
其應用可以看如下例子:
public static void circleSortTest2() {Circle[] circles = {new Circle(3), new Circle(1), new Circle(2)};java.util.Arrays.sort(circles, new CircleComparator());for (Circle circle: circles) {System.out.print(circle.getArea() + " ");} }輸出:
3.141592653589793 12.566370614359172 28.274333882308138從輸出可以看到,3個圓類的對象確實按照面積大小進行了升序排列。
結合匿名內部類Comparator接口
如果在程序中我們只是在某個地方需要使用一次Comparator接口來對某個沒有實現Comparable接口的類的對象的數組或列表來進行比較或者排序,這時,可能會覺得需要專門寫一個類似乎有些麻煩。或者說,我們在調用一個不能排序的對象的時候,想要將它可以排序這個特性封裝到一個類中(比如在leetcode上做題的時候,只能提交一個類),這時,我們便可以結合內部類及匿名內部類來編寫代碼。
這里給出一個例子,還是對于上面我們的圓來進行排序,但是,在這里我們使用匿名內部類來完成這個操作。定義在另一個類的內部的類便被稱為內部類,而沒有顯式地寫出其類名的內部類便被稱為匿名內部類,其詳細解釋可以看匿名內部類里的解釋。
代碼:
import java.util.*;public class TestSort {public static void main(String[] args) {CircleSortWithInner();}public static void CircleSortWithInner() {Circle circle1 = new Circle(3);Circle circle2 = new Circle(1);Circle circle3 = new Circle(4);Circle circle4 = new Circle(2);Circle[] circles = {circle1, circle2, circle3, circle4};for (Circle circle: circles) circle.getName();Arrays.sort(circles, new Comparator<Circle>() {@Overridepublic int compare(Circle o1, Circle o2) {if(o1.getArea() > o2.getArea()) return 1;else if(o1.getArea() < o2.getArea()) return -1;else return 0;}});System.out.println();for (Circle circle: circles) circle.getName();} }輸出:
Circle:3.0 Circle:1.0 Circle:4.0 Circle:2.0 Circle:1.0 Circle:2.0 Circle:3.0 Circle:4.0可以看到,這個圓的數組已經被正確地排序了。
在這個例子中,向Arrays.sort方法中傳入的第二個參數,應為Comparator接口的一個對象,這里就是用匿名內部類來實現定義這樣一個實現Comparator接口的類并生成一個其對象并將其傳入方法。
如果只需使用一次的話,這種定義比較器的方法還是比較方便的,但是也會讓代碼的可讀性下降,因此使用的時候需要權衡一下。
下面說一下降序
升序排列代碼
import java.util.Arrays; import java.util.Comparator; public class Test{public static void main(String args[]) {//注意這里的數據類型必須為Integer而不能為int,因為只有Integer類繼承了Comparator接口而不是intInteger[] _arr = new Integer[] { 5, 3, 1, 2, 4 };Arrays.sort(_arr, new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return o1-o2;}});for(int number: _arr)System.out.print(number + " ");System.out.println();} }輸出:1 2 3 4 5
降序排列代碼
輸出:5 4 3 2 1
總結
以上是生活随笔為你收集整理的Java 中类的比较与排序方法(应用Comparable接口与Comparator接口)通俗易懂的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Address already in u
- 下一篇: Java 数组升序降序排列Arrays.