Java Generics Ад - PullRequest
       29

Java Generics Ад

25 голосов
/ 14 августа 2011

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

Все сводится к этому коду.Все работает как положено, кроме звонка на genericsHell(ShapeSaver.class):

interface Shape { }

interface Circle extends Shape { }

interface ShapeProcessor<T extends Shape> { }

class CircleDrawer implements ShapeProcessor<Circle> { } 

class ShapeSaver<T extends Shape> implements ShapeProcessor<T> { }

class Test {
    void genericsHeaven(ShapeProcessor<? extends Shape> a) {}

    void genericsHell(Class<? extends ShapeProcessor<? extends Shape>> a) {}

    void test() {
        genericsHeaven(new CircleDrawer());
        genericsHeaven(new ShapeSaver<Circle>());
        genericsHell(CircleDrawer.class);
        genericsHell(ShapeSaver.class); // ERROR: The method genericsHell is not applicable for the arguments (Class<ShapeSaver>)
    }
}

Ответы [ 2 ]

17 голосов
/ 15 августа 2011

Тип ShapeSaver.class равен Class<ShapeSaver>.Когда он передается в genericsHell(), компилятору необходимо проверить, является ли Class<ShapeSaver> подтипом Class<? extends ShapeProcessor<?>, что сводится к тому, является ли ShapeSaver подтипом ShapeProcessor<?>.Отношение подтипа не выполняется, вызов метода завершается неудачей.

То же самое должно быть верно для решения @ Bohemian.Здесь проверка подтипа происходит при проверке границы T после вывода T.Это тоже должно провалиться.Похоже, это ошибка компилятора, которая каким-то образом неверно истолковывает правило, что Raw присваивается Raw<X>, как если бы Raw был подтипом Raw<X>.см. также Enum.valueOf выдает предупреждение для неизвестного типа класса, расширяющего Enum?

Простое решение вашей проблемы - действительно объявить

void genericsHell(Class<? extends ShapeProcessor> a)

, ShapeSaver - это подтип ShapeProcessor, и вызов компилируется.

Это не просто обходной путь.Для этого есть веская причина.Строго говоря, для любого Class<X>, X должен быть необработанным типом.Например, Class<List> - это нормально, Class<List<String>> - нет.Потому что действительно нет класса, который представляет List<string>;есть только класс, представляющий List.

Игнорировать строгое предупреждение о том, что вы не должны использовать необработанный типИногда мы должны использовать необработанные типы, учитывая, как устроена система типов Java.Даже основные API Java (Object.getClass()) используют необработанные типы.


Вы, вероятно, намеревались сделать что-то подобное

genericsHell(ShapeSaver<Circle>.class);

К сожалению, это не разрешено.Java может иметь, но не вводить литерал типа вместе с обобщениями.Это создало много проблем для большого количества библиотек.java.lang.reflect.Type беспорядок и непригоден для использования.Каждая библиотека должна представить свое собственное представление системы типов для решения проблемы.

Вы можете позаимствовать одну, например, у Guice, и вы сможете

genericsHell( new TypeLiteral< ShapeSaver<Circle> >(){} )
                               ------------------  

(научитьсяпропустите скобки вокруг ShaveSaver<Circle> при чтении кода)

В теле метода genericsHell() вы получите полную информацию о типе, а не только класс.

11 голосов
/ 14 августа 2011

Ввод метода genericsHell позволяет компилировать:

static <T extends ShapeProcessor<?>> void genericsHell(Class<T> a) {}

EDITED: Это позволяет компилятору указать из контекста или путем кодирования явного типа, что ShapeProcessor не буквально любой ShapeProcessor, но такой же как тот, который передается в качестве параметра. Если бы вызов был явно набран (что делает компилятор под прикрытием), код будет выглядеть так:

MyClass.<ShapeSaver>genericsHell(ShapeSaver.class);

Что интересно, выдает предупреждение , но все равно компилируется. Однако явный тип не требуется, поскольку в параметре достаточно информации о типе, чтобы вывести универсальный тип.


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

static interface Shape { }

static interface Circle extends Shape { }

static interface ShapeProcessor<T extends Shape> { }

static class CircleDrawer implements ShapeProcessor<Circle> { }

static class ShapeSaver<T extends Shape> implements ShapeProcessor<T> { }

static void genericsHeaven(ShapeProcessor<? extends Shape> a) { }

// The change was made to this method signature:
static <T extends ShapeProcessor<?>> void genericsHell(Class<T> a) { }

static void test() {
    genericsHeaven(new CircleDrawer());
    genericsHeaven(new ShapeSaver<Circle>());
    genericsHell(CircleDrawer.class);
    genericsHell(ShapeSaver.class);
}
...