Java Stream を任意順序でソートする


はじめに

以下のような Itemtop -> middle -> bottom の順序でソートしたい。

List<Item> list = Arrays.asList(
        new Item(1, "bottom"),
        new Item(2, "top"),
        new Item(3, "middle"),
        new Item(4, "top"));

第2ソートキーを id とすると、Guava では ComparisonChain で Ordering.explicit() を指定することとなる。

ComparisonChain.start()
    .compare(o1.getName(), o2.getName(), Ordering.explicit(priorities).nullsLast())
    .compare(o1.getId(), o2.getId(), Ordering.natural().nullsLast())
    .result();


Java8 Stream での任意順序ソート

Java8 Stream では、Ordering.explicit() に相当するものは無いので、以下のようにする。

List<String> priorities = Arrays.asList("top", "middle", "bottom");

List<Item> sorted = list.stream()
    .sorted(Comparator
        .comparing((Item item) -> priorities.indexOf(item.getName()))
        .thenComparing(Item::getId))
    .collect(Collectors.toList());

以下のように並び替えることができる。

Item {2, 'top'}, Item {4, 'top'}, Item {3, 'middle'}, Item {1, 'bottom'}


未知のキーを末尾に持ってくる

前述のソートだと、new Item(5, "?") であったり new Item(6, null) のような、未知キーが先頭にきてしまう。

その場合は、逆順でソートした方がよい。

List<String> priorities = Arrays.asList("top", "middle", "bottom");
Collections.reverse(priorities);
        
List<Item> sorted = list.stream()
    .sorted(Comparator
        .comparing((Item item) -> priorities.indexOf(item.getName()),
                   Comparator.reverseOrder())
        .thenComparing(Item::getId))
    .collect(Collectors.toList());

以下のような並びになる。

Item {2, 'top'}, Item {4, 'top'}, Item {3, 'middle'}, Item {1, 'bottom'},
    Item {5, '?'}, Item {6, null}