社内se × プログラマ × ビッグデータ

プログラミングなどITに興味があります。

Java ソート条件を動的に指定してみたい

Java8 で書いてます。

静的に指定

// Item("name", "price", "reviewAve", "reviewNum")
Item itemA = new Item("itemA", 1000, 3.3f, 100);
Item itemB = new Item("itemB", 2000, 4.5f, 20);
Item itemC = new Item("itemC", 3000, 4.5f, 10);
		
List<Item> items = Arrays.asList(itemA, itemB, itemC);
		
// static sort condition		
System.out.println("sort by price desc");
items.sort(Comparator.comparing(Item::getPrice).reversed());  // <- こんな感じで指定
items.forEach(item -> System.out.println(item.getName()));

こんな感じで指定すれば、"price" の降順でソートされます。

sort by price desc
itemC
itemB
itemA

もし自分たちが欲しい結果がいつも "price" の降順なのであれば、これで十分かもしれません。
ただ、もし"price" の昇順で結果が欲しい時があれば、このプログラムでは期待する結果を返してくれません。

動的に指定してみたい

設定ファイルやプログラムの引数などから、ソート条件を与えてあげて、それに基づいてソートして欲しい。
ここでは、以下の手順での実現を考えてみます。
1. 想定されるソートの条件を Map に格納(key に条件、value にComparator)おく
2. プログラムの引数でソート条件を与える
3. 与えられたソート条件を基に、Map から Comparator を取得する
4. 取得した Comparator を基に、ソートする

ソート条件を保持するクラスを定義する

public final class SortCondition {
    private final String field;
    private final Direction direction;
	    
    public SortCondition(String field, Direction direction) {
    	this.field = field;
    	this.direction = direction;
    }
	    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof SortCondition) {
            SortCondition condition = (SortCondition) obj;
            return this.field.equals(condition.field) && this.direction.equals(condition.direction);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return Objects.hash(field, direction);
    }
}

昇順か降順か

protected enum Direction {
	ASC,
	DESC
}

想定されるソート条件を Map に格納(定義)

Map<SortCondition, Comparator<Item>> comparatorMap = new HashMap<>();
comparatorMap.put(new SortCondition("price", Direction.ASC), Comparator.comparing(Item::getPrice));
comparatorMap.put(new SortCondition("price", Direction.DESC), Comparator.comparing(Item::getPrice).reversed());
comparatorMap.put(new SortCondition("reviewAve", Direction.ASC), Comparator.comparing(Item::getReviewAve));
comparatorMap.put(new SortCondition("reviewAve", Direction.DESC), Comparator.comparing(Item::getReviewAve).reversed());
comparatorMap.put(new SortCondition("reviewNum", Direction.ASC), Comparator.comparing(Item::getReviewNum));
comparatorMap.put(new SortCondition("reviewNum", Direction.DESC), Comparator.comparing(Item::getReviewNum).reversed());

ソート条件を引数から取得

private List<SortCondition> getSortCondition(String[] args) {
  List<SortCondition> sortConditions = new ArrayList<>();
  for (int i = 0; i < args.length; i++) {
  	String[] condition = args[i].split(":", 0);
       	sortConditions.add(new SortCondition(condition[0], Direction.valueOf(condition[1])));
  }
  return sortConditions;
}

ソート条件から Comparator を構築

private Comparator<Item> comparatorBuilder(List<SortCondition> conditions) {
  Comparator<Item> dynamicComparator = this.comparatorMap.get(conditions.get(0));
  for (int i = 1; i < conditions.size(); i++) {
	dynamicComparator = dynamicComparator.thenComparing(this.comparatorMap.get(conditions.get(i)));
  }
  return dynamicComparator;
}

ソートする

private void sort(List<Item> items) {
  String[] args = {"reviewAve:DESC", "reviewNum:DESC"};
  List<SortCondition> sortConditions = getSortCondition(args);

  Comparator<Item> dynamicComparator = comparatorBuilder(sortConditions);
	
  System.out.println("sort by dynamic condition");
  items.sort(dynamicComparator);
  items.forEach(item -> System.out.println(item.getName()));
}

今回の例では、第1ソート条件を"reviewAve:DESC", 第2ソート条件を"reviewNum:DESC" としました。

sort by dynamic condition
itemB
itemC
itemA

所感

一応、やりたかったことは実現できましたが、欠点はやはり想定されるソート条件を予め Map に格納(定義)しているところで、結局定義しているソート条件以外は指定できない。
それを引数に基づいて呼び出しているに過ぎないところです。
リフレクションとか使えば、もっと柔軟に Comparator を生成することができるのかもしれないですが。