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 for Object.getClass(),返回的类型是表达式的基于通配符的编译时类型。由于编译器只知道this返回 AbstractClass 实例,因此this.getClass()返回Class<? extends AbstractClass>.

这意味着您getNameMap在构造函数中的调用将返回一个Map<String, ? extends AbstractClass>. 这意味着,虽然返回的 Map 具有特定(非通配符)类型的值,但该确切类型在编译时是未知的;编译器只知道 Map 的值必须是 AbstractClass 或从 AbstractClass 继承的东西。所以编译器不能安全地添加this一个值,因为在编译时不知道 AbstractClass 的子类型this代表哪个。

举一个更简单的例子:如果返回了一个方法Map<String, ? extends Number>,那么编译器将不知道将 Integer 添加到 Map 是否安全,因为 Map 的实际非通配符类型可能是Map<String, Double>,Map<String, Short>等。

至于解决方案:我认为没有办法让 Map 使用泛型将每个单独的键的类型与其对应的值的类型匹配。我会忘记在内部地图的值上使用有界类型,而是使用动态转换:

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

如果您只想存储任何抽象类,只需将您的地图声明为

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

这将允许您将 AbstractClass 或其子类的任何实例存储在内部映射中,针对 AbstractClass 或其子类之一。

于 2012-12-09T14:49:20.320 回答
0

您的问题基本上可以归结为:

给定具有此签名的方法:

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

和任何引用类型的变量:

<any reference type> bar;

不可能传递barbar.getClass()这个方法:

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

即使可以证明总是存在一些T它是正确的(即T= 的实际运行时类型bar)。

这是由于.getClass()导致此问题的类型的语言中的特殊情况。

我可以想到两种方法来解决这个问题:

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 回答