【问题标题】:Avoiding Returning Wildcard Types避免返回通配符类型
【发布时间】:2012-05-08 22:28:17
【问题描述】:

我有一个包含一组单例通配符类型的类,例如:

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&lt;?&gt;,前提是Key。所以类似下面的方法会被添加到ObliviousClass:

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

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

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

我理解这个问题,因为客户端必须转换返回的Type&lt;?&gt;。但我真的不想让ObliviousClass 成为泛型类型,ObliviousClass&lt;T&gt;,因为那样我上面的客户端代码将无法工作......

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

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

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

【问题讨论】:

  • 你有没有考虑像ObliviousClass&lt;T&gt;那样为你的类添加一个泛型类型?
  • 这样做会阻止我将 Type 和 Type 添加到集合中。类 Type 有一个泛型类型,允许客户在使用它时有一些必要的灵活性,但 ObliviousClass 只负责维护对每个类的引用,而不直接对它们做任何事情。因此,将它们保持为通配符类型对我来说似乎很合适,但检索它们是我的问题。

标签: java generics wildcard generic-collections


【解决方案1】:

这是一种在映射中存储给定类型的多个实例的类型安全方法。关键是您需要在检索值时提供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 */

【讨论】:

  • 感谢您的回答。与我上面的静态方法相比,这有什么优势?另外,我想存储参数化类型,所以客户端代码不必看起来像:oc.get(k1, Type&lt;Integer&gt;.class);。这还有效吗?
  • 它不适用于参数化类型,因为(由于类型参数的擦除)您无法获得所需的 Class 实例。如果你扩展Type&lt;T&gt;,在你的扩展中提供类型参数(例如class IntegerType extends Type&lt;Integer&gt; {...}),那么类型参数(Integer,在这个例子中,不会被删除,你可以通过反射来执行转换. 这称为“超类型令牌”。但是,这种方法存在一些问题;请参阅Neal Gafter's blog
【解决方案2】:

只需键入您的课程:

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);
    }
}

仅供参考,此时您可以使用delegation pattern

您的示例客户端代码需要声明ObliviousClass两个 实例:ObliviousClass&lt;String&gt;ObliviousClass&lt;Integer&gt;

编辑:

如果您必须有多种类型,您可以在方法上强加一个类型,但您会收到编译器警告,提示您进行不安全的强制转换:

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);

选择您喜欢的并使用它。

【讨论】:

  • 感谢您的回答,但这是我试图避免的。我想要一个单独的类 ObliviousClass,它甚至可能是一个单例,来保存 Type&lt;?&gt; 的集合,而不关心所保存的实际参数化类型是什么。
  • 你能帮我理解混合类型的问题吗?这似乎完全合理,因为 ObliviousClass 并不关心实际的参数化类型是什么,并且只负责在客户要求时将它们提供给客户。如果我遗漏了一些基本的东西,请告诉我。
  • 如果客户关心类型,那么 ObliviousClass 也应该如此 - 它存在供客户使用并满足他们的需求。
  • 另外,请查看最新版本的代码以了解其他选项以及如何调用它们
【解决方案3】:

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

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

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

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

【讨论】:

    【解决方案4】:

    您的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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-03-29
      • 2017-03-07
      • 2021-01-29
      • 1970-01-01
      • 1970-01-01
      • 2016-04-11
      • 1970-01-01
      相关资源
      最近更新 更多