web-dev-qa-db-ja.com

Java 8 Streamを使用して配列をHashMapに変換する方法

Java 8 Streamを使用して配列をマップに変換する関数を書いています。

これが私が欲しかったものです

public static <K, V> Map<K, V> toMap(Object... entries) {
    // Requirements:
    // entries must be K1, V1, K2, V2, .... ( even length )
    if (entries.length % 2 == 1) {
        throw new IllegalArgumentException("Invalid entries");
    }

    // TODO
    Arrays.stream(entries).????
}

有効な使用法

Map<String, Integer> map1 = toMap("k1", 1, "k2", 2);

Map<String, String> map2 = toMap("k1", "v1", "k2", "v2", "k3", "v3");

無効な使用法

Map<String, Integer> map1 = toMap("k1", 1, "k2", 2, "k3");

何か助けはありますか?

ありがとう!

10
Loc

あなたは使うかもしれません

_public static <K, V> Map<K, V> toMap(Object... entries) {
    if(entries.length % 2 == 1)
        throw new IllegalArgumentException("Invalid entries");
    return (Map<K, V>)IntStream.range(0, entries.length/2).map(i -> i*2)
        .collect(HashMap::new, (m,i)->m.put(entries[i], entries[i+1]), Map::putAll);
}
_

しかし、(founded)unchecked警告が表示されます。あなたのメソッドは、任意のオブジェクトの配列に対して正しく型付けされた_Map<K, V>_を返すという約束を守ることができず、さらに悪いことに、例外で失敗することはありませんが、タイプが間違っています。

一般的に使用されるクリーナーのソリューションは

_public static <K, V> Map<K, V> toMap(
                               Class<K> keyType, Class<V> valueType, Object... entries) {
    if(entries.length % 2 == 1)
        throw new IllegalArgumentException("Invalid entries");
    return IntStream.range(0, entries.length/2).map(i -> i*2)
        .collect(HashMap::new,
                 (m,i)->m.put(keyType.cast(entries[i]), valueType.cast(entries[i+1])),
                 Map::putAll);
}
_

実行時に正当性がチェックされるため、警告なしでコンパイルできます。呼び出しコードを調整する必要があります:

_Map<String, Integer> map1 = toMap(String.class, Integer.class, "k1", 1, "k2", 2);
Map<String, String> map2 = toMap(
                           String.class, String.class, "k1", "v1", "k2", "v2", "k3", "v3");
_

実際の型をクラスリテラルとして指定する必要があることに加えて、ジェネリックキーまたは値の型(Classとして表現できないため)をサポートせず、コンパイル時の安全性がなく、ランタイムチェック。


それは価値があります Java 9を見る 。そこで、次のことができるようになります。

_Map<String, Integer> map1 = Map.of("k1", 1, "k2", 2);
Map<String, String>  map2 = Map.of("k1", "v1", "k2", "v2", "k3", "v3");
_

これにより、HashMapではなく、指定されていない型のimmutableマップが作成されますが、興味深い点はAPIです。

と組み合わせることができるメソッド<K,V> Map.Entry<K,V> entry(K k, V v)があります
<K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)を使用して、可変長のマップを作成します(ただし、varargsはまだ255個のパラメーターに制限されています)。

同様のことを実装できます:

_public static <K,V> Map.Entry<K,V> entry(K k, V v) {
    return new AbstractMap.SimpleImmutableEntry<>(k, v);
}
public static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries) {
    return Arrays.stream(entries)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
_

便利なメソッドofは唯一の方法で実装されています。これはタイプセーフで実行できます。次のように、引数の数が異なるオーバーロードされたメソッドとして

_public static <K,V> Map<K,V> of() {
    return new HashMap<>();// or Collections.emptyMap() to create immutable maps
}
static <K,V> Map<K,V> of(K k1, V v1) {
    return ofEntries(entry(k1, v1));
}
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2) {
    return ofEntries(entry(k1, v1), entry(k2, v2));
}
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
    return ofEntries(entry(k1, v1), entry(k2, v2), entry(k3, v3));
}
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
    return ofEntries(entry(k1, v1), entry(k2, v2), entry(k3, v3), entry(k4, v4));
}   
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
    return ofEntries(entry(k1, v1), entry(k2, v2), entry(k3, v3), entry(k4, v4));
}   
_

(Java 9は10個のマッピングでカットを行います。それ以上ある場合は、ofEntries(entry(k1, v1), …)バリアントを使用する必要があります)。

このパターンに従う場合は、toMapを書くのではなく、「map」で呼び出すのではなく、ofの名前をそのまま使用するか、Mapだけを使用する必要があります。 ]_ インターフェース。

これらのオーバーロードは非常にエレガントに見えないかもしれませんが、すべての問題を解決します。 Classオブジェクトを指定せずに、質問と同じようにコードを記述できますが、コンパイル時の型の安全性が得られ、奇数の引数を使用してコードを呼び出そうとしても拒否されます。

特定の数のパラメーターでカットする必要がありますが、すでに述べたように、可変引数でさえ無制限のパラメーターをサポートしていません。また、ofEntries(entry(…), …)フォームは、大きなマップにとってそれほど悪くありません。


コレクターCollectors.toMap(Map.Entry::getKey, Map.Entry::getValue)は不特定のマップタイプを返します。これは不変である可能性もあります(現在のバージョンではHashMapです)。 HashMapインスタンスが返されることを保証したい場合は、代わりにCollectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1,v2)->{throw new IllegalArgumentException("duplicate key");}, HashMap::new)を使用する必要があります。

10
Holger

キータイプと値タイプが異なるマップでは、必要なものを正確に取得することはおそらく機能しません。これは、Javaの可変アリティ宣言(Object... entries part)は1つのタイプのみをサポートします。

いくつかのオプションが思い浮かびます:

  1. 動的にチェックを行い、値が一致しない場合は不正な引数例外をスローすることができます。しかし、コンパイラの型チェックは失われます。

  2. Pairクラスを定義し、静的インポートで少し遊んで、ほとんど必要なものを取得できます。

例えば。:

class Pair<K,V> {
    final K k;
    final V v;
    Pair( K ak, V av) {
        k=ak;
        v=av;
    }
    static <A,B> Pair<A,B> p(A a, B b) {
        return new Pair(a,b);
    }
}

public class JavaTest8 {

    <K,V> Map<K,V> toMap( Pair<K,V>... pairs ) {
        return Arrays.stream(pairs).collect(Collectors.toMap(p->p.k, p->p.v));
    }

    public static void main(String[] args) {
        // Usage
        Map<String,Integer> sti = toMap( p("A",1), p("B",2) );
        Map<Integer,Boolean> itb = toMap( p(1,true), p(42,false) );
    }
}
2

ここにJDK 8ストリームによる私のアイデアがあります:

public static <K, V> Map<K, V> toMap(final Object... entries) {
    // Requirements:
    // entries must be K1, V1, K2, V2, .... ( even length )
    if (entries.length % 2 == 1) {
        throw new IllegalArgumentException("Invalid entries");
    }

    final Map<K, V> map = new HashMap<>((int) (entries.length / 2 * 1.25 + 1));
    IntStream.range(0, entries.length / 2).forEach(i -> map.put((K) entries[i * 2], (V) entries[i * 2 + 1]));
    return map;

    // OR: 
    //    return IntStream.range(0, entries.length / 2).boxed().reduce(new HashMap<K, V>(), (m, i) -> {
    //        m.put((K) entries[i * 2], (V) entries[i * 2 + 1]);
    //        return m;
    //    }, (a, b) -> {
    //        a.putAll(b);
    //        return b;
    //    });
}

サードパーティのライブラリ AbacusUtil を使用してもかまわない場合は、コードを次のように簡略化できます。

public static <K, V> Map<K, V> toMap2(final Object... entries) {
    // Requirements:
    // entries must be K1, V1, K2, V2, .... ( even length )
    if (entries.length % 2 == 1) {
        throw new IllegalArgumentException("Invalid entries");
    }

    return Stream.of(entries).split0(2).toMap(e -> (K) e.get(0), e -> (V) e.get(1));
}

そして、ストリームAPIを特に使用しない場合は、forループが最も効率的な方法だと思います。

public static <K, V> Map<K, V> toMap3(final Object... entries) {
    // Requirements:
    // entries must be K1, V1, K2, V2, .... ( even length )
    if (entries.length % 2 == 1) {
        throw new IllegalArgumentException("Invalid entries");
    }

    final Map<K, V> map = new HashMap<>((int) (entries.length / 2 * 1.25 + 1));

    for (int i = 0, len = entries.length; i < len; i++) {
        map.put((K) entries[i], (V) entries[++i]);
    }

    return map;

    // OR just call the method in AbacusUtil.       
    // return N.asMap(entries);
}
1
user_3380739

マップリテラルなどを使用できます。
これを達成するには、ファクトリーメソッドを使用できます:

// Creates a map from a list of entries
@SafeVarargs
public static <K, V> Map<K, V> mapOf(Map.Entry<K, V>... entries) {
    LinkedHashMap<K, V> map = new LinkedHashMap<>();
    for (Map.Entry<K, V> entry : entries) {
        map.put(entry.getKey(), entry.getValue());
    }
    return map;
}

// Creates a map entry
public static <K, V> Map.Entry<K, V> entry(K key, V value) {
    return new AbstractMap.SimpleEntry<>(key, value);
}

最後に、次のようなことができます。

public static void main(String[] args) {
    Map<String, Integer> map = mapOf(entry("a", 1), entry("b", 2), entry("c", 3));
    System.out.println(map);
}

出力:

{a = 1、b = 2、c = 3}

これがあなたに正しい方法を与えることを願っています。

0
nazar_art
public static <K, V, E> Map<K, V> toMap(Function<E, K> toKey, Function<E, V> toValue, E[][] e){

        final Map<K, V> newMap = new HashMap<>();

        Arrays
                .stream(e, 0, e.length - 1)
                .forEach(s ->
                {
                    if (s[0] != null || s[1] != null)
                        newMap.put(toKey.apply(s[0]), toValue.apply(s[1]));
                }
                );

        return newMap;

}




public static void main(String[] args) {

        Object[][] objects = new Object[10][2];
        objects[0][0] ="Ahmet";
        objects[0][1] =28;
        objects[1][0] ="Mehmet";
        objects[1][1] =18;
        objects[2][0] ="Kemal";
        objects[2][1] =55;

Map<String, Integer> newMap = toMap((Object::toString), (Object v) -> Integer.parseInt(v.toString()), objects);

System.out.println(newMap.get("Ahmet") + " " + newMap.get("Kemal"));


}
0
user9159521