构造函数的参考 – 创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
::
new
forms,则考虑单个概念方法。 该方法具有int
types的单个参数,返回ArrayType ,并且没有throws
子句。 如果n = 1 ,这是唯一可能适用的方法; 否则,没有可能适用的方法。
换句话说,上面的List[]::new
expression式的行为与您写下的行为相同:
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,未经检查的转换 :
设
G
用n个参数来命名genericstypes声明。从原始类或接口types(§4.8)
G
到格式为G<T₁,...,Tₙ>
任何参数化types有一个未经检查的转换 。从原始数组types
G[]ᵏ
到格式为G<T₁,...,Tₙ>[]ᵏ
any的任何数组types都有一个未经检查的转换。 (符号[]ᵏ
表示k维的数组types。)使用未经检查的转换会导致编译时未经检查的警告,除非所有types自variables
T
(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代码中有效消失。