11

我有一个带有单例通配符类型集合的类,例如:

public ObliviousClass{

    private static final ObliviousClass INSTANCE = new ObliviousClass();

    private Map<Key, Type<?>> map = new HashMap<Key, Type<?>>();

    public void putType(Key key, Type<?> type){
        map.put(type);
    }

    // returns the singleton
    public static ObliviousClass getInstance(){
        return INSTANCE;
    }

}

我希望能够在客户端代码中向此集合添加不同的参数化类型:

void clientMethod(){
    ObliviousClass oc = ObliviousClass.getInstance();

    Type<Integer> intType = ...
    Type<String> stringType = ...

    oc.putType(new Key(0), intType);
    oc.putType(new Key(1), stringType);
}

到目前为止,据我了解,一切都很好。但是客户还需要能够获得Type<?>提供的Key. 因此,将添加类似以下的方法ObliviousClass

public Type<?> getType(Key key){
    return map.get(key);
}

但在我方便的Effective Java副本中,我读到:

不要使用通配符类型作为返回类型。

我理解这个问题,因为客户必须将返回的Type<?>. 但我真的不想制作ObliviousClass一个泛型类型ObliviousClass<T>,因为这样我上面的客户端代码就不起作用了......

我想做的事情有更好的设计吗? -我目前的解决方案是为客户端提供静态方法;类似于:

public static <T> void getType(ObliviousClass instance, Key key, Type<T> dest){
    dest = (Type<T>)instance.getType(key);
}

我四处搜寻,但找不到完全消除我困惑的答案。

4

4 回答 4

6

这是一种在映射中存储给定类型的多个实例的类型安全方法。关键是您需要Class在检索值时提供一个实例才能执行运行时类型检查,因为静态类型信息已被删除。

class ObliviousClass {

  private final Map<Key, Object> map = new HashMap<Key, Object>();

  public Object put(Key key, Object value)
  {
    return map.put(key, value);
  }

  public <T> T get(Key key, Class<? extends T> type)
  {
    return type.cast(map.get(key)); 
  }

}

用法如下所示:

oc.put(k1, 42);
oc.put(k2, "Hello!");
...
Integer i = oc.get(k1, Integer.class);
String s = oc.get(k2, String.class);
Integer x = oc.get(k2, Integer.class); /* Throws ClassCastException */
于 2012-05-08T23:25:53.953 回答
1

只需输入您的班级:

public ObliviousClass <T> {

    private Map<Key, Type<T>> map = new HashMap<Key, Type<T>>();

    public void putType(Key key, Type<T> type){
        map.put(type);
    }

    public Type<T> getType(Key key){
        map.get(key);
    }
}

仅供参考,此时您可以使用委托模式

您的示例客户端代码需要声明ObliviousClass:ObliviousClass<String>ObliviousClass<Integer>.

编辑:

如果你必须有多种类型,你可以在你的方法上强加一个类型,但是你会得到一个编译器警告,因为不安全的强制转换:

public class ObliviousClass {

    private final Map<Key, Type<?>> map = new HashMap<Key, Type<?>>();

    public void putType(Key key, Type<?> value) {
       map.put(key, value);
    }

    @SuppressWarnings("unchecked")
    public <T> Type<T> getType1(Key key, Class<T> typeClass) {
       return (Type<T>)map.get(key); 
    }

    @SuppressWarnings("unchecked")
    public <T> Type<T> getType2(Key key) {
        return (Type<T>) map.get(key);
    }
}

客户可以像这样键入对这些方法的调用:

Type<Integer> x = obliviousClass.getType1(key, Integer.class);
Type<Integer> y = obliviousClass.<Integer>getType2(key);

选择您喜欢哪个并使用它。

于 2012-05-08T23:03:04.580 回答
0

对于那些多年后遇到这个问题的人来说,这不是 Java 泛型的设计方式。(我打算发表评论,但有更多细节。)

通用模式为每个类型 ID 管理一个类,而不是多个不同的类。如果我们考虑更简单的 List<T>,则字符串或整数的列表(如 List<String> 或 List<Integer>)就是泛型的定义方式。每种类型一个类。这样,在引用值时就有了一致的类型。存储不相关的类型与 List<Object> 相同。只有程序员才能知道何时存储了多种类型以及如何通过强制转换来检索它们。

将子类存储到父类是可以的,但是当从集合中访问而不进行强制转换时,父类的联系就是已知的。例如,使用 Map<String, Runnable> 之类的接口定义的通用集合。但是,即使将其他公共方法添加到实现中,也只有 run() 方法是可见的(除非程序员显式强制转换)。要访问其他方法,必须进行强制转换。

这是 Java 中的一个限制。可以定义一种语言来了解 L-Value 类型——甚至是 Java。但事实并非如此。添加新功能时,[Sun 和] Oracle 会考虑许多向后兼容的因素。使用泛型编译的代码旨在在具有类型擦除的旧 JVM 上运行。一旦确定泛型是一致引用的,Java 就会在编译时使用类型擦除。字节码使用对象,就好像实例被(某种)定义为列表一样。如果选择放弃向后兼容性,如 Java 9 和 11,那么多种类型可能是可行的。

于 2019-01-04T17:33:06.803 回答
0

根据设计,您ObliviousClass的 不知道它所持有的项目的参数化类型。所以为了打字安全,你应该避免这样的设计:-\

但如果你想保留它,首先要做的就是你必须施放。这是没有办法的。但是你这样做的方式很容易出错。例如:

oc.put(k1, intType);
oc.put(k2, strType);
Type<Integer> tint = oc.get(k1, Integer.class)
Type<String>  tstr = oc.get(k1, String.class)  // typo in k2: compile fine

最糟糕的是,由于类型擦除,它只会在你实际使用时在运行时失败tstr,而不是当你从ObliviousClass.

因此,您可以通过以其他方式跟踪参数化类型来提高安全性。例如,您可以将键与类型相关联,而不是丢失它:

@Value // lombok
class Key<T> {
    private int index;
}

class Type<T> {}

class ObliviousClass {

    // side note: static final can be public safely
    public static final ObliviousClass instance = new ObliviousClass();

    private List<Type<?>> map = new ArrayList<>();

    public <T> Key<T> appendType(Type<T> type){
        // here, I found it nicer that obliviousClass generates and return the key
        // otherwise use:  "public <T> void appendType(key<T> key, Type<T> type)"
        // that binds parametrized type of both key and type arguments
        map.add(type);
        return new Key<>(map.size() - 1);
    }


    public <T> Type<T> get(Key<T> key){
        return (Type<T>) map.get(key.index);
    }
}

然后你可以使用它,例如:

    Type<Integer> intType = new Type<>();
    Type<String>  strType = new Type<>();
    Key<Integer> k1 = ObliviousClass.instance.appendType(intType);
    Key<String>  k2 = ObliviousClass.instance.appendType(strType);

    Type<Integer> t1 = ObliviousClass.instance.get(k1);
    Type<String>  t2 = ObliviousClass.instance.get(k2);
    Type<String>  t3 = ObliviousClass.instance.get(k1); // won't compile
于 2020-02-19T12:17:38.643 回答