18

I get the following compilation message:

[javac]   ... error: incompatible types
[javac]         exceptionClassHolder = new Holder<>( (new Exception()).getClass() );
[javac]                                ^
[javac]   required: Holder<Class<? extends Exception>>
[javac]   found:    Holder<Class<CAP#1>>
[javac]   where CAP#1 is a fresh type-variable:
[javac]     CAP#1 extends Exception from capture of ? extends Exception
[javac] 1 error

It would seem to me that the according to the message all should be correct. CAP#1 indeed extends Exception. So how should the above message be understood? SSCCE below (initially not posted since I was hoping to understand the error message itself in the general case):

class Holder<T> {
    public T t;
    public Holder(T t) {
       this.t = t;
    }
}

public class FooMain {
    public static void main(String args[]) throws Exception {
        Holder<Class<? extends Exception>> exceptionClassHolder;
        exceptionClassHolder = new Holder<>( (new Exception()).getClass() );
    }
}
Marcus Junius Brutus
  • 26,087
  • 41
  • 189
  • 331

2 Answers2

26

Unfortunately, the existing answers don't explain what's going on here. First, the solution is to simply specify the type argument to Holder:

Holder<Class<? extends Exception>> exceptionClassHolder;
exceptionClassHolder =
        new Holder<Class<? extends Exception>>(new Exception().getClass());

The reason your version didn't work is because new Exception().getClass() returns a Class<? extends Exception>, where ? is a wildcard capture (referred to in the compiler error message as CAP#1). Since you use the "diamond operator" with new Holder<>, the compiler infers Class<CAP#1 extends Exception> for T and so Holder<Class<CAP#1 extends Exception>> is the type of the created object.

However, this doesn't match your declared type of Holder<Class<? extends Exception>>. It uses a nested wildcard, which doesn't capture: while CAP#1 extends Exception is some specific type extending Exception, the nested ? extends Exception represents literally any type extending Exception.

And while Class<CAP#1 extends Exception> is a subtype of Class<? extends Exception>, Holder<Class<CAP#1 extends Exception>> is not a subtype of Holder<Class<? extends Exception>> because generics aren't covariant, so the assignment fails.

By manually specifying Class<? extends Exception> for T, you help the compiler avoid this "trap".

See my similar answers on these posts:

Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • +1 for the extended explanation. But I don't understand why you state that "First, the solution is to simply specify the type argument to Holder". Why? In Java 7, it should be inferred by the compiler. Anyway, if not, it should have crashed at compile time, which is not the OP's issue. – Andrei Nicusan Dec 12 '13 at 16:40
  • 1
    It is inferred by the compiler, but not "correctly" - the inference gives precedence to the constructor argument over the variable. And it did fail at compile time. – Paul Bellora Dec 12 '13 at 16:42
  • Interesting. I only looked at the return type of Object.getClass() and not at the text of the documentation, which indeed says that the actual type returned is `Class extends Exception>`. Am I right in thinking that this is a very special case handled specifically by the compiler, and that it's not possible to do what getClass() does with regular Java code? – JB Nizet Dec 12 '13 at 21:01
  • @JBNizet That's exactly right, sorry for not elaborating earlier. – Paul Bellora Dec 12 '13 at 21:21
  • I still prefer the solution by Joop Eggen since it's making it possible to use the diamond operator and the ugliness is hidden in only one place. However I am accepting your answer as my question was worded to elicit information on understanding the message and the error and not so much on what solution to apply. – Marcus Junius Brutus Dec 13 '13 at 07:17
  • ...and this is why I dislike Java's implementation of generics. I don't know how the average junior Java developer will understand how to avoid this aside from trial-and-error with IDE error messages. – Ed Griebel Apr 22 '16 at 17:47
  • @EdGriebel: you’re talking about an issue that did not exist prior to Java 7 (as there was no “diamond operator”) and has been solved in Java 8 already via target typing. Considering the time scale of Generics, from Java 5 to Java 9, it’s not appropriate to use this issue of one version to discredit “Java’s implementation of generics” in general. – Holger Jan 05 '18 at 13:09
2
    Holder<? extends Class<? extends Exception>> exceptionClassHolder;
    exceptionClassHolder = new Holder<>( (new Exception()).getClass() );

The reason is that

  1. Because you are not using Exception.class but an Exception object, java thinks ? extends Exception is required.
  2. The same holds for getClass(), again ? extends Class needed, though Class is final.

Certainly one day this will be simplified.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • This is unnecessary - see my answer. – Paul Bellora Dec 12 '13 at 16:41
  • 1
    @PaulBellora my solution works _with_ the diamond operator (as questioned). Reading your excellent answer (my +1), one receives the impression that a diamond operator should not work. _As you seem to have, I have a programming language/compiler construction background, so you might ellaborate a bit more._ Thanks. – Joop Eggen Dec 12 '13 at 17:43
  • You're right that the diamond operator works when assigning to `Holder extends Class extends Exception>>` - this is because that type is assignable from `Holder>`, unlike `Holder>`. The type inference process is still the same - the compiler gives precedence to the constructor argument and infers `Class` for `T`. – Paul Bellora Dec 12 '13 at 18:12
  • I wanted to point out that using `Holder extends Class extends Exception>>` is unnecessary because it restricts `exceptionClassHolder` to being a ["producer"](http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs) only, which might not be desirable. – Paul Bellora Dec 12 '13 at 18:18