5

共通の親から拡張された複数のクラスに一種のインターン ファクトリを実装しようとしています。ロジックの多くは同じですが、ルックアップは静的である必要があるため、実際には継承できません。望ましい構文は次のようなものです。

Car c = AbstractClass.valueOf(Car.class, "Ford");

Car には車に関連する特定のメソッドがありますが、インスタンスは共通のキャッシュに保存されます。これが私がこれまでに持っているものです。私のコンパイルエラーは、コンストラクターのプットにあります:

「型 Map のメソッド put(String, capture#3-of ? extends AbstractClass) は、引数 (String, AbstractClass) には適用できません」

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

public abstract class AbstractClass {

    private static Map<Class<? extends AbstractClass>, LinkedHashMap<String, ? extends AbstractClass>> map = new HashMap<Class<? extends AbstractClass>, LinkedHashMap<String, ? extends AbstractClass>>();

    private static synchronized <T extends AbstractClass> Map<String, T> getNameMap(Class<T> clazz) {
        LinkedHashMap<String, T> nameToEnum = (LinkedHashMap<String, T>) map.get(clazz);
        if (nameToEnum == null) {
            nameToEnum = new LinkedHashMap<String, T>();
            map.put(clazz, nameToEnum);
        }

        return nameToEnum;
    }

    public static <T extends AbstractClass> T valueOf(Class<T> clazz, String name) {
        return getNameMap(clazz).get(name);
    }

    public static <T extends AbstractClass> Collection<T> VALUES(Class<T> clazz) {
        return getNameMap(clazz).values();
    }

    public static <T extends AbstractClass> Set<T> SORTED_VALUES(Class<T> clazz) {
        return new TreeSet<T>(getNameMap(clazz).values());
    }

    AbstractClass(String name) {
        AbstractClass.getNameMap(this.getClass()).put(name, this);
    }

}
4

3 に答える 3

3

のjavadocによるとObject.getClass()、返される型はワイルドカードベースのコンパイル時型の式です。コンパイラはそれthisがAbstractClassインスタンスを返すことだけを知っているので、をthis.getClass()返しますClass<? extends AbstractClass>

これはgetNameMap、コンストラクターでの呼び出しが。を返すことを意味しますMap<String, ? extends AbstractClass>。つまり、返されるMapには特定の(ワイルドカードではない)タイプの値がありますが、その正確なタイプはコンパイル時にわかりません。コンパイラは、マップの値がAbstractClassまたはAbstractClassから継承するものである必要があることのみを認識しています。thisしたがって、コンパイル時にAbstractClassのサブタイプがどのサブタイプをthis表すかがわからないため、コンパイラは値として安全に追加できません。

より簡単な例を使用するには:メソッドが返された場合Map<String, ? extends Number>、マップの実際の非ワイルドカードタイプは、などである可能性があるため、コンパイラは整数をマップに追加しても安全かどうかわかりませMap<String, Double>Map<String, Short>

解決策について:マップにジェネリックを使用させて、個々のキーのタイプを対応する値のタイプと一致させる方法はないと思います。内部マップの値に有界型を使用することを忘れ、代わりに動的キャストを使用します。

private static Map<Class<? extends AbstractClass>, Map<String, AbstractClass>> map = new HashMap<>();

private static synchronized Map<Class<? extends AbstractClass>, Map<String, AbstractClass>> getNameMap(Class<T> clazz) {
    // same as before
}

public static <T extends AbstractClass> T valueOf(Class<T> clazz, String name) {
    return clazz.cast(getNameMap(clazz).get(name));
}
于 2012-12-09T15:05:07.193 に答える
1

AbstractClass である何かを格納したいだけの場合は、マップを次のように宣言するだけです。

private static Map<Class<? extends AbstractClass>, LinkedHashMap<String, AbstractClass>> map = 
    new HashMap<Class<? extends AbstractClass>, LinkedHashMap<String, AbstractClass>>();

これにより、AbstractClass またはそのサブクラスの 1 つに対して、AbstractClass またはそのサブクラスの任意のインスタンスを内部マップに格納できます。

于 2012-12-09T14:49:20.320 に答える
0

あなたの問題は基本的にこれに要約できます:

このシグネチャを持つメソッドが与えられた場合:

public static <T> void foo(T x, Class<T> y);

および任意の参照型の変数:

<any reference type> bar;

このメソッドbarに渡すことはできません。bar.getClass()

foo(bar, bar.getClass()); // error

正しいものが常に存在することは証明可能ですがT(つまりT、実際の実行時の型bar)。

.getClass()この問題を引き起こすのは、言語のタイプの特殊なケースによるものです。

これを解決するには、次の 2 つの方法が考えられます。

1) クラス オブジェクトをキャストして、参照と同じ型でパラメーター化します (技術的には正しくありませんが)。

AbstractClass(String name) {
    AbstractClass.getNameMap((Class<AbstractClass>)this.getClass()).put(name, this);
}

2) オブジェクトをクラス メソッドのパラメーターと同じ型にキャストします。クラスの型にワイルドカードがあるため、これにはキャプチャ ヘルパーが必要です。

private static <T> void helper(Class<T> clazz, String name, Object obj) {
    AbstractClass.getNameMap(clazz).put(name, (T)obj);
}
AbstractClass(String name) {
    helper(this.getClass(), name, this);
}

(チェックされていないキャストが必要ない場合は、行うことができますAbstractClass.getNameMap(clazz).put(name, clazz.cast(obj));

于 2012-12-09T20:26:19.787 に答える