什么是?
。它是否与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
我可以说的全部内容。那究竟是什么呢?
答案 0 :(得分:3)
你有一些选择:
public <T> T accept(RecipientTypeVisitor<T> visitor) {...}
这表明您的方法使用了一些泛型并链接了它的一些组件。
答案 1 :(得分:3)
通配符类型的捕获是编译器使用的类型,表示通配符类型的特定实例的类型,在一个特定位置。
示例:以一个带有两个通配符参数void m(Ex<?> e1, Ex<?> e2)
的方法为例。声明的e1
和e2
类型的编写完全相同,Ex<?>
。但是e1
和e2
可能具有不同且不兼容的运行时类型。
类型检查器必须不考虑相同的类型,即使它们以相同的方式编写。因此,在编译期间,e1
和e2
的类型参数被赋予特定类型,对于它们使用的每个位置都是新类型。这些新类型称为其声明类型的 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
,而x
是Object
的子类型;所以我们可以将返回值分配给Object
。