重複 Java 8でMap<K、V>へのリスト<Object[]>




java ラムダ式 (2)

多くの場合、次のようなクエリの結果を変換する必要があります。

select category, count(*)
from table
group by category

キーがカテゴリで値が同じカテゴリに属する​​レコードの数であるマップへ。

多くの永続フレームワークはList<Object[]>のような問い合わせの結果を返します。ここでオブジェクト配列は2つの要素(categoryと返された各結果セット行のcount)を含みます。

このリストを対応するマップに変換するための最も読みやすい方法を見つけようとしています。

もちろん、従来の方法では、マップを作成してエントリを手動で配置します。

Map<String, Integer> map = new HashMap<>();
list.stream().forEach(e -> map.put((String) e[0], (Integer) e[1]));

私の頭に浮かんだ最初のワンライナーは、箱から出してすぐに使えるCollectors.toMapコレクターを利用することでした。

Map<String, Integer> map = list.stream().collect(toMap(e -> (String) e[0], e -> (Integer) e[1]));

しかし、このe -> (T) e[i]構文は、従来の方法よりもやや読みにくくなっています。 これを克服するために、そのようなすべての状況で再利用できるutilメソッドを作成することができます。

public static <K, V> Collector<Object[], ?, Map<K, V>> toMap() {
  return Collectors.toMap(e -> (K) e[0], e -> (V) e[1]);
}

それから私は完璧なワンライナーを手に入れました:

Map<String, Integer> map = list.stream().collect(Utils.toMap());

型推論のため、キーと値をキャストする必要さえありません。 しかし、これはコードの他の読者(utilメソッドのシグネチャのCollector<Object[], ?, Map<K, V>>など)を理解するのは少し難しいCollector<Object[], ?, Map<K, V>>

私は疑問に思っています、もっと読みやすく/エレガントな方法でこれを達成するのを助けることができるJava 8ツールボックスの中に何か他にありますか?


あなたの現在の「ワンライナー」は現状のままで問題ないと思います。 しかし、コマンドに組み込まれたマジックインデックスが特に気に入らなければ、enumにカプセル化できます。

enum Column {
    CATEGORY(0), 
    COUNT(1);

    private final int index;

    Column(int index) {
        this.index = index;
    }

    public int getIntValue(Object[] row) {
        return (int)row[index]);
    }

    public String getStringValue(Object[] row) {
        return (String)row[index];
    }
}

それからあなたは抽出コードがもう少し明確になります:

list.stream().collect(Collectors.toMap(CATEGORY::getStringValue, COUNT::getIntValue));

理想的には、列にtypeフィールドを追加して、正しいメソッドが呼び出されることを確認します。

質問の範囲外ですが、理想的には、クエリをカプセル化する行を表すクラスを作成します。 次のようなものです(わかりやすくするためにゲッターをスキップしました)。

class CategoryCount {
    private static final String QUERY = "
        select category, count(*) 
        from table 
        group by category";

    private final String category;
    private final int count;

    public static Stream<CategoryCount> getAllCategoryCounts() {
        list<Object[]> results = runQuery(QUERY);
        return Arrays.stream(results).map(CategoryCount::new);
    }

    private CategoryCount(Object[] row) {
        category = (String)row[0];
        count = (int)row[1];
    }
}

これは、クエリと行のデコードとの間の依存関係を同じクラスに入れ、不要な詳細をすべてユーザーから隠します。

それからあなたの地図を作成することはなります:

Map<String,Integer> categoryCountMap = CategoryCount.getAllCategoryCounts()
    .collect(Collectors.toMap(CategoryCount::getCategory, CategoryCount::getCount));

クラスキャストを隠す代わりに、読みやすくするためにいくつかの関数を作成します。

Map<String, Integer> map = results.stream()
        .collect(toMap(
                columnToObject(0, String.class),
                columnToObject(1, Integer.class)
        ));

完全な例:

package com.bluecatcode.learning.so;

import com.google.common.collect.ImmutableList;

import java.util.List;
import java.util.Map;
import java.util.function.Function;

import static java.lang.String.format;
import static java.util.stream.Collectors.toMap;

public class Q35689206 {

    public static void main(String[] args) {
        List<Object[]> results = ImmutableList.of(
                new Object[]{"test", 1}
        );

        Map<String, Integer> map = results.stream()
                .collect(toMap(
                        columnToObject(0, String.class),
                        columnToObject(1, Integer.class)
                ));

        System.out.println("map = " + map);
    }

    private static <T> Function<Object[], T> columnToObject(int index, Class<T> type) {
        return e -> asInstanceOf(type, e[index]);
    }

    private static <T> T asInstanceOf(Class<T> type, Object object) throws ClassCastException {
        if (type.isAssignableFrom(type)) {
            return type.cast(object);
        }
        throw new ClassCastException(format("Cannot cast object of type '%s' to '%s'",
                object.getClass().getCanonicalName(), type.getCanonicalName()));
    }
}




collectors