繁体   English   中英

JDK 11 Generics 使用 Set.of 时的问题

[英]JDK 11 Generics Issue when using Set.of

使用 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解析为两个 arguments 的相同类型。 这就是它失败的原因。 编译器根本无法更改您声明的类型。

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

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

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

当我用鼠标在 Eclipse 上调用此isValid()调用 hover 时,我看到它将执行以下方法:

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

当编译器试图解析isValid的泛型类型参数T可能使用的类型时,它需要找到一个公共的超类型StringstrValue的类型)和IntegerSet.of(123, 1234, 101) ) 的元素类型Set.of(123, 1234, 101) ),并找到Serializable

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

isValid(strValue, intSet1);

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

如果你改变

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

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

然后

isValid(strValue, intSet1);

也会通过编译。

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

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

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

 javac --debug=verboseResolution=all YourClass.java

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

  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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM