4

I have a problem understanding captures in type inference. I have some code that looks like this:

import java.util.EnumSet;

class A {
    static enum E1 {
    X
    }

    private static <T extends Enum<T>> EnumSet<T> barEnum(Class<T> x) {
        return null;
    }

    private static void foo1(EnumSet<E1> s, E1 e) {
        EnumSet<E1> x2 = barEnum(e.getClass());
    }

    private static void foo2(EnumSet<E1> s) {
        EnumSet<E1> x = barEnum(s.iterator().next().getClass());
    }
}

This gives two errors when compiling:

Test.java:15: error: method barEnum in class A cannot be applied to given types;
        EnumSet<E1> x2 = barEnum(e.getClass());
                         ^
  required: Class<T>
  found: Class<CAP#1>
  reason: inference variable T has incompatible equality constraints E1,CAP#2
  where T is a type-variable:
    T extends Enum<T> declared in method <T>barEnum(Class<T>)
  where CAP#1,CAP#2 are fresh type-variables:
    CAP#1 extends E1 from capture of ? extends E1
    CAP#2 extends E1 from capture of ? extends E1
Test.java:19: error: method barEnum in class A cannot be applied to given types;
        EnumSet<E1> x = barEnum(s.iterator().next().getClass());
                        ^
  required: Class<T>
  found: Class<CAP#1>
  reason: inference variable T has incompatible equality constraints E1,CAP#2
  where T is a type-variable:
    T extends Enum<T> declared in method <T>barEnum(Class<T>)
  where CAP#1,CAP#2 are fresh type-variables:
    CAP#1 extends E1 from capture of ? extends E1
    CAP#2 extends E1 from capture of ? extends E1
Note: Test.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
2 errors

While trying to understand the error, I changed foo2 to capture the value of getClass() in a local variable to see the actual type:

private static void foo2(EnumSet<E1> s) {
    // this works
    Class<? extends Enum> c = s.iterator().next().getClass();
    EnumSet<E1> y = barEnum(c);
}

Now, the error disappeared and the code is compiled. I don't understand how the introduction of a local variable with the exact same type as the expression changes the type inference algorithm and solves the problem.

Jens
  • 9,058
  • 2
  • 26
  • 43
  • It doens't have anything to do with *local* variables. It has to do with the types concerned. – user207421 Jan 31 '18 at 09:36
  • @EJP Care to elaborate? The local variable does not change the types, so why is there a difference? And why does the generic version work? – Jens Jan 31 '18 at 09:37

2 Answers2

1

When you assign s.iterator().next().getClass() to a local variable, you are using a raw type - Enum. That's how you get over the compilation error, but get a warning instead.

You can get the same behavior without the local variable if you use casting instead:

private static void foo2(EnumSet<E1> s) {
    EnumSet<E1> x = barEnum((Class<? extends Enum>)s.iterator().next().getClass());
}

You can avoid the cast with:

private static void foo2(EnumSet<E1> s) {
    EnumSet<E1> x = barEnum(s.iterator().next().getDeclaringClass());
}
Eran
  • 387,369
  • 54
  • 702
  • 768
  • Thnaks. The local variable should have had type `Class extends E1>`, then it is consistent. But I still don't understand why the generic function compiles without a cast. – Jens Jan 31 '18 at 09:42
  • I think I'll split the question with the generics into another question and accept your answer. – Jens Jan 31 '18 at 10:01
-1

In your original example, T is incompatible with E1 because T is an enum and E1 isn't. (Still don't know why though. Suggestions welcome.)

Change your method declarations to include type definitions to tell the compiler E1 is also an enum:

private static <E1 extends Enum<E1>> void foo1(EnumSet<E1> s, E1 e) {
Mark Jeronimus
  • 9,278
  • 3
  • 37
  • 50
  • I am not sure I understand. `E1` is `enum E1 {X}` which means that it extends `Enum` (JLS: The direct superclass of an enum type named E is Enum). Making it generic results in essentially the same function as `foo3` which compiles, but I still don't understand why this makes a difference. – Jens Jan 31 '18 at 09:33