简体   繁体   中英

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:

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 .

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); , you're calling isValid(String, Set<Integer>) , and the compiler does not resolve T to the same type for the two 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. So the compiler works at inferring T that is applicable in context. As Eran points out, this is 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 ). 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.

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:

<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 .

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.

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> . And in that case, a String and a Set<Integer> cannot be passed to your isValid() method.

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? The type T is inferred by the compiler to either String or Integer , so the call must absolutely fail.

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. 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":

 javac --debug=verboseResolution=all YourClass.java

The output is going to be long, but the main part that we care about is:

  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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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