18

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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Alok Dubey
  • 419
  • 4
  • 13
  • @Eran Thanks for the correction, I failed to read that line's comment. I moved my IdeOne.com link into the questions for the convenience of others. – Basil Bourque Jun 02 '21 at 22:08

3 Answers3

20

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.

ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • 3
    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)? – Joachim Sauer Jun 02 '21 at 07:48
  • Thanks, @JoachimSauer. Will add the JLS link too. I just remembered that jsr link, being the first place I read about it. – ernest_k Jun 02 '21 at 07:51
  • Aren't such type of generics a bit weird and may produce vulnerability. Although testing needs to be done but still – Nishant Lakhara Jun 02 '21 at 08:15
  • 2
    @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. – ernest_k Jun 02 '21 at 08:21
  • @JoachimSauer poly expressions are in place everywhere were generics, lambdas, etc are used. Their type depends on the context of their usage. – Eugene Jun 02 '21 at 14:45
  • 1
    @Eugene: thanks, I'm well aware of what they are and what they do, I just didn't know the name of that concept. – Joachim Sauer Jun 02 '21 at 14:47
9

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

Eran
  • 387,369
  • 54
  • 702
  • 768
  • 1
    probably Eclipse somehow simplifies this for you, but the type is [not going to be Serializable](https://stackoverflow.com/a/67807376/1059372). – Eugene Jun 02 '21 at 14:46
6

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.

Eugene
  • 117,005
  • 15
  • 201
  • 306