2017-02-20 30 views
56

在Java中没有任何警告,这是不可能的创建泛型类型直接的数组:构造参考 - 在创建泛型阵列

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

然而,我们可以做到这一点使用原始类型:

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); 

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

+4

有趣...编写'n - > new Test [n];'而不是'Test [] :: new'(应该基本相同)会再次给你一个未经检查的编译警告 – Roland

+2

@Roland它们都是也被排除在相同的字节码上。确实有趣 – Eugene

+1

@Eugene:好吧,对于正式的规则,通常无关于语言结构的解构。但是编译器显然应该在这里发出* unchecked *警告。 – Holger

回答

28

你的问题是有道理的。总之,方法参考确实使用原始类型(或应该使用原始类型),究其原因,为什么通用阵列的创建是被禁止的,当使用方法的引用,因此,能够静静地创建一个函数创建仍适用通用数组显然违反了语言设计的意图。

为什么禁止创建泛型数组的原因是数组类型继承,源于泛型前一代,与泛型类型系统不兼容。即你可以这样写:

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执行类型推断推断出通用元素类型List<String>。这很容易证明,通用阵列的创建仍然是被禁止的:

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

由于List<String>[]::new是非法的,如果List[]::new被接受而不发出警告,通过推断它是有效的非法List<String>[]::new才是怪事。

JLS §15.13明确规定:

如果一个方法参考表达式具有形式数组类型::new,然后数组类型必须表示一个类型是reifiable(§4.7)或编译时错误发生。

这已经意味着List<String>[]::new是非法的,因为List<String>不reifiable,而List<?>[]::new是合法的,因为List<?>是reifiable和List[]::new是合法的,如果我们考虑List是一个原始类型,为原料类型List是可验证的。

然后§15.13.1规定:

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

换句话说,所述List[]::new表达的行为以上是相同的,如果你写:

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

不同之处在于该方法create只是概念上的。事实上,通过这个明确的方法声明,在create方法中只有原始类型警告,但没有未检查有关在方法参考中将List[]转换为List<String>[]的警告。所以这是可以理解的,编译器在List[]::new的情况下会发生什么情况,其中使用原始类型的方法只是名义上的,即在源代码中不存在。

但由于没有选中警告是JLS §5.1.9, Unchecked Conversion明显违反:

G名称与ň类型参数的泛型类型声明。

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

存在从原始数组类型G[]ᵏ到格式为G<T₁,...,Tₙ>[]ᵏ的任何数组类型的未经检查的转换。 (记号[]ᵏ指示ķ尺寸数组类型。)

使用未经检查的转换会导致编译时未检查警告除非所有类型的参数Tᵢ(1≤Ñ)是无限通配符(§4.5.1),或未经检查的警告被SuppressWarnings注释(§9.6.4.5)所抑制。

所以,List[]List<?>[]转换是合法的,因为List是参数采用无界通配符,但是从List[]List<String>[]转换,必须出示选中警告,这是至关重要的位置,因为使用List[]::new不会生成原始类型以显式创建方法出现的警告。没有原始类型警告似乎不是一个违反(据我了解§4.8),这不会是一个问题,如果javac创建所需的未经检查警告。

+0

很好的分析。我仍然认为类型推断是没有警告的原因。但我同意它不应该推断一种类型。 –

+3

有趣的是,只要您使用方法引用,当未经检查的转换从原始方法返回类型转换为参数化目标接口类型时,似乎总是默默忽略此警告。 +1 –

+1

jdk-9,构建157 - 相同的“非警告”行为 – Eugene

10

我能想到的最好的是JLS指定泛型类型的构造函数的方法引用推断泛型参数: “如果方法或构造函数是泛型的,则适当的类型参数可以是推断或明确提供。“以后它给ArrayList::new作为一个例子,将其描述为“推断类型参数通用类”,从而建立该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隐含推断通用参数。

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

+1

https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13 “如果方法或构造函数是泛型的,则可以推断出合适的类型参数,明确提供。“ 后来它给出了ArrayList :: new作为示例,并将其描述为”泛型类的推断类型参数“,从而确定”ArrayList :: new“(而不是”ArrayList <> :: new“)是语法 –

+1

好点, –

+1

但是,如果推断出类型参数,这意味着将调用'new Test []',这是一个编译器错误,因为没有办法通过'Test '如果_if_仍然调用'new Test []'(这是我的假设),这种类型推断不是一个很好的解释,为什么没有警告imho。 –

5

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

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

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

是的,从Test[]Test<String>[]的未经检查的演员阵容仍在发生;它只是在匿名的背景下发生在幕后。

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

由于anonymous method做肮脏的工作,未选中的铸有效地从托管代码消失。

+0

这与我的初步答案类似,但我不认为它实际上回答了问题。即使“肮脏的工作”是隐藏的,效果仍然是一样的,问题是为什么编译器在这种情况下不会发出警告。我认为正确答案(我在下面提供)是“new Test [n]”引用原始类型Test,而方法引用Test [] :: new则推断泛型类型参数。 –

+3

@WillisBlackburn我不是任何方式的专家,但我认为构造器类型推断不是关键原因。毕竟我们在这里讨论数组。猜猜我们将不得不等待,直到专家权衡。 –