引入
用了一段時間的jdk8的新特性,lambda表達式、方法引用、stream流,用起來是真的順手啊,最近碰到了一個排序的問題,引發了一些思考,然後就寫了這篇部落格,歸納總結sorted的用法,在做筆記的同時也讓自己有更深的了解。
資料準備
1. 依賴
我喜歡用google的集合工具類,讓我構造測試資料更簡便。然後也用lombok,依賴:
<!--google集合工具類-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.0-jre</version>
</dependency>
<!--lombok,需要插件配合使用-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
2. 相關類
User類
/*
*鍊式調用,我也有寫相關部落格,不過不是介紹基礎用法的,是這個鍊式調用一個不太完美的地方,
*感興趣的可以去看一看:https://blog.csdn.net/ql_7256/article/details/120274432
*/
@Data
@Accessors(chain = true)
public class User {
private String username;
private String password;
private Integer age;
private Integer height;
private Address address;
private Map others;
}
Address類,注意:這個類在後續的測試中要改動
@Data
@Accessors(chain = true)
public class Address {
private String province;
private String city;
private String county;
}
資料準備,
private List<User> users = new ArrayList<User>() {{
add(new User().setUsername("張三").setPassword("123456").setAge(20).setHeight(170)
.setAddress(new Address().setProvince("四川省").setCity("成都市").setCounty("武侯區"))
.setOthers(ImmutableMap.builder().put("sorted","AAA").put("bbb","BBB").put("ccc","CCC").build()));
add(new User().setUsername("李四").setPassword("123456").setAge(16).setHeight(175)
.setAddress(new Address().setProvince("四川省").setCity("成都市").setCounty("錦江區"))
.setOthers(ImmutableMap.builder().put("sorted","DDD").put("eee","EEE").put("fff","FFF").build()));
add(new User().setUsername("王五").setPassword("123456").setAge(20).setHeight(180)
.setAddress(new Address().setProvince("四川省").setCity("成都市").setCounty("青羊區"))
.setOthers(ImmutableMap.builder().put("sorted","GGG").put("hhh","HHH").put("iii","III").build()));
add(new User().setUsername("趙六").setPassword("123456").setAge(17).setHeight(168)
.setAddress(new Address().setProvince("四川省").setCity("成都市").setCounty("高新區"))
.setOthers(ImmutableMap.builder().put("sorted","JJJ").put("kkk","KKK").put("lll","LLL").build()));
}};
private List<String> strings = new ArrayList<String>() {{
add("222");add("666");add("444");add("111");add("333");add("555");
}};
private List<Integer> integers = new ArrayList<Integer>() {{
add(222);add(555);add(666);;add(333);add(444);add(111);
}};
private List others = new ArrayList() {{
add(444);add(555);add(666);add(111);add(222);add(333);
}};
初體驗
stream流、方法引用、lambda那些前置知識咱們就不說了哈,直接上手,先體直覺的驗一下排序
List<String> sortedStrings = strings.stream().sorted().collect(Collectors.toList());
// [111, 222, 333, 444, 555, 666]
System.out.println(sortedStrings);
List<User> sortedUsers = users.stream().sorted(Comparator.comparing(e -> e.getAge())).collect(Collectors.toList());
// 等效寫法如下
// List<User> sortedUsers = users.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toList());
// [User(username=李四, password=123456, age=16, height=175, address=Address(province=四川省, city=成都市, county=錦江區), others={sorted=DDD, eee=EEE, fff=FFF}), User(username=趙六, password=123456, age=17, height=168, address=Address(province=四川省, city=成都市, county=高新區), others={sorted=JJJ, kkk=KKK, lll=LLL}), User(username=張三, password=123456, age=20, height=170, address=Address(province=四川省, city=成都市, county=武侯區), others={sorted=AAA, bbb=BBB, ccc=CCC}), User(username=王五, password=123456, age=20, height=180, address=Address(province=四川省, city=成都市, county=青羊區), others={sorted=GGG, hhh=HHH, iii=III})]
System.out.println(sortedUsers);
很簡單,也沒啥難了解的,就是排序
基礎用法
排序的初體驗之後,我們來看看幾種正式場景下的使用
1. 降序幾種方式
在上面的體驗排序中,排序的結果預設都是升序的,那如果我要降序呢?那怎麼辦?有三種方式,或者三種寫法
1. 使用reversed
根據user中的age降序
List<User> collect = users.stream().sorted(Comparator.comparing(User::getAge).reversed()).collect(Collectors.toList());
// [User(username=張三, password=123456, age=20, height=170, address=Address(province=四川省, city=成都市, county=武侯區), others={sorted=AAA, bbb=BBB, ccc=CCC}), User(username=王五, password=123456, age=20, height=180, address=Address(province=四川省, city=成都市, county=青羊區), others={sorted=GGG, hhh=HHH, iii=III}), User(username=趙六, password=123456, age=17, height=168, address=Address(province=四川省, city=成都市, county=高新區), others={sorted=JJJ, kkk=KKK, lll=LLL}), User(username=李四, password=123456, age=16, height=175, address=Address(province=四川省, city=成都市, county=錦江區), others={sorted=DDD, eee=EEE, fff=FFF})]
System.out.println(collect);
根據User中age先排序,預設情況下是升序,然後再逆序一下,就變成了降序。但是這樣有點不好,因為是先排序然後在逆序的,要兩步操作。
2.使用Comparator.reverseOrder
根據user中的age降序
List<User> collect1 = users.stream().sorted(Comparator.comparing(User::getAge, Comparator.reverseOrder())).collect(Collectors.toList());
// [User(username=張三, password=123456, age=20, height=170, address=Address(province=四川省, city=成都市, county=武侯區), others={sorted=AAA, bbb=BBB, ccc=CCC}), User(username=王五, password=123456, age=20, height=180, address=Address(province=四川省, city=成都市, county=青羊區), others={sorted=GGG, hhh=HHH, iii=III}), User(username=趙六, password=123456, age=17, height=168, address=Address(province=四川省, city=成都市, county=高新區), others={sorted=JJJ, kkk=KKK, lll=LLL}), User(username=李四, password=123456, age=16, height=175, address=Address(province=四川省, city=成都市, county=錦江區), others={sorted=DDD, eee=EEE, fff=FFF})]
System.out.println(collect1);
這種方式原理和和方法1差不多,隻是寫法不一樣
3. 在sorted中使用compareTo
方式1和方式2都是利用sorted預設序,然後再逆序來實作排序的,這樣會有兩個步驟,先升序,然後再逆序。難道就沒有直接按照降序來排序的方法?肯定是有的。既然是排序,肯定是可以指定規則的
按照年齡降序
List<User> collect2 = users.stream().sorted((x, y) -> y.getAge().compareTo(x.getAge())).collect(Collectors.toList());
// [User(username=張三, password=123456, age=20, height=170, address=Address(province=四川省, city=成都市, county=武侯區), others={sorted=AAA, bbb=BBB, ccc=CCC}), User(username=王五, password=123456, age=20, height=180, address=Address(province=四川省, city=成都市, county=青羊區), others={sorted=GGG, hhh=HHH, iii=III}), User(username=趙六, password=123456, age=17, height=168, address=Address(province=四川省, city=成都市, county=高新區), others={sorted=JJJ, kkk=KKK, lll=LLL}), User(username=李四, password=123456, age=16, height=175, address=Address(province=四川省, city=成都市, county=錦江區), others={sorted=DDD, eee=EEE, fff=FFF})]
System.out.println(collect2);
這種方式是通過Integer中的compareTo方法來實作降序的,當然也可以自己實作,隻不過age是Integer類型,既然Integer中已經實作兩個Integer比較的方法,就可以偷個懶
4. 在sorted自定義規則
List<User> collect3 = users.stream().sorted((x, y) -> y.getAge() - x.getAge()).collect(Collectors.toList());
// [User(username=張三, password=123456, age=20, height=170, address=Address(province=四川省, city=成都市, county=武侯區), others={sorted=AAA, bbb=BBB, ccc=CCC}), User(username=王五, password=123456, age=20, height=180, address=Address(province=四川省, city=成都市, county=青羊區), others={sorted=GGG, hhh=HHH, iii=III}), User(username=趙六, password=123456, age=17, height=168, address=Address(province=四川省, city=成都市, county=高新區), others={sorted=JJJ, kkk=KKK, lll=LLL}), User(username=李四, password=123456, age=16, height=175, address=Address(province=四川省, city=成都市, county=錦江區), others={sorted=DDD, eee=EEE, fff=FFF})]
System.out.println(collect3);
比較實際的最終結果就是傳回一個數字,大于0、小于0、等于0,分别就代表大于、小于、等于,是以在sorted方法中傳回一個做減法的數字即可。
2. 多級排序
比如我現在的需求是先按照年齡降序,年齡相同的再按照名字降序
List<User> collect = users.stream().sorted(Comparator.comparing(User::getAge, Comparator.reverseOrder()).thenComparing(User::getHeight, Comparator.reverseOrder())).collect(Collectors.toList());
// [User(username=王五, password=123456, age=20, height=180, address=Address(province=四川省, city=成都市, county=青羊區), others={sorted=GGG, hhh=HHH, iii=III}), User(username=張三, password=123456, age=20, height=170, address=Address(province=四川省, city=成都市, county=武侯區), others={sorted=AAA, bbb=BBB, ccc=CCC}), User(username=趙六, password=123456, age=17, height=168, address=Address(province=四川省, city=成都市, county=高新區), others={sorted=JJJ, kkk=KKK, lll=LLL}), User(username=李四, password=123456, age=16, height=175, address=Address(province=四川省, city=成都市, county=錦江區), others={sorted=DDD, eee=EEE, fff=FFF})]
System.out.println(collect);
thenComparing顧名思義,再比較
進階
放在以前,我肯定會從stream 排序的api說起,看看有哪些方法,再看怎麼調用,但是這流式太過抽象,是以我先講了怎麼用,再回頭來看看有哪些api,本質是什麼
1. 本質
在java.util.stream.Stream中,sorted方法有兩個重載形式

一個是無參,一個是需要一個參數java.util.Comparator。
1. Stream sorted();
其實這兩個方法我們都用過,在初體驗中,第一個就是無參的,這樣會根據預設規則排序,至于預設規則是什麼,就是排序對象實作的java.lang.Comparable接口中的compareTo方法,不然你試試跑一下這個
List<User> collect = users.stream().sorted().collect(Collectors.toList());
直接報錯,報錯的原因就是,你要排序一堆User,但是sorted這個無參的方法不知道排序的規則是什麼。是以,在使用這個無參的方法時,被排序的元素必須得實作java.lang.Comparable接口,來指定排序規則。
2. Stream sorted(Comparator<? super T> comparator);
除了初體驗中的第一個排序,其他的全都是使用的這個方法,很神奇是吧?我好像傳的參數不止這樣。
但事實上就是這樣子,隻傳了這個一個參數,無非有兩種傳參形式:一種是确确實實的傳了一個java.util.Comparator進去,另外一種是自己實作了java.util.Comparator中的抽象方法compare,這個方法用來進行元素間的比較。因為java.util.Comparator是一個函數式接口,接口中隻有compare這一個抽象方法,是以可以結合lambda表達式使用。
2. 拓展及思考
List<User> sortedUsers = users.stream().sorted(Comparator.comparing(x -> x.getOthers().get("aaa").toString())).collect(Collectors.toList());
List<User> sortedUsers2 = users.stream().sorted(Comparator.comparing(x -> x.getOthers().get("aaa").toString()).reversed()).collect(Collectors.toList());