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

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,未经检查的转换:

让我们Gn类参数命名泛型类型声明.

存在未检查的转换从原始类或接口类型(§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创建了必需的未经检查警告,则不会出现问题.

  • 是的,这是一个错误.我们已经提交了[JDK-8175317](https://bugs.openjdk.java.net/browse/JDK-8175317).我认为之前没有捕获过,因为数组构造函数referencess在Stream.toArray()中更常用,而`toArray(List [] :: new)`会发出警告.这通过涉及目标类型逻辑的不同路径.奇怪的是,这种简单赋值的情况不会被检查. (6认同)
  • 有趣的是,只要您使用方法引用,当从原始方法返回类型到参数化目标接口类型发生未经检查的转换时,似乎始终默默忽略此警告.+1 (3认同)
  • @Jorn Vernee:但是`Test <?> []`与`Test <String> []`不兼容,所以推断`Test <?> []`并默默地将它分配给`Test <String> []`比将Test []`赋给`Test <String> []`更糟糕. (3认同)

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隐式推断了泛型参数.

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

  • 当然,类型推断是一个很好的解释.对构造函数的底层调用是一个运行时问题,无论如何都会删除泛型.类型推断发生在_compile_ time,与调用构造函数无关.完全没有.它只取决于编译器是否有足够的信息来强制执行类型断言,类型推断说"是的,确实如此". (2认同)
  • 泛型数组类型的问题是你总是可以编写`Object [] oa = genericArray;`,然后将`Test <Object>`存储到`oa`而不会收到任何警告.即使在运行时,也不会检查.使用方法引用尚未解决此问题,并且类型推断无关紧要. (2认同)

Pat*_*ker 5

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

Java泛型只是一种编译时错觉,因此原始类型当然会在运行时用于创建数组.

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

是的,从投选中Test[]Test<String>[]仍在进行之中; 它只是在一个匿名的背景下发生在幕后.

Test<String>[] t3 = ((IntFunction<Test<String>[]>) Test[]::new).apply(10);
Run Code Online (Sandbox Code Playgroud)

由于匿名方法正在执行脏工作,因此未经检查的强制转换会从托管代码中消失.

  • @WillisBlackburn我不是一个专家,但我认为构造函数类型推断不是关键原因.毕竟我们在这里谈论阵列.猜猜我们将不得不等到专家权衡. (3认同)