読者です 読者をやめる 読者になる 読者になる

きくらげ観察日記

好きなことを、適当に。

JavaでMapの初期化を行う方法いろいろ

Javaの面倒臭さの一つに、Mapリテラルが存在しないというものがあります。普通にMapを使おうとすると、

import java.util.*;

class MapLiteralExample {
    public static void main(String[] args) {
        Map<String, Integer> hogeMap = new HashMap<>();
        hogeMap.put("hoge", 3);
        hogeMap.put("fuga", 2);
        hogeMap.put("piyo", 5);
        for (Map.Entry<String, Integer> e : hogeMap.entrySet()) {
            System.out.printf("%s: %d\n", e.getKey(), e.getValue());
        }
    }
}

という感じになってしまい、キーの数が増えると非常に見にくい。
これを少しでも改善するために、色々な所で色々な人がtips的なものを書いています。

インスタンス初期化子でputする

割とよくみる方法です。Mapの無名サブクラスを作り、インスタンス初期化子でputをする方法です。

import java.util.*;

class MapLiteralExample {
    public static void main(String[] args) {
        Map<String, Integer> hogeMap = new HashMap<String, Integer>() {{
                put("hoge", 3);
                put("fuga", 2);
                put("piyo", 5);
            }};
        for (Map.Entry<String, Integer> e : hogeMap.entrySet()) {
            System.out.printf("%s: %d\n", e.getKey(), e.getValue());
        }
    }
}

何度もhogeMapと書かなくてよくなったため、少しだけ楽になりました。
しかし、プログラマーはどうも「Mapリテラルっぽさ」が無いと不安に感じてしまうようです。この方法では満足しない人も一定数存在するでしょう。

putを改名する

先ほどのバージョンの進化版です。

import java.util.*;

class MyMap<K, V> extends HashMap<K, V> {
    public void _(K key, V val) {
        put(key, val);
    }
}

class MapLiteralExample {
    public static void main(String[] args) {
        Map<String, Integer> hogeMap = new MyMap<String, Integer>() {{
                _("hoge", 3); _("fuga", 2); _("piyo", 5);
            }};
        for (Map.Entry<String, Integer> e : hogeMap.entrySet()) {
            System.out.printf("%s: %d\n", e.getKey(), e.getValue());
        }
    }
}

putを目立たない名前に改名することによって、少しだけMapリテラルっぽさが上昇しました。putの別名は_にするのが一般的なようです。
ちなみに、アンダースコア1つのみからなる名前は今後のJavaでは使用できなくなる可能性が高いので、使わないようにしましょう。

メソッドチェーンできるputを追加する

こちらもput改造系のtipsです。putを_に改名した上で、最後にthisを返すことによって、少しだけMapリテラルっぽく書くことができるようになります。
Javaではないですが、C++でこのようなことをやっているコードを見たことがあります。

import java.util.*;

class MyMap2<K, V> extends HashMap<K, V> {
    public MyMap2<K, V> _(K key, V val) {
        put(key, val);
        return this;
    }
}

class MapLiteralExample {
    public static void main(String[] args) {
        Map<String, Integer> hogeMap = new MyMap2<String, Integer>()
            ._("hoge", 3)
            ._("fuga", 2)
            ._("piyo", 5);
        for (Map.Entry<String, Integer> e : hogeMap.entrySet()) {
            System.out.printf("%s: %d\n", e.getKey(), e.getValue());
        }
    }
}

Builder的なものを作る

先ほどの2つの方法の弱点は、単一のMapのサブクラスにしか利用できないことです。上の実装はすべてHashMapで行いましたが、TreeMapでも同じことをやりたい場合、MyTreeMapのようなクラスを作らなければならなくなります。
こちらの方法はその欠点を解消しています。

import java.util.*;
import java.util.function.*;

class Pair<A, B> {
    private A first;
    private B second;
    public Pair(A a, B b) {
        first = a;
        second = b;
    }
    public A getFirst() {
        return first;
    }
    public B getSecond() {
        return second;
    }
}

class MapBuilder<K, V> {
    private List<Pair<K, V>> entries;
    public MapBuilder() {
        entries = new LinkedList<Pair<K, V>>();
    }
    public MapBuilder<K, V> _(K key, V value) {
        entries.add(new Pair<>(key, value));
        return this;
    }
    public Map<K, V> build(Supplier<Map<K, V>> new_) {
        Map<K, V> m = new_.get();
        for (Pair<K, V> e : entries) {
            m.put(e.getFirst(), e.getSecond());
        }
        return m;
    }
}
class MapLiteralExample {
    public static void main(String[] args) {
        Map<String, Integer> hogeMap = new MapBuilder<String, Integer>()
            ._("hoge", 3)
            ._("fuga", 2)
            ._("piyo", 5)
            .build(HashMap<String, Integer>::new);
        for (Map.Entry<String, Integer> e : hogeMap.entrySet()) {
            System.out.printf("%s: %d\n", e.getKey(), e.getValue());
        }
    }
}

インターフェースがコンストラクタを持てないことを解決するための苦肉の策です。
すこしコードは長くなってしまいましたが、これで任意のMapに対応することができるようになりました。
欠点は、一度キーと値のリストを持ってしまっているので、無駄にコストがかかるということでしょうか。

ちなみに

github.com
リフレクションによりラムダ式の名前を参照することによって、擬似的に

key1 -> value1, key2 -> value2, ...

形式のハッシュテーブルを再現することもできます。
まあ、あくまで曲芸的なものでしかありませんが……。