使用Set.of时的JDK11泛型问题

使用 JDK 11 时,我无法理解以下类型安全问题。谁能解释当我直接传递Set.of参数时没有收到编译错误的原因:

public static void main(String[] args) {
    var intSet1 = Set.of(123, 1234, 101);
    var strValue = "123";
    isValid(strValue, intSet1);// Compilation error (Expected behaviour)
    **isValid(strValue, Set.of(123, 1234, 101));// No Compilation error**
}

static <T> boolean isValid(T value, Set<T> range) {
    return range.contains(value);
}

您可以在 IdeOne.com 上实时运行此代码。

回答

简而言之,编译器在第一次调用时坚持使用您声明的类型,但在第二次调用时有一定的空间来推断兼容类型。

使用isValid(strValue, intSet1);,您正在调用isValid(String, Set<Integer>),并且编译器不会T将两个参数解析为相同的类型。这就是它失败的原因。编译器根本无法更改您声明的类型。

isValid(strValue, Set.of(123, 1234, 101))但是,WithSet.of(123, 1234, 101)是一个poly 表达式,其类型在调用上下文中建立。因此,编译器致力于推断T适用于上下文的内容。正如 Eran 指出的那样,这是Serializable.

为什么第一个有效而第二个无效?这仅仅是因为编译器在作为第二个参数给出的表达式类型方面具有一定的灵活性。intSet1是一个独立的表达式,并且Set.of(123, 1234, 101)是一个 poly 表达式(请参阅JLS和 有关 poly expression 的描述)。在第二种情况下,上下文允许编译器计算适用于TString第一个参数的类型兼容的具体类型的类型。

  • Neat, I didn't know the name "poly expression". But shouldn't this link go to the [JLS](https://docs.oracle.com/javase/specs/jls/se16/html/jls-15.html#jls-15.12), since that's now the authoritative source (or maybe link to both)?
  • @NishantLakhara The worst they get is *complex* and *hard to understand* for developers. But if we didn't have the benefit of poly expressions, it would be very cumbersome to program using generics (lambda expressions, as an example, would immediately become a pain). There's possibility for surprises though, when the compiler works out a type the developer didn't expect or detect, but I'm sure any tested code can easily deal with this aspect. I'm unable to see it as unsafe, though.

回答

isValid(strValue, Set.of(123, 1234, 101));

当我将鼠标悬停isValid()在 Eclipse上的这个调用上时,我看到它将执行以下方法:

<Serializable> boolean com.codebroker.dea.test.StringTest.isValid(Serializable value, Set<Serializable> range)

当编译器尝试解析可能用于 的泛型类型参数T的类型时isValid,它需要找到String(的类型strValue)和Integer(的元素类型Set.of(123, 1234, 101))的公共超类型,并找到Serializable

因此Set.of(123, 1234, 101)解析为Set<Serializable>而不是Set<Integer>,因此编译器可以传递 aSerializable和 a Set<Serialiable>to isValid(),这是有效的。

isValid(strValue, intSet1);

另一方面,当您第一次分配Set.of(123,1234,101)给一个变量时,它被解析为 a Set<Integer>。在这种情况下, aString和 aSet<Integer>不能传递给您的isValid()方法。

如果你改变

var intSet1 = Set.of(123, 1234, 101);

Set<Serializable> intSet1 = Set.of(123,1234,101);

然后

isValid(strValue, intSet1);

也会通过编译。


回答

当您(作为人类)查看编译的第二个时 isValid,可能会想 - 这怎么可能?T编译器将类型推断 Stringor Integer,因此调用必须绝对失败。

编译器在查看方法调用时会以一种非常不同的方式思考。它查看方法参数和提供的类型,并尝试为您推断出完全不同且出乎意料的类型。有时,这些类型是“不可表示的”,这意味着编译器可以存在的类型,但作为用户的您不能声明此类类型。

有一个特殊的(未记录的)标志,您可以使用它来编译您的类并了解编译器如何“思考”:

 javac --debug=verboseResolution=all YourClass.java

输出会很长,但我们关心的主要部分是:

  instantiated signature: (INT#1,Set<INT#1>)boolean
  target-type: <none>
  where T is a type-variable:
    T extends Object declared in method <T>isValid(T,Set<T>)
  where INT#1,INT#2 are intersection types:
    INT#1 extends Object,Serializable,Comparable<? extends INT#2>,Constable,ConstantDesc
    INT#2 extends Object,Serializable,Comparable<?>,Constable,ConstantDesc

可以看到推断和使用的类型不是Stringand Integer


以上是使用Set.of时的JDK11泛型问题的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>