Подтвердить что ты не робот

Несоответствие типов для универсальных классов

У меня есть следующий код, который не будет компилироваться, и хотя есть способ его компиляции, я хочу понять, почему он не компилируется. Может кто-нибудь просветить меня конкретно, почему я получаю сообщение об ошибке, которое я отправлю в конце, пожалуйста?

public class Test {
    public static void main(String args[]) {
        Test t = new Test();
        t.testT(null);
    }

    public <T extends Test> void testT(Class<T> type) {
        Class<T> testType = type == null ? Test.class : type; //Error here
        System.out.println(testType);
    }
}

Type mismatch: cannot convert from Class<capture#1-of ? extends Test> to Class<T>

Отбрасывая Test.class в Class<T>, он компилируется с предупреждением Unchecked cast и отлично работает.

4b9b3361

Ответ 1

Причина в том, что Test.class имеет тип Class <Test> . Вы не можете назначить ссылку типа Class <Test> к переменной типа Class <T> поскольку они не одно и то же. Это, однако, работает:

Class<? extends Test> testType = type == null ? Test.class : type;

Подстановочный знак позволяет использовать класс <T> и Class <Test> ссылки, которые будут назначены testType.

Существует тонна информации о поведении Java-дженериков в Часто задаваемые вопросы по Java Generics Angelika Langer. Я приведу пример, основанный на некоторой информации там, где используется базовый API-интерфейс иерархии классов Number.

Рассмотрим следующий метод:

public <T extends Number> void testNumber(final Class<T> type)

Это позволяет успешно компилировать следующие операторы:

testNumber(Integer.class);
testNumber(Number.class);

Но следующее не будет компилироваться:

testNumber(String.class);

Теперь рассмотрим следующие утверждения:

Class<Number> numberClass = Number.class;
Class<Integer> integerClass = numberClass;

Вторая строка не может скомпилировать и произвести эту ошибку Type mismatch: cannot convert from Class<Number> to Class<Integer>. Но Integer extends Number, так почему это не удается? Посмотрите на следующие два утверждения, чтобы узнать, почему:

Number anumber = new Long(0);
Integer another = anumber;

Довольно легко понять, почему вторая строка не компилируется здесь. Вы не можете назначить экземпляр Number переменной типа Integer, потому что нет способа гарантировать, что экземпляр Number имеет совместимый тип. В этом примере Number на самом деле является Long, который, безусловно, не может быть назначен Integer. Фактически, ошибка также является несоответствием типа: Type mismatch: cannot convert from Number to Integer.

Правило заключается в том, что экземпляр не может быть назначен переменной, которая является подклассом типа экземпляра, поскольку нет гарантии, которая совместима.

Подобные же методы ведут себя так же. В сигнатуре обобщенного метода T является просто заполнителем, чтобы указать, что метод позволяет компилятору. Когда компилятор встречает testNumber(Integer.class), он по существу заменяет T на Integer.

Подстановочные знаки добавляют дополнительную гибкость, поскольку следующее будет компилироваться:

Class<? extends Number> wildcard = numberClass;

Так как Class<? extends Number> указывает любой тип, который является Number или подклассом Number, это совершенно законно и потенциально полезно во многих случаях.

Ответ 2

Предположим, что я продлеваю тест:

public class SubTest extends Test {
  public static void main(String args[]) {
    Test t = new Test();
    t.testT(new SubTest());
  }
}

Теперь, когда я вызывал testT, параметр типа <T> равен SubTest, что означает, что переменная testType равна Class<SubTest>. Test.class имеет тип Class<Test>, который не присваивается переменной типа Class<SubTest>.

Объявление переменной testType как Class<? extends Test> является правильным решением; кастинг на Class<T> скрывает реальную проблему.

Ответ 3

Удалить условное, и ошибка немного приятнее...

public class Test {
    public static void main(String args[]) {
        Test t = new Test();
        t.testT(null);
    }

    public <T extends Test> void testT(Class<T> type) {
    Class<T> testClass = Test.class;
        System.out.println(testClass);
    }
}


Test.java:10: incompatible types
found   : java.lang.Class<Test>
required: java.lang.Class<T>
        Class<T> testClass = Test.class;