問題
在項目中,有一處地方需要對日期區間進行排序
我需要以日期區間的開始日為第一優先級,結束日為第二優先級進行排序
代碼
我當時寫的代碼如下:
List<Pair<LocalDate, LocalDate>> dateIntervals = new ArrayList<>();
// 省略構造日期區間
dateIntervals.sort(Comparator.comparing(Pair::getLeft).thenComparing(Pair::getRight));
這段看上去很正確的代碼,居然是沒辦法編譯的。
做了一些試驗
dateIntervals.sort(Comparator.comparing(Pair::getLeft));
當僅以日期開始日排序,可以編譯沒問題
那么把Comparator單獨提取出來呢
Comparator<Pair<LocalDate, LocalDate>> cmp = Comparator.comparing(Pair::getLeft);
dateIntervals.sort(cmp);
這樣當然是沒有問題的
Comparator<Pair<LocalDate, LocalDate>> cmp = Comparator.comparing(Pair::getLeft).thenComparing(Pair::getRight);
dateIntervals.sort(cmp);
這樣是沒法編譯的,和我原來的寫法其實沒有本質的區別
Comparator<Pair<LocalDate, LocalDate>> cmp = Comparator.comparing(Pair::getLeft);
cmp = cmp.thenComparing(Pair::getRight);
dateIntervals.sort(cmp);
當我再嘗試把thenComparing分開來寫時,居然又可以通過編譯了
對此我感到很困惑,我在偉大萬能的stackoverflow上翻到了一個類似的問題
這個哥們碰到的問題與我的問題雖然不是同一個,但卻是類似的。
本質的問題是Java語言的類型推導
來看一下Comparator#comparing
的源碼
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
再來看一下Comparator#thenComparing
的源碼
default <U extends Comparable<? super U>> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor)
{
return thenComparing(comparing(keyExtractor));
}
好的,再回顧一下下面這段可以通過編譯的代碼
Comparator<Pair<LocalDate, LocalDate>> cmp = Comparator.comparing(Pair::getLeft);
dateIntervals.sort(cmp);
由於dateIntervals
是List<Pair<LocalDate,LocalDate>>
類型,Java編譯器可以根據這個目標類型來進行推導,所以sort函數內的Comparator.comparing
應該返回的是Comparator<Pair<LocalDate,LocalDate>>
,那么comparing內的函數的入參是Pair<LocalDate,LocalDate>就確定下來了。
再來看一下不能編譯的如下代碼
Comparator<Pair<LocalDate, LocalDate>> cmp = Comparator.comparing(Pair::getLeft).thenComparing(Pair::getRight);
dateIntervals.sort(cmp);
問題是thenComparing
返回的Comparator<T>
中的T是跟着調用方走的,也就是意味着要得先知道前面一部分 Comparator.comparing(Pair::getLeft)
的類型,但是這種情況下前面這一部分沒辦法根據目標類型進行推導,所以類型推導在這里就陷入了一種僵局。
這不得不說是Java語言中類型推導還不夠完美的地方。
那么如何解決這個問題呢,除了上面那種Comparator分兩步走的情況,
直接指定范型類型來調用方法,專治各種范型推導失敗。
關於指定范型類型調用方法的語法規范可以參考JLS 15.12中寫明的方法調用。
所以,最后我寫的語句是如下的:
dateIntervals.sort(Comparator.<Pair<LocalDate, LocalDate>, LocalDate>comparing(Pair::getLeft) .thenComparing(Pair::getRight));