理解Java中捕获的类型(符号'?')

时间:2015-06-12 07:40:16

标签: java generics

什么是?。它是否与Java编译器的实现细节有关,或者类型是在JLS中定义的。

例如,

public interface RecipientTypeVisitor<ReturnType> {
    public ReturnType visit(RecipientSetType t);
}

public class RecipientSetType extends RecipientType{

    public Integer accept(RecipientTypeVisitor<?> visitor){ //Error:
        return visitor.visit(this);             //Cannot convert capture of #1 to Integer
    }
}

但如果我们这样写:

public interface RecipientTypeVisitor<ReturnType> {
    public ReturnType visit(RecipientSetType t);
}

public class RecipientSetType extends RecipientType{

    public Object accept(RecipientTypeVisitor<?> visitor){ //Ok:
        return visitor.visit(this);             //this implies tha the capture of #1 is 
                                     //a subtype of Object as any refrence type in Java.
    }
}

关于captured type我可以说的全部内容。那究竟是什么呢?

5 个答案:

答案 0 :(得分:3)

你有一些选择:

  1. 你必须更换?在接受方法的签名中通过整数。
  2. 你也可以接受你的方法接受:
  3. public <T> T accept(RecipientTypeVisitor<T> visitor) {...}

    这表明您的方法使用了一些泛型并链接了它的一些组件。

答案 1 :(得分:3)

通配符类型的捕获是编译器使用的类型,表示通配符类型的特定实例的类型,在一个特定位置。

示例:以一个带有两个通配符参数void m(Ex<?> e1, Ex<?> e2)的方法为例。声明的e1e2类型的编写完全相同,Ex<?>。但是e1e2可能具有不同且不兼容的运行时类型。

类型检查器必须不考虑相同的类型,即使它们以相同的方式编写。因此,在编译期间,e1e2的类型参数被赋予特定类型,对于它们使用的每个位置都是新类型。这些新类型称为其声明类型的 capture

捕获通配符是一种未知的,但是正常和具体的类型。它可以像其他类型一样使用。

可以在JLS

中找到对此的技术说明
  

5.1.10。捕获转换

     

让G命名一个泛型类型声明(§8.1.2,§9.1.2),其中n类型参数为A1,...,An,对应的边界为U1,...,Un。

     

存在从参数化类型G(§4.5)到参数化类型G的捕获转换,其中,对于1≤i≤n:

     
      
  • 如果Ti是形式为?的通配符类型参数(第4.5.1节),则Si是一个新类型变量,其上限为Ui [A1:= S1,...,An:= Sn]并且其下限是null类型(§4.1)。
  •   
  • ...
  •   

我们可以将此应用于您的示例:

public Integer accept(RecipientTypeVisitor<?> visitor){ //Error:
    return visitor.visit(this);             //Cannot convert capture of #1 to Integer
}

在编译期间引入了RecipientTypeVisitor的类型参数的捕获。捕获的类型参数是具体但完全未知的(JLS称之为“新类型变量”),当然不能转换为Integer

无法直接表示通配符类型的捕获,因此您无法声明该类型的变量或使用它做很多事情。但是,您可以通过调用泛型方法作为参数来间接获取它的名称。我引用的JLS部分有一个很好的例子:

public static void reverse(List<?> list) { rev(list); }
private static <T> void rev(List<T> list) {
    List<T> tmp = new ArrayList<T>(list);
    for (int i = 0; i < list.size(); i++) {
        list.set(i, tmp.get(list.size() - i - 1));
    }
}

答案 2 :(得分:1)

每个类都从Object扩展,因此从?Object的类型转换是安全的。 为了与Integer相同,您可以使用RecipientTypeVisitor<? extends Integer>,但由于Integer是无用的最终类,因此Integer不能被子类化。它相当于RecipientTypeVisitor<Integer>

你为什么要这个?

您可以查看official tutorial here

最重要的是要知道:

  

在通用代码中,问号(?),称为通配符,   代表一种未知的类型。

     

(...)

     

在某些情况下,编译器会推断出通配符的类型。对于   例如,列表可以定义为List,但在评估时   表达式,编译器从代码中推断出特定类型。这个   场景称为通配符捕获。

     

在大多数情况下,您无需担心通配符捕获,   除非您看到包含短语“capture”的错误消息   的”。

答案 3 :(得分:1)

字符?是一个通配符,意味着您不知道该类型。根据定义Object,它的类型不是undefined,但在Java中,您可以在Object类型中保存任何类型的对象。

ArrayList<?> list1 = new Arraylist<?>(); // list of undefined objects

您无法在list1中添加对象,但在投射后可以。

ArrayList<Object> list2 = new Arraylist<Object>(); // list of objects of type Object

您可以在任何类型的list2中添加对象

答案 4 :(得分:1)

(这是我的另一个答案,但它更适合这个问题,解释通配符本身)

通配符? 不是类型。它是类型参数。但语法非常具有欺骗性(按设计)。

让我们使用不同的语法 - 如果有任何第一级通配符,请使用{}代替<>,例如

List{?},  Map{String, ? extends Number}

{?}的含义是声明一个联合类型

List{? extends Number}  ==  union of List<Number>, List<Integer>, List<Long>, ....

很容易看出,List<Integer>List{? extends Number}的子类型;并且List{? extends Number}List{? extends Object}

的子类型

在我们的语法中,<>保留用于替换带有类型的类型变量。所以我们写了 List<String>等等。很容易理解它们的含义 - 只需在T的源代码中用String替换List,我们就会得到一个古老的普通类。

    interface List<String>
        String get(int)

这不能用于通配符 - 这没有任何意义

    interface List<?>
        ? get(int)

因此不允许new ArrayList{?}()class MyList implements List{?}

那么,我们如何使用List{?}?我们可以采用哪些方法?

表达式的类型List{?}时,我们知道它是一个对象,并且该对象必须属于某个未知的List<x>的子类< strong>类型 x。这是通配符捕获

obj is a List{?}  =>  obj is a List<x>, where x a subtype of Object.

即使在编译时未知x的确切类型,我们仍然可以进行替换

    interface List<x>
        x get(int)

所以我们可以理解来电obj.get(0);它返回x,而xObject的子类型;所以我们可以将返回值分配给Object