Java中對集合排序的實現演變:從Comparable、Comparator到lambda,從啰嗦到簡潔


今年初學Java,是個新人。若文中有錯誤紕漏,希望能指出,見諒。

 

目標:對 User 對象集合進行排序,要求使用簡單並且代碼可讀性強。

User 類定義如下:

public class User {

    /**
     * id
     */
    private String id;
    /**
     * 姓名
     */
    private String Name;
    /**
     * 年齡
     */
    private int age;
    /**
     * 身高
     */
    private Integer height;
    /**
     * 體重
     */
    private Double weight;
    /**
     * 性別
     */
    private boolean sex;
    /**
     * 出生年月
     */
    private Date birthday;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Integer getHeight() {
        return height;
    }

    public void setHeight(Integer height) {
        this.height = height;
    }

    public Double getWeight() {
        return weight;
    }

    public void setWeight(Double weight) {
        this.weight = weight;
    }

    public boolean isSex() {
        return sex;
    }

    public void setSex(boolean sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}
User類

User 對象集合定義如下:

private List<User> getUsers() throws Exception{
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

    User u1 = new User();
    u1.setId(UUID.randomUUID().toString());
    u1.setName("隔壁老王");
    u1.setSex(false);
    u1.setAge(33);
    u1.setHeight(168);
    u1.setWeight(72.5);
    u1.setBirthday(format.parse("1970-05-13"));

    User u2 = new User();
    u2.setId(UUID.randomUUID().toString());
    u2.setName("小頭爸爸");
    u2.setSex(false);
    u2.setAge(30);
    u2.setHeight(172);
    u2.setWeight(59.8);
    u2.setBirthday(format.parse("1980-10-08"));

    User u3 = new User();
    u3.setId(UUID.randomUUID().toString());
    u3.setName("大頭兒子的媽媽");
    u3.setSex(true);
    u3.setAge(27);
    u3.setHeight(165);
    u3.setWeight(47.3);
    u3.setBirthday(format.parse("1983-11-15"));

    User u4 = new User();
    u4.setId(UUID.randomUUID().toString());
    u4.setName("大頭兒子");
    u4.setSex(false);
    u4.setAge(9);
    u4.setHeight(108);
    u4.setWeight(37D);
    u4.setBirthday(format.parse("2001-04-01"));

    List<User> data = new ArrayList<>();
    data.add(u1);
    data.add(u2);
    data.add(u3);
    data.add(u4);

    return data;
}
User集合定義

 
User定義中,age(年齡)的數據類型為 int,height(身高)的數據類型為 Integer,以基本類型、包類型為比較,分別對它們實現排序。

先實現對 height(身高)的排序。

實現按 height(身高)排序的方式有多種,按照“jdk版本從低到高”、“寫法從復雜到簡單”的次序逐一演示。

方法1:定義比較器類

List<User> data = this.getUsers();
System.out.println("原始數據:");
this.prints(data);

class HeightComparator implements Comparator {
    public int compare(Object object1, Object object2) {
            User p1 = (User) object1;
            User p2 = (User) object2;
            return p1.getHeight().compareTo(p2.getHeight());
        }
    }

Collections.sort(data, new HeightComparator());
System.out.println("按身高排序后:");
this.prints(data);

 

方法2:定義比較器對象

List<User> data = this.getUsers();
System.out.println("原始數據:");
this.prints(data);

    Comparator<User> c1 = new Comparator<User>() {
        @Override
        public int compare(User o1, User o2) {
            return o1.getHeight() - o2.getHeight();
        }
    };

Collections.sort(data, c1);
System.out.println("按身高排序后:");
this.prints(data);

 

方法3:以lambda方式定義比較器對象

List<User> data = this.getUsers();
System.out.println("原始數據:");
this.prints(data);

Function<User, Integer> f1 = u -> u.getHeight();
Comparator<User> c1 = Comparator.comparing(f1);
//上述2句代碼,也可以簡化成一句:
//Comparator<User> c1 = Comparator.comparing(u -> u.getHeight());

Collections.sort(data, c1);
System.out.println("按身高排序后:");
this.prints(data);

 

jrk1.8之后實現了lambda表達式,於是有了Comsumer、Function、Predicate這些基於lambda的實現(在java里不知道概念叫什么),相應的Comparator也實現了對Function的支持,因此方法3需要jdk1.8的支持。從上述3種方式來看,代碼量越來越少,也越來越優雅。
方法3已經能夠一句代碼實現按 height(身高)排序的功能:

Collections.sort(data, Comparator.comparing(u -> u.getHeight()));

但這仍舊不足,因為排序操作應該只需要關心“要排序的集合”和“按什么排序”,上面這句代碼還多了“比較器”。不能忍,必須消滅它。

實現對List的工具類方法sort和sortDescending,分別表示順序排序和倒序排序,實現如下:

/**
     * 根據指定屬性對集合順序排序
     * @param data 集合對象
     * @param func 委托
     * @param <T> 數據類型
     * @param <R> 要排序的屬性的數據類型
     */
    public static <T, R extends Comparable<? super R>> void sort(List<T> data, Function<T, R> func){
        Comparator<T> comparator = Comparator.comparing(func);
        data.sort(comparator);
    }

    /**
     * 根據指定屬性對集合倒序排序
     * @param data 集合對象
     * @param func 委托
     * @param <T> 數據類型
     * @param <R> 要排序的屬性的數據類型
     */
    public static <T, R extends Comparable<? super R>> void sortDescending(List<T> data, Function<T, R> func){
        Comparator<T> comparator = Comparator.comparing(func).reversed();
        data.sort(comparator);
    }

由於jdk中只支持對List的排序(List對象內置sort方法,Collections.sort方法雖然封裝在Collections中,但其實也只支持List),因此可以將上述工具方法封裝在ListUtil中,有了這2個方法,就可以像下面那樣“快速”地寫出可讀性很高的排序了:

List<User> data = this.getUsers();
System.out.println("原始數據:");
this.prints(data);

ListUtil.sort(data, a -> a.getHeight());
System.out.println("按身高順序排序后:");
this.prints(data);

ListUtil.sortDescending(data, a -> a.getHeight());
System.out.println("按身高倒序排序后:");
this.prints(data);

若想根據其他屬性排序,動幾下(真的是幾下)手指頭,就可以實現:

ListUtil.sort(data, a -> a.getAge());

ListUtil.sortDescending(data, a -> a.getWeight());

 

那么實現對 age(年齡)的排序,和上面是不是一樣?
並不完全一樣,存在較大的差異。
像上面的方法1是無法實現對 age(年齡)的排序的,因為該屬性數據類型為 int。
我們來看一下方法1中一句關鍵代碼:

return p1.getHeight().compareTo(p2.getHeight());

Heigth是Integer類型,而這種包裝類,都是繼承了Comparable接口並實現了compareTo方法的,因此上述代碼中可以直接使用compareTo方法。但基本類型int並沒有該方法的實現,因此無法以方法1的方式實現排序。

方法2可以實現對 age(年齡)的排序,但當排序屬性非Number類型時,這個compare寫起來就微微麻煩了,因為compare的返回值的含義是“1表示大於,0表示等於,-1表示小於”。當排序屬性是boolean、string等類型時,得這樣寫:

public int compare(User o1, User o2) {
        //性別屬性,boolean類型
        int i1 = o1.isSex() ? 1 : 0;
        int i2 = o2.isSex() ? 1 : 0;
        return i1 - i2;
}


public int compare(UT o1, UT o2) {
        //姓名屬性,String類型
        int i1 = (o1.getName() == null ? 0 : (o1.getName().charAt(0)));
        int i2 = (o2.getName() == null ? 0 : (o2.getName().charAt(0)));
        return i1 - i2;
}

方法3可以實現對 age(年齡)的排序,並且也支持其他各種數據類型的屬性,Comparaor中會有對各種數據類型(包含非Number類型)的默認排序規則,比如boolean類型按照false->true排序。

List<User> data = this.getUsers();
System.out.println("原始數據:");
this.prints(data);

Function<User, Integer> f1 = u -> u.getAge();
Comparator<User> c1 = Comparator.comparing(f1);
//上述2句代碼,也可以簡化成一句:
//Comparator<User> c1 = Comparator.comparing(u -> u.getHeight());

Collections.sort(data, c1);
System.out.println("按年齡排序后:");
this.prints(data);

需要說明的是,雖然 age(年齡)的數據類型是int,但Function中的返回類型不能寫成int,因為Function的泛型定義並不支持基本類型。 
所以,如果要對 sex(性別,數據類型是 boolean)排序,這句Function的定義得這樣寫:

Function<UT, Boolean> f1 = u -> u.isSex();


綜上所述,無論是對 height(身高)的排序還是對 age(年齡)的排序,方法3都支持得非常棒。而前面貼出的擴展工具方法—— sort 和 sortDescending,都是基於方法3的,所以該擴展方法,適用於各種排序。


結尾。
擁抱變化吧,雖然目前java實現的lambda由於受擦除式泛型的限制,還不是非常靈活,但目前這些新寫法,無論是代碼數量、簡潔程度、優雅性、可讀性,在我看來都優於常規的for寫法。雖然jdk1.8本身暴露的lambda接口寥寥無幾,但我們能夠去擴展去完善這些api,讓集合操作更優雅簡潔。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM