18

使用 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 上实时运行此代码

4

3 回答 3

20

简而言之,编译器在第一次调用时被你声明的类型卡住了,但在第二次调用时有一定的自由度来推断兼容的类型。

使用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 表达式的描述)。在第二种情况下,上下文允许编译器计算一种适用于与第一个参数的类型T兼容的具体的类型。String

于 2021-06-02T07:44:55.233 回答
9

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

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

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

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

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

isValid(strValue, intSet1);

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

如果你改变

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

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

然后

isValid(strValue, intSet1);

也会通过编译。

于 2021-06-02T07:26:14.717 回答
5

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

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

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

 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

于 2021-06-02T14:44:04.903 回答