今年初學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 對象集合定義如下:

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定義中,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,讓集合操作更優雅簡潔。