web-dev-qa-db-ja.com

複数の値タイプを持つマップで未チェックの割り当てを避けますか?

Java 7の警告で問題が発生しています:

_Unchecked assignment: 'Java.lang.Class' to 'Java.lang.Class<T>'
_

以下のget関数のClass<T> type = typeMap.get(key);行で取得しています。

基本的にここでやろうとしているのは、不明なタイプのキー/値ペアのセットを格納することです(ただし、すべてはnullを除くObjectの子孫です)が、タイプは失われません。そこで、ジェネリックを使用して次のコンテンツを持つクラスを作成しました。 2つのマップがあります(1つはデータを保存し、もう1つはクラスタイプを保存します:

_    private Map<String, Object>  dataMap = new HashMap<>();
    private Map<String, Class> typeMap = new HashMap<>();

    public  <T> void put(String key, T instance){
        dataMap.put(key, instance);
        if (instance == null){
            typeMap.put(key,null);
        }
        else {
            typeMap.put(key, instance.getClass());
        }
    }

    public <T> T get(String key){
        Class<T> type = typeMap.get(key);
        if (type == null){
            return null;
        }
        return type.cast(dataMap.get(key));
    }
_

それはうまく動作しますが、警告は私を悩ます。文句を言わずにこのキャストを実行するためのJavaを取得する方法はありますか(それを抑制する以外に)?または、私がやろうとしていることを達成するためのより良い方法はありますか? Java 8では、実際にダイブする機会がまだないので、どうでしょうか。

ありがとう!

28
irotsoma

あなたが示したことは安全ではない理由は、この割り当ての場合です:

_Class<T> type = typeMap.get(key);
_

Tは、マップから取得したClassとは関係ありません。 Tは常に、getへの呼び出しの周囲のコンテキストから推測されます。たとえば、次の一連の呼び出しを実行できます。

_// T is inferred from the arguments as String (which is fine)
example.put("k", "v");
// T is inferred from the return value target type as Integer
Integer i = example.get("k");
_

getメソッド内で、_String.class_は型マップから正しく取得されますが、チェックされていない変換は_Class<Integer>_に行われます。データマップから取得した値はStringであるため、type.cast(...)の呼び出しはスローされません。暗黙のチェックされたキャスト実際に戻り値に発生し、キャストしますIntegerClassCastExceptionがスローされます。

この奇妙な相互作用は、 type erasure によるものです。


そのため、単一のデータ構造に複数のタイプを格納する場合、ニーズに応じてさまざまな方法でアプローチできます。

1.コンパイルチェックを実行する方法がない場合は、コンパイルチェックを省略できます。

Classの保存は、上で示したように、有用な検証を実行しないため、ここではほとんど意味がありません。したがって、次の行に沿ってマップを再設計できます。

_class Example {
    private final Map<String, Object> m = new HashMap<>();

    void put(String k, Object v) {
        m.put(k, v);
    }

    Object getExplicit(String k) {
        return m.get(k);
    }

    @SuppressWarnings("unchecked")
    <T> T getImplicit(String k) {
        return (T) m.get(k);
    }
}
_

getExplicitgetImplicitは同様のことを行いますが、次のことを行います。

_String a = (String) example.getExplicit("k");
// the generic version allows an implicit cast to be made
// (this is essentially what you're already doing)
String b = example.getImplicit("k");
_

どちらの場合も、私たちは間違いを犯さないように、プログラマーとしての私たち自身の認識に頼っているだけです。

警告を抑制することは必ずしも悪いことではありません。警告が実際に何を意味し、どのような意味があるのか​​を理解することが重要です。

2. Classgetに渡すと、戻り値が有効でなければなりません。

これは私が典型的に見た方法です。

_class Example {
    private final Map<String, Object> m = new HashMap<>();

    void put(String k, Object v) {
        m.put(k, v);
    }

    <T> T get(String k, Class<T> c) {
        Object v = m.get(k);
        return c.isInstance(v) ? c.cast(v) : null;
    }
}

example.put("k", "v");
// returns "v"
String s = example.get("k", String.class);
// returns null
Double d = example.get("k", Double.class);
_

しかし、もちろん、それはgetに2つのパラメーターを渡す必要があることを意味します。

3.キーをパラメーター化します。

これは小説ですが、より高度であり、より便利な場合とそうでない場合があります。

_class Example {
    private final Map<Key<?>, Object> m = new HashMap<>();

    <V> Key<V> put(String s, V v) {
        Key<V> k = new Key<>(s, v);
        put(k, v);
        return k;
    }

    <V> void put(Key<V> k, V v) {
        m.put(k, v);
    }

    <V> V get(Key<V> k) {
        Object v = m.get(k);
        return k.c.isInstance(v) ? k.c.cast(v) : null;
    }

    static final class Key<V> {
        private final String k;
        private final Class<? extends V> c;

        @SuppressWarnings("unchecked")
        Key(String k, V v) {
            // this cast will always be safe unless
            // the outside world is doing something fishy
            // like using raw types
            this(k, (Class<? extends V>) v.getClass());
        }

        Key(String k, Class<? extends V> c) {
            this.k = k;
            this.c = c;
        }

        @Override
        public int hashCode() {
            return k.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            return (o instanceof Key<?>) && ((Key<?>) o).k.equals(k);
        }
    }
}
_

例えば:

_Key<Float> k = example.put("k", 1.0f);
// returns 1.0f
Float f = example.get(k);
// returns null
Double d = example.get(new Key<>("k", Double.class));
_

エントリが既知または予測可能な場合、これは理にかなっている可能性があります。

_final class Keys {
    private Keys() {}
    static final Key<Foo> FOO = new Key<>("foo", Foo.class);
    static final Key<Bar> BAR = new Key<>("bar", Bar.class);
}
_

そうすれば、取得が行われるたびにキーオブジェクトを構築する必要がなくなります。これは、特に文字列型のシナリオにいくつかの強力な型付けを追加する場合に非常にうまく機能します。

_Foo foo = example.get(Keys.FOO);
_

4.あらゆる種類のオブジェクトを配置できるマップを持っていない、何らかの種類の多態性を使用する。

可能であれば、面倒ではありませんが、それは良い選択肢です。異なるタイプが使用される一般的な動作がある場合は、それをインターフェースまたはスーパークラスにして、キャストを使用する必要がないようにします。

簡単な例は次のようになります。

_// bunch of stuff
Map<String, Object> map = ...;

// store some data
map.put("abc", 123L);
map.put("def", 456D);

// wait awhile
awhile();

// some time later, consume the data
// being particular about types
consumeLong((Long) map.remove("abc"));
consumeDouble((Double) map.remove("def"));
_

そして、代わりに次のようなものに置き換えることができます。

_Map<String, Runnable> map = ...;

// store operations as well as data
// while we know what the types are
map.put("abc", () -> consumeLong(123L));
map.put("def", () -> consumeDouble(456D));

awhile();

// consume, but no longer particular about types
map.remove("abc").run();
map.remove("def").run();
_
34
Radiodef

タイプClassの要素をタイプClass<T>の変数に割り当てようとしています。もちろん、これは未チェックの割り当てです。異種マップを実装しているようです。 Java(およびその他の厳密に型指定された言語)には、静的に型保証された方法でマップの値型を表現する方法がありません。

これは、要素タイプが実行時にのみ認識されるためです。コンパイラに、まだ知られていないものを静的に型チェックすることを期待することは、あまりにも多くを求めています。コンパイラーは、妥当な静的型推論を行うことすらできないため、動的型推論の将来を予測することを期待するのは、本当にストレッチです。

4
Judge Mental