构造函数的参考 – 创buildgenerics数组时没有警告

在Java中,不可能直接创build一个genericstypes的数组:

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

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

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

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

 interface ArrayCreator<T> { T create(int n); } ArrayCreator<Test<String>[]> ac = Test[]::new; // No warning Test<String>[] t3 = ac.create(10); 

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

你的问题是有道理的。 简而言之,方法引用确实使用原始types(或者应该使用原始types),原因是为什么通用数组的创build被禁止,在使用方法引用时仍然适用,因此,可以静默地创build函数创build通用数组显然违反了语言devise的意图。

为什么禁止创buildgenerics数组的原因是源于前generics时代的数组typesinheritance与genericstypes系统不兼容。 即你可以写:

 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 

在这个地方,必须强调的是,与这里的一些答案相反,编译器不对expression式List[]::new执行types推断以推导出通用元素typesList<String> 。 很容易certificategenerics数组的创build仍然被禁止:

 IntFunction<List<String>[]> af = List<String>[]::new; // does not compile 

由于List<String>[]::new是非法的,如果List[]::new被接受而没有警告,通过推断它是非法的List<String>[]::new是很奇怪的。

JLS第15.13条明确规定:

如果一个方法引用expression式的forms为ArrayType :: new ,则ArrayType必须表示一个可被赋值的types(§4.7),否则会发生编译时错误。

这已经意味着List<String>[]::new是非法的,因为List<String>是不可赋值的,而List<?>[]::new是合法的,因为List<?>是可赋值的, List[]::new如果我们认为List是一个原始types ,那么List[]::new是合法的,因为原始types List是可validation的。

然后§15.13.1指出:

如果方法引用expression式具有ArrayType :: newforms,则考虑单个概念方法。 该方法具有inttypes的单个参数,返回ArrayType ,并且没有throws子句。 如果n = 1 ,这是唯一可能适用的方法; 否则,没有可能适用的方法。

换句话说,上面的List[]::newexpression式的行为与您写下的行为相同:

  IntFunction<List<String>[]> af = MyClass::create; … private static List[] create(int i) { return new List[i]; } 

除了create的方法只是名义上的。 实际上,通过这个明确的方法声明,在create方法中只有原始types警告,但是在方法引用处没有关于将List[]转换为List<String>[] 未经检查的警告。 所以这是可以理解的,在List[]::new情况下,使用原始types的方法只是名义上的,即在源代码中不存在。

但是没有未经检查的警告显然违反了JLS§5.1.9,未经检查的转换

Gn个参数来命名genericstypes声明。

从原始类或接口types(§4.8) G到格式为G<T₁,...,Tₙ>任何参数化types有一个未经检查的转换

从原始数组typesG[]ᵏ到格式为G<T₁,...,Tₙ>[]ᵏ any的任何数组types都有一个未经检查的转换。 (符号[]ᵏ表示k维的数组types。)

使用未经检查的转换会导致编译时未经检查的警告,除非所有types自variablesT (1≤i≤n)都是无界通配符(第4.5.1节),或者未经检查的警告被SuppressWarnings注释(第9.6.4.5节)。

因此, List[]List<?>[]是合法的,因为List是使用无界通配符进行参数化的,但是从List[]List<String>[]必须产生未经检查的警告,这是至关重要的在这里,由于使用List[]::new不会产生显式的创build方法显示的原始types警告。 没有原始types警告似乎不是违反(据我了解§4.8 ),这不会是一个问题,如果javac创build所需的未经检查的警告。

我可以想到的最好的是, JLS指定genericstypes的构造函数的方法引用推断generics参数:“如果方法或构造函数是generics的,则可以显式推断或提供适当的types参数。 “ 后来,它以ArrayList::new为例,并将其描述为“generics类的推断types参数”,从而确定ArrayList::new (而不是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隐含地推断generics参数。

所以我认为对数组构造函数的方法引用也是一样的。

它仍然使用原始types来创build数组,对吗?

Javagenerics只是编译时的错觉,所以原始types当然会在运行时用来创build数组。

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

是的,从Test[]Test<String>[]的未经检查的转换仍然在进行; 它只是在匿名的背景下发生在幕后。

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

由于匿名方法正在进行肮脏的工作,未经检查的强制转换会从托pipe代码中有效消失。