Vla*_* M. 55 java generics language-lawyer java-8
在Java中,不可能直接创建泛型类型的数组:
Test<String>[] t1 = new Test<String>[10]; // Compile-time error
Run Code Online (Sandbox Code Playgroud)
但是,我们可以使用原始类型执行此操作:
Test<String>[] t2 = new Test[10]; // Compile warning "unchecked"
Run Code Online (Sandbox Code Playgroud)
在Java 8中,也可以使用构造函数引用:
interface ArrayCreator<T> {
T create(int n);
}
ArrayCreator<Test<String>[]> ac = Test[]::new; // No warning
Test<String>[] t3 = ac.create(10);
Run Code Online (Sandbox Code Playgroud)
为什么编译器在最后一种情况下不显示警告?它仍然使用原始类型来创建数组,对吗?
Hol*_*ger 28
你的问题是合理的.简而言之,方法引用确实使用原始类型(或应该使用原始类型)以及为什么禁止创建泛型数组的原因,仍然适用于使用方法引用时,因此,能够以静默方式创建函数创建通用数组明显违反了语言设计的意图.
禁止创建通用数组的原因是源于前Generics时代的数组类型继承与泛型类型系统不兼容.即你可以写:
IntFunction<List<String>[]> af = List[]::new; // should generate warning
List<String>[] array = af.apply(10);
Object[] objArray = array;
objArray[0] = Arrays.asList(42);
List<String> list = array[0]; // heap pollution
Run Code Online (Sandbox Code Playgroud)
在这个地方,必须强调的是,与此处的一些答案相反,编译器不对表达式执行类型推断List[]::new以推导出通用元素类型List<String>.很容易证明仍然禁止通用数组创建:
IntFunction<List<String>[]> af = List<String>[]::new; // does not compile
Run Code Online (Sandbox Code Playgroud)
既然List<String>[]::new是非法的,那么如果List[]::new在没有警告的情况下被接受,通过推断它实际上是非法的,这将是奇怪的List<String>[]::new.
JLS§15.13明确指出:
如果方法引用表达式的格式为ArrayType
::new,则ArrayType必须表示可重新生成的类型(第4.7节),否则会发生编译时错误.
这已经暗示这List<String>[]::new是非法的,因为它List<String>是不可再生的,而且List<?>[]::new是合法的,List<?>可以回收的,并且List[]::new如果我们认为List是原始类型是合法的,因为原始类型 List是可再生的.
然后§15.13.1规定:
如果方法引用表达式具有ArrayType形式
::new,则考虑使用单个名义方法.该方法具有单个参数类型int,返回ArrayType,并且没有throws子句.如果n = 1,这是唯一可能适用的方法; 否则,没有可能适用的方法.
换句话说,List[]::new上面表达式的行为与您编写的相同:
IntFunction<List<String>[]> af = MyClass::create;
…
private static List[] create(int i) {
return new List[i];
}
Run Code Online (Sandbox Code Playgroud)
除了这个方法create只是名义上的.事实上,使用此显式方法声明,方法中只有原始类型警告create,但没有关于在方法引用处转换为to的未经检查的警告.所以这是可以理解的,在这种情况下编译器会发生什么,其中使用原始类型的方法只是名义上的,即在源代码中不存在.List[]List<String>[]List[]::new
但缺少未经检查的警告显然违反了JLS§5.1.9,未经检查的转换:
让我们
G用n类参数命名泛型类型声明.存在未检查的转换从原始类或接口类型(§4.8)
G的任何参数化的类型的形式的G<T?,...,T?>.从原始数组类型
G[]?到表单的任何数组类型都有未经检查的转换G<T?,...,T?>[]?.(符号[]?表示k维的数组类型.)未经检查的转换的使用将导致编译时间未检查的警告,除非所有类型的参数
Tᵢ(1≤ 我 ≤ Ñ)是无界的通配符(§4.5.1),或者未经检查的警告是由抑制SuppressWarnings注释(§9.6.4.5).
因此,List[]to 转换List<?>[]是合法的,因为List使用无界通配符进行参数化,但转换List[]为List<String>[]必须产生未经检查的警告,这在此至关重要,因为使用List[]::new不会产生显式创建时出现的原始类型警告方法.缺少原始类型警告似乎不是违规行为(据我所知§4.8),如果javac创建了必需的未经检查警告,则不会出现问题.
Wil*_*urn 10
我能想到的最好的是JLS指定对泛型类型的构造函数的方法引用推断出泛型参数:"如果方法或构造函数是通用的,则可以推断或明确提供适当的类型参数. " 稍后它给出ArrayList::new了一个例子并将其描述为"泛型类的推断类型参数",从而确定ArrayList::new(而不是ArrayList<>::new)是推断参数的语法.
给出一个类:
public static class Test<T> {
public Test() {}
}
Run Code Online (Sandbox Code Playgroud)
这给出了一个警告:
Test<String> = new Test(); // No <String>
Run Code Online (Sandbox Code Playgroud)
但这不是:
Supplier<Test<String>> = Test::new; // No <String> but no warning
Run Code Online (Sandbox Code Playgroud)
因为Test::new隐式推断了泛型参数.
所以我假设对数组构造函数的方法引用以相同的方式工作.
它仍然使用原始类型来创建数组,对吗?
Java泛型只是一种编译时错觉,因此原始类型当然会在运行时用于创建数组.
为什么编译器在最后一种情况下不显示警告?
是的,从投选中Test[]以Test<String>[]仍在进行之中; 它只是在一个匿名的背景下发生在幕后.
Test<String>[] t3 = ((IntFunction<Test<String>[]>) Test[]::new).apply(10);
Run Code Online (Sandbox Code Playgroud)
由于匿名方法正在执行脏工作,因此未经检查的强制转换会从托管代码中消失.
| 归档时间: |
|
| 查看次数: |
2199 次 |
| 最近记录: |