Несовместимые типы и свежая переменная типа

Я получаю следующее сообщение компиляции:

[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

Мне кажется, что согласно сообщению все должно быть правильно. CAP#1 действительно расширяет исключение. Итак, как должно быть понято это сообщение? SSCCE ниже (изначально не опубликовано, так как я надеялся понять само сообщение об ошибке в общем случае):

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() );
    }
}

Ответ 1

К сожалению, существующие ответы не объясняют, что происходит здесь. Во-первых, решение состоит в том, чтобы просто указать аргумент типа Holder:

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

Причина, по которой ваша версия не работает, состоит в том, что new Exception().getClass() возвращает Class<? extends Exception>, где ? является подстановочный знак (упоминается в сообщении об ошибке компилятора как CAP#1). Поскольку вы используете "алмазный оператор" с new Holder<>, компилятор выводит Class<CAP#1 extends Exception> для T и поэтому Holder<Class<CAP#1 extends Exception>> - это тип созданного объекта.

Однако это не соответствует объявленному типу Holder<Class<? extends Exception>>. Он использует вложенный подстановочный знак, который не захватывает: while CAP#1 extends Exception - это определенный тип расширения Exception, вложенный ? extends Exception представляет буквально любой тип, расширяющий Exception.

И хотя Class<CAP#1 extends Exception> является подтипом Class<? extends Exception>, Holder<Class<CAP#1 extends Exception>> не является подтипом Holder<Class<? extends Exception>>, потому что generics не являются ковариантными, поэтому присваивание не выполняется.

Ручным указанием Class<? extends Exception> для T, вы поможете компилятору избежать этой "ловушки".

См. мои похожие ответы на эти сообщения:

Ответ 2

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

Причина в том, что

  • Поскольку вы не используете Exception.class, а объект Exception, java считает, что требуется ? extends Exception.
  • То же самое верно для getClass(), снова ? extends Class необходимо, хотя класс является окончательным.

Конечно, в один прекрасный день это будет упрощено.