Java 自引用泛型与通配符

4

有没有可能指定未知的泛型类型是自我引用的?

一个失败的尝试:

import java.util.*;

class Generics {
   public enum A { A1, A2 }
   public enum B { B1, B2 }

   public static List<? extends Enum<?>> listFactory(String[] args) {
      if (args.length == 0) {
         return new ArrayList<A>(Arrays.asList(A.A1, A.A2));
      } else {
         return new ArrayList<B>(Arrays.asList(B.B1, B.B2));
      }
   }

   public static void main(String[] args) {
      List<? extends Enum<?>> lst = listFactory(args);
      dblList(lst);
      System.out.println(lst);
   }

   public static <EType extends Enum<EType>> void dblList(List<EType> lst) {
      int size = lst.size();
      for (int i = 0; i < size; i++) {
         lst.add(lst.get(i));
      }
   }
}

这会导致编译错误:

Generics.java:17: error: method dblList in class Generics cannot be applied to given types;
      dblList(lst);
      ^
  required: List<EType>
  found: List<CAP#1>
  reason: inferred type does not conform to declared bound(s)
    inferred: CAP#1
    bound(s): Enum<CAP#1>
  where EType is a type-variable:
    EType extends Enum<EType> declared in method <EType>dblList(List<EType>)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Enum<?> from capture of ? extends Enum<?>
1 error

理想情况下,listFactory() 的返回类型应该表示列表包含自引用泛型类型(其确切类型未知)。
这个是否可能?如果是,listFactory()lst 的类型应该是什么?

你尝试过将 List<? super EType> 作为 dblist 参数类型吗? - Danail Alexiev
@DanailAlexiev 那样做是行不通的。 - Jean-François Savard
1
这是一个非常棘手的使用案例。请参考https://dev59.com/TGUp5IYBdhLWcg3wTGTe - ZhongYu
我的建议是使用原始类型,抑制警告 - dblList((List)lst) - ZhongYu
3
我很怀疑API是否真的需要如此严格的限制。例如,EnumSet.allOf可以有一个更宽松的签名 <E extends Enum<?>,它应该可以正常工作。 - ZhongYu
2个回答

1
《Effective Java》第28条建议不要在返回类型中使用通配符:

不要将通配符类型用作返回类型。 这样做不会为用户提供额外的灵活性,反而会强制他们在客户端代码中使用通配符类型。

正确使用通配符类型时,它们对类的用户几乎是不可见的。它们使方法接受应该接受的参数并拒绝应该拒绝的参数。 如果类的用户必须考虑通配符类型,则该类的API可能存在问题。

这是EJ所描述的问题的一个很好的例子。 listFactory()实际上只返回一个List<Enum<?>>,但通过声明通配符返回类型,您必须跳过许多障碍才能完成看似简单的任务。

如果您改为给listFactory()添加如下签名:

public static List<Enum<?>> listFactory(String[] args)

你也可以清理一下dblList()的签名:

public static <E> void dblList(List<E> lst)

0
在Java中,类型参数可以是具体类型、通配符类型或类型变量。具体类型对于您的使用情况不够灵活,通配符不能被限制为自引用(因为每个通配符的出现都可以代表不同的类型)。
这就留下了类型变量,它们可以被限制为自引用,但是由构造函数或方法的调用者提供,而不是被调用者,因此我们不能只做以下操作:
<E extends Enum<E>> List<E> listFactory(String[] args);

因为有人可能会使用不正确的类型参数调用此函数。

解决这个问题的一种方法是装饰返回类型:

interface EnumList<E extends Enum<E>> extends List<E> {

}

EnumList<?> listFactory(String[] args);

调用者可以这样做:

EnumList<?> x = listFactory(args);
dblList(x);

在 dblList 中使用通配符捕获来操作列表:

<E extends Enum<E>> void dblList(List<E> list);

值得注意的是,这使得方法签名写起来更加困难,因此只有在需要知道类型是自我引用的情况下才应该这样做。我之所以提到这一点,是因为你的dblList方法并不需要,可以简单地写成:
<E> void dblList(List<E> list);

instead.


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接