构造函数引用 - 创建泛型数组时没有警告

时间:2017-02-20 14:14:31

标签: java generics java-8 language-lawyer

在Java中,不可能直接创建泛型类型数组:

<tr v-for="icon in icons">
    <td><i :class="'fa fa-' + icon.css"></i></td>
    <td>{{icon.name}}</td>
</tr>

但是,我们可以使用原始类型执行此操作:

Test<String>[] t1 = new Test<String>[10]; // Compile-time error

在Java 8中,也可以使用构造函数引用:

Test<String>[] t2 = new Test[10]; // Compile warning "unchecked"

为什么编译器在最后一种情况下不显示警告?它仍然使用原始类型来创建数组,对吗?

3 个答案:

答案 0 :(得分: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

在这个地方,必须强调的是,与此处的一些答案相反,编译器对表达式List[]::new执行类型推断以推导出通用元素类型{{1} }。很容易证明仍然禁止通用数组创建:

List<String>

由于IntFunction<List<String>[]> af = List<String>[]::new; // does not compile 是非法的,如果在没有警告的情况下接受List<String>[]::new,通过推断它实际上是非法的List[]::new会很奇怪。

JLS §15.13明确指出:

  

如果方法引用表达式的格式为 ArrayType List<String>[]::new ::,则 ArrayType 必须表示可重新生成的类型(第4.7节) ,或发生编译时错误。

这已经暗示new是非法的,因为List<String>[]::new无法恢复,而List<String>是合法的,因为List<?>[]::new是可以恢复的,List<?>是我们认为List[]::new原始类型是合法的,因为原始类型 List是可以恢复的。

然后§15.13.1声明:

  

如果方法引用表达式的格式为 ArrayType List ::,则会考虑使用单一的名义方法。该方法具有类型new的单个参数,返回 ArrayType ,并且没有int子句。如果 n = 1 ,这是唯一可能适用的方法;否则,没有可能适用的方法。

换句话说,上面throws表达式的行为与您编写的行为相同:

List[]::new

除了方法 IntFunction<List<String>[]> af = MyClass::create; … private static List[] create(int i) { return new List[i]; } 只是名义上的。实际上,使用此显式方法声明,create方法只有原始类型警告,但没有关于{{1}转换的未经检查警告}到方法参考的create。所以这是可以理解的,在List[]情况下编译器会发生什么,其中使用原始类型的方法只是名义上的,即在源代码中不存在。

但是,未经检查警告的缺失明显违反了JLS §5.1.9, Unchecked Conversion

  

List<String>[]使用 n 类型参数命名泛型类型声明。

     

从原始类或接口类型(§4.8)List[]::new中有未经检查的转换G形式的任何参数化类型。

     

从原始数组类型GG<T₁,...,Tₙ>形式的任何数组类型都有未经检查的转换。 (符号G[]ᵏ表示 k 维度的数组类型。)

     

除非所有类型参数G<T₁,...,Tₙ>[]ᵏᵢ(1≤ i n )是无界通配符(§4.5.1),或者[]ᵏ注释(第9.6.4.5节)禁止未经检查的警告。

因此,将T转换为SuppressWarnings是合法的,因为List[]使用无界通配符参数化,但从List<?>[]转换为List必须生成未经检查的警告,这在此至关重要,因为使用List[]不会产生使用显式创建方法显示的原始类型警告。缺少原始类型警告似乎不是违规行为(据我所知§4.8)并且如果List<String>[]创建了必需的<{1}}则不会出现问题em>未选中警告。

答案 1 :(得分:10)

我能想到的最好的是JLS指定对泛型类型的构造函数的方法引用推断出泛型参数: &#34;如果方法或构造函数是通用的,则可以推断或明确提供适当的类型参数。&#34;稍后它以ArrayList::new为例,将其描述为&#34;推断通用类的类型参数,&#34;从而确定ArrayList<>::new(而不是public static class Test<T> { public Test() {} } )是推断参数的语法。

给出一个课程:

Test<String> = new Test(); // No <String>

这会发出警告:

Supplier<Test<String>> = Test::new; // No <String> but no warning

但这并不是:

Test::new

因为<html> <head> <title>Page name @</title> </head> </html> 隐含地推断了泛型参数。

所以我假设对数组构造函数的方法引用的工作方式相同。

答案 2 :(得分:5)

  

它仍然使用原始类型来创建数组,对吗?

Java泛型只是一种编译错觉,所以原始类型当然会在运行时用来创建数组。

  

为什么编译器在最后一种情况下不显示警告?

是的,从Test[]Test<String>[]的未经检查的演员表仍在进行中;它只是在一个匿名的背景下发生在幕后。

Test<String>[] t3 = ((IntFunction<Test<String>[]>) Test[]::new).apply(10);

由于anonymous method正在执行脏工作,因此未经检查的强制转换会从托管代码中消失。