5

複数の異なるタイプの値をサポートしながら、ジェネリックの利点を提供するマップを作成したいと考えています。ジェネリック コレクションの主な利点は次の 2 つだと思います。

  • コレクションに間違ったものを入れる際のコンパイル時の警告
  • コレクションから物を取り出すときにキャストする必要はありません

だから私が欲しいのは地図です:

  • 複数の値オブジェクトをサポートし、
  • マップに入れられた値をチェックします (できればコンパイル時に)
  • マップから取得するときにオブジェクトの値が何であるかを知っています。

ジェネリックを使用した基本ケースは次のとおりです。

Map<MyKey, Object> map = new HashMap<MyKey, Object>();
// No type checking on put();
map.put(MyKey.A, "A");  
map.put(MyKey.B, 10);
// Need to cast from get();
Object a = map.get(MyKey.A); 
String aStr = (String) map.get(MyKey.A);

このキーに関連付けられた値のクラスによって生成される AbstractKey を作成することにより、2 番目の問題を解決する方法を見つけました。

public interface AbstractKey<K> {
}
public enum StringKey implements AbstractKey<String>{
  A,B;  
}
public enum IntegerKey implements AbstractKey<Integer>{
  C,D;
}

その後、TypedMap を作成し、put() メソッドと get() メソッドをオーバーライドできます。

public class TypedMap extends HashMap<AbstractKey, Object> {
  public <K> K put(AbstractKey<K> key, K value) {
    return (K) super.put(key, value);
  }
  public <K> K get(AbstractKey<K> key){
    return (K) super.get(key);
  }
}

これにより、次のことが可能になります。

TypedMap map = new TypedMap();
map.put(StringKey.A, "A");
String a = map.get(StringKey.A);

ただし、キーに間違った値を入力しても、コンパイル エラーは発生しません。ClassCastException代わりに、 get()でランタイムを取得します。

map.put(StringKey.A, 10); // why doesn't this cause a compile error?
String a = map.get(StringKey.A); // throws a ClassCastException

この .put() がコンパイル エラーを出すことができれば理想的です。現在の次善策として、ランタイムClassCastExceptionを put() メソッドでスローすることができます。

// adding this method to the AbstractKey interface:
public Class getValueClass();

// for example, in the StringKey enum implementation:
public Class getValueClass(){
  return String.class;
}

// and override the put() method in TypedMap:
public <K> K put(AbstractKey<K> key, K value){
  Object v = key.getValueClass().cast(value);
  return (K) super.put(key, v);
}

これで、ClassCastExceptionは次のようにマップに配置されたときにスローされます。これは、間違ったキーと値の組み合わせが TypedMap に配置された場所を特定するためのデバッグをより簡単かつ迅速に行うことができるため、望ましい方法です。

map.put(StringKey.A, 10); // now throws a ClassCastException

だから、私は知りたいです:

  • map.put(StringKey.A, 10)コンパイルエラーにならないのはなぜですか?
  • 値がキーのジェネリック型に関連付けられていない場合に、プットで意味のあるコンパイル エラーが発生するように、この設計をどのように適応させることができますか?

  • これは、私が望むものを実現するのに適した設計ですか (トップを参照)? (その他の考え/コメント/警告もいただければ幸いです...)

  • 私が望むものを達成するために使用できる代替デザインはありますか?

編集 - 説明:

  • これが悪い設計だと思われる場合、その理由を説明していただけますか?
  • 値の型の例として String と Integer を使用しました。実際には、使用できるようにしたいキーと値の型のペアが多数あります。これらを 1 つのマップで使用したい - それが目的です。
4

4 に答える 4

3

項目 29: タイプセーフな異種コンテナーを検討する。—<a href="http://my.safaribooksonline.com/9780137150021?portal=informit" rel="nofollow">Joshua Bloch、Effective Java、第 2 版、第 5 章: ジェネリック

于 2012-05-03T11:14:19.087 に答える
3

あなたはジェネリックをいじり、悪い方法でオーバーロードしています。拡張HashMap<AbstractKey, Object>しているため、クラスはメソッドを継承していますObject put(AbstractKey k, Object v)。クラスputで、別のシグネチャを持つ別のメソッドを定義しています。つまり、putメソッドをオーバーライドするのではなく、メソッドをオーバーロードしているだけです。

記述するmap.put(StringKey.A, 10)と、コンパイラは引数の型に適合するメソッドを見つけようとしますput(StringKey, Integer)。あなたのメソッドの署名は適用されませんが、継承されたものは適用されますput--StringKeyと互換性がAbstractKeyあり、Integerと互換性がありObjectます。そのため、そのコードを への呼び出しとしてコンパイルしますHashMap.put

これを修正する方法:putのようなカスタム名に名前を変更しtypedPutます。

経験から言えば、あなたのアプローチはとても楽しくて魅力的ですが、実際には苦労する価値はありません。

于 2012-05-03T10:16:23.683 に答える
1

私見ですが、すべての問題は元のデザインの匂いから来ています。さまざまなタイプの値をマップに入れたいということです。Value代わりに、整数値と文字列値を共通の型にラップします。このようなもの:

public class Value {
    private enum Type {
        STRING, INTEGER;
    }

    private Type type;
    private Object value;

    private Value(Object value, Type type) {
        this.value = value;
        this.type = type;
    }

    public static Value fromString(String s) {
        return new Value(s, Type.STRING);
    }

    public static Value fromInteger(Integer i) {
        return new Value(i, Type.INTEGER);
    }

    public Type getType() {
        return this.type;
    }

    public String getStringValue() {
        return (String) value;
    }

    public Integer getIntegerValue() {
        return (Integer) value;
    }

    // equals, hashCode
}

このように、必要なのは、だけMap<SomeKey, Value>で、マップから値を安全に取得できます。

Value v = map.get(someKey);
if (v.getType() == Type.STRING) {
    String s = v.getStringValue();
}
else if (v.getType() == Type.INTEGER) {
    Integer i = v.getIntegerValue();
}
于 2012-05-03T10:24:58.730 に答える
0

これを考慮してください:(Effective Javaのおかげで)

public class TypedMap {

    private Map<AbstractKey<?>, Object> map = new HashMap<AbstractKey<?>, Object>();

    public <T> T get(AbstractKey<T> key) {
        return key.getType().cast(map.get(key));
    }

    public <T> T put(AbstractKey<T> key, T value) {
        return key.getType().cast(map.put(key, key.getType().cast(value)));
    }

    public static interface AbstractKey<K> {

        Class<K> getType();
    }

    public static enum StringKey implements AbstractKey<String> {

        A, B;

        public Class<String> getType() {
            return String.class;
        }
    }

    public static enum IntegerKey implements AbstractKey<Integer> {

        C, D;

        public Class<Integer> getType() {
            return Integer.class;
        }
    }
  }

これはコンパイル時エラーを生成します

TypedMap map = new TypedMap();
TypedMap.AbstractKey<Integer> intKey = TypedMap.IntegerKey.C;
TypedMap.AbstractKey<String> strKey = TypedMap.StringKey.A;
map.put(strKey, "A");
map.put(intKey, 10);
map.put(strKey, 10); // this cause a compile error?
于 2012-05-03T13:06:39.903 に答える