简体   繁体   English

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

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

I am unable to understand the below issue for type safety when using JDK 11. Can anyone explain the reason for not getting a compilation error when I am directly passing the Set.of in the argument:使用 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);
}

You can run this code live at IdeOne.com .您可以在 IdeOne.com 上实时运行此代码

To put it simply, the compiler is stuck with your declared types on the first call, but has some latitude to infer a compatible type on the second one.简而言之,编译器在第一次调用时被你声明的类型卡住了,但在第二次调用时有一定的自由度来推断兼容的类型。

With isValid(strValue, intSet1);使用isValid(strValue, intSet1); , you're calling isValid(String, Set<Integer>) , and the compiler does not resolve T to the same type for the two arguments. ,您正在调用isValid(String, Set<Integer>) ,编译器不会将T解析为两个 arguments 的相同类型。 This is why it's failing.这就是它失败的原因。 The compiler simply can't change your declared types.编译器根本无法更改您声明的类型。

With isValid(strValue, Set.of(123, 1234, 101)) , though, Set.of(123, 1234, 101) is a poly expression, whose type is established in the invocation context.但是,对于isValid(strValue, Set.of(123, 1234, 101))Set.of(123, 1234, 101)是一个多边形表达式,其类型是在调用上下文中建立的。 So the compiler works at inferring T that is applicable in context.因此,编译器致力于推断适用于上下文的T As Eran points out, this is Serializable .正如 Eran 指出的,这是Serializable

Why does the first one work and the second one doesn't?为什么第一个有效而第二个无效? It's simply because the compiler has some flexibility around the type of the expression given as the second argument.这仅仅是因为编译器对作为第二个参数给出的表达式类型具有一定的灵活性。 intSet1 is a standalone expression, and Set.of(123, 1234, 101) is a poly expression (see the JLS and this description about poly expression ). intSet1是一个独立的表达式,而Set.of(123, 1234, 101)是一个 poly 表达式(参见JLS关于 poly 表达式的描述)。 In the second case, the context allows the compiler to compute a type that works to a concrete T that is compatible with String , the first argument's type.在第二种情况下,上下文允许编译器计算一个适用于与第一个参数类型String兼容的具体T的类型。

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

When I hover with my mouse over this isValid() call on Eclipse, I see that it's going to execute the following method:当我用鼠标在 Eclipse 上调用此isValid()调用 hover 时,我看到它将执行以下方法:

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

When the compiler tries to resolve the possible type to use for the generic type parameter T of isValid , it needs to find a common super type of String (the type of strValue ) and Integer (the element type of Set.of(123, 1234, 101) ), and finds Serializable .当编译器试图解析isValid的泛型类型参数T可能使用的类型时,它需要找到一个公共的超类型StringstrValue的类型)和IntegerSet.of(123, 1234, 101) ) 的元素类型Set.of(123, 1234, 101) ),并找到Serializable

Therefore Set.of(123, 1234, 101) is resolved to Set<Serializable> instead of Set<Integer> , so the compiler can pass a Serializable and a Set<Serialiable> to isValid() , which is valid.因此Set.of(123, 1234, 101)被解析为Set<Serializable>而不是Set<Integer> ,因此编译器可以将SerializableSet<Serialiable>传递给isValid() ,这是有效的。

isValid(strValue, intSet1); isValid(strValue, intSet1);

On the other hand, when you first assign Set.of(123,1234,101) to a variable, it is resolved to a Set<Integer> .另一方面,当您第一次将Set.of(123,1234,101)分配给变量时,它被解析为Set<Integer> And in that case, a String and a Set<Integer> cannot be passed to your isValid() method.在这种情况下,不能将StringSet<Integer>传递给您的isValid()方法。

If you change如果你改变

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

to

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

Then然后

isValid(strValue, intSet1);

will pass compilation too.也会通过编译。

When you (as a human) look at the second isValid that compiles, probably think - how is that possible?当您(作为人类)查看编译的第二个isValid时,可能会想 - 这怎么可能? The type T is inferred by the compiler to either String or Integer , so the call must absolutely fail.编译器将类型T推断StringInteger ,因此调用必须绝对失败。

The compiler when it looks at the method call thinks in a very different way.编译器在查看方法调用时会以非常不同的方式思考。 It looks at the method arguments, at the types provided and tries to infer an entirely different and un-expected type(s) for you.它在提供的类型中查看方法 arguments,并尝试为您推断出完全不同且意外的类型。 Sometimes, these types are "non-denotable", meaning types that can exist for the compiler, but you as a user, can not declare such types.有时,这些类型是“不可表示的”,这意味着编译器可以存在的类型,但作为用户,您不能声明这些类型。

There is a special (un-documented) flag that you can compile your class with and have glimpse into how the compiler "thinks":有一个特殊的(未记录的)标志,您可以使用它编译 class 并了解编译器如何“思考”:

 javac --debug=verboseResolution=all YourClass.java

The output is going to be long, but the main part that we care about is: 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

You can see that the types that are inferred and used are not String and Integer .可以看到推断和使用的类型不是StringInteger

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

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