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

Java Generics Hell

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

Все это сводится к этому коду. Все работает как ожидалось, кроме вызова 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>)
    }
}
4b9b3361

Ответ 1

Тип 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> >(){} )
                               ------------------  

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

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

Ответ 2

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

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

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

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

Что интересно, дает предупреждение типа, но все еще компилируется. Явный тип не требуется, однако из-за наличия достаточной информации о типе из параметра в infer общий тип.


В вашем вопросе отсутствуют некоторые объявления, поэтому я добавил их для создания Short Self-Contained Correct Example - то есть этот код компилируется как-есть

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