Comments 39
Я в Java не разбираюсь, но разве MyClass не должен быть параметрическим, или определять параметр T базового класса?
А то куда-то параметр T при наследовании пропал.
А то куда-то параметр T при наследовании пропал.
Пример вымышленный. В реальном коде наследник будет параметризованным. Но здесь параметризация опущена умышленно, чтобы раскрыть проблему.
В целом, не параметризовать наследника — это не ошибка.
Дженерики в Java создавались с учетом обратной совместимости. Раньше был просто Collection, потом создали generics и появился Collection. Но те, у кого старый код, вполне могли создать своего наследника MyCollection extends SomeCollection (где SomeCollection параметризован ). Т.к. у нас есть обратная совместимость, то старый код не свалится, просто параметризация Collection будет для него проигнорирована.
В целом, не параметризовать наследника — это не ошибка.
Дженерики в Java создавались с учетом обратной совместимости. Раньше был просто Collection, потом создали generics и появился Collection. Но те, у кого старый код, вполне могли создать своего наследника MyCollection extends SomeCollection (где SomeCollection параметризован ). Т.к. у нас есть обратная совместимость, то старый код не свалится, просто параметризация Collection будет для него проигнорирована.
То есть Java инициирует все пропущенные генерики параметров классом Object?
Забавно.
Забавно.
Формально — да. В реальности — «generics в Java не существует» :)
А с чего такие выводы, что не существует? Дело в том, что в рантайме ничего не мешает определить какой именно тип используется у класса как параметризатор для дженерикс. Или вы имеете в виду, что в Listв рантайме ничего не мешает положить Integer?
Да, ничто не мешает положить туда Integer, это сделано опять же в целях совместимости.
(Старые программы могли использовать эту особенность)
(Старые программы могли использовать эту особенность)
Имеется в виду второе. List «ничего не знает» о том, что же в нем лежит (для каких элементов он предназначен). Если компилятор не проверил то, что кладется в List (или просто не имел возможности это сделать) на этапе компиляции, то мы можем получить ClassCastException в неожиданных местах.
На самом деле это не важно. В данном случае мы получаем эксепшн при взятии не правильного элемента из коллекции, а если бы в рантайме тип коллекции проверялся, то мы бы получали эксепшн при попытке положить. Т.е. все сводится к месту где мы замечаем проблему. Кстати в commons-collections есть способы «затипизировать» коллекции таким образом, что будет эксепшн при попытке положить в рантайме.
Закралась опечатка. Должно быть:
«Раньше был просто Collection, потом создали generics и появился Collection<E>»
Приняли за html =)
«Раньше был просто Collection, потом создали generics и появился Collection<E>»
Приняли за html =)
абсолютно не должен быть. даже более — это очень частая ситуация, когда интерфейсы параметризируются со временем, а все имплементирующие классы никто трогать не будет
Issue by design :)
А именно: generic'и в java реализованы поверх jvm, на уровне компилятора чуть ли не синтаксической заменой; никакой информации в рантайме о том, каким типом был параметризован generic, не поддерживается. Соответственно, Class, параметризованный наиболее общим типом Object, после компиляции будет заменен на Class (будет эквивалентен просто типу Class). Именно такой реализации вы не предоставили (напомню, что классы компилируются отдельно друг от друга, и потому MyClass требует точной ссылки на Class, а не на Class
А именно: generic'и в java реализованы поверх jvm, на уровне компилятора чуть ли не синтаксической заменой; никакой информации в рантайме о том, каким типом был параметризован generic, не поддерживается. Соответственно, Class, параметризованный наиболее общим типом Object, после компиляции будет заменен на Class (будет эквивалентен просто типу Class). Именно такой реализации вы не предоставили (напомню, что классы компилируются отдельно друг от друга, и потому MyClass требует точной ссылки на Class, а не на Class
… Почему-то сожрало остаток фразы.
Так вот, кстати, Netbeans это понимает и предлагает заменить Class
Так вот, кстати, Netbeans это понимает и предлагает заменить Class
>параметризованный наиболее общим типом Object
Не совсем понял, причем тут именно Object. В данном примере можно заменить Class на любой другой параметризованный класс, а Object на любой объект. У меня в изначальном примере было Collection, но я хотел вырезать импорты чтобы сделать пост покороче, поэтому взял все классы из java.lang :).
Насчет рантайма согласен, но: как связана параметризация метода test и параметризация класса BaseClass?
Достоверно известно, что пример будет работать, если:
1. Добавить параметризацию при наследовании (напр. MyClass extends BaseClass)
2. Убрать параметризацию BaseClass.
3. Убрать дженерики из параметров метода test (т.е. оставить test(Class clazz)).
Мне непонятно почему параметризация BaseClass приводит к ошибке.
Не совсем понял, причем тут именно Object. В данном примере можно заменить Class на любой другой параметризованный класс, а Object на любой объект. У меня в изначальном примере было Collection, но я хотел вырезать импорты чтобы сделать пост покороче, поэтому взял все классы из java.lang :).
Насчет рантайма согласен, но: как связана параметризация метода test и параметризация класса BaseClass?
Достоверно известно, что пример будет работать, если:
1. Добавить параметризацию при наследовании (напр. MyClass extends BaseClass)
2. Убрать параметризацию BaseClass.
3. Убрать дженерики из параметров метода test (т.е. оставить test(Class clazz)).
Мне непонятно почему параметризация BaseClass приводит к ошибке.
Опять забыл что угловые скобки съедаются как html, пардон.
Правильно читать: «в изначальном примере было Collection<String>» и «напр. MyClass extends BaseClass<String>»
Правильно читать: «в изначальном примере было Collection<String>» и «напр. MyClass extends BaseClass<String>»
Насчёт «никакой информации в рантайме» вы неправы. Как раз в случае наследования от параметризованных классов информация о параметризации предка имеется и может быть вытащена через reflection.
Вы не правы. Сохраняется только информация о конкретной параметризации полей, методов и их аргументов
Я говорил про getGenericSuperclass(). А вот что бы сохранялись типы полей, методов и аргументов — это новость… Как же Type Erasure?
Применимо только для конкретной параметризации (когда она ясна на этапе компиляции). Тоесть, если ваш класс параметризуем (а не параметризован), то информацию о парметризации нельзя достать в рантайме.
Наверное вы просто не пробовали:) А вот у меня код отлично в рантайме определяет генерикс типы классов.
Можно достать информацию о параметризации конкретного аргумента/поля/результата функции. То есть если есть функция
List<String> doSomething()
То через reflection можно получить информацию о том, чем параметризирован List в данном конкретном случае.
Но если просто получать Class.forName(«List»), то, разумеется, никакой информации о параметризации мы не получим.
List<String> doSomething()
То через reflection можно получить информацию о том, чем параметризирован List в данном конкретном случае.
Но если просто получать Class.forName(«List»), то, разумеется, никакой информации о параметризации мы не получим.
Поскольку родовые типы реализуются практически полностью в компиляторе Java, а не в библиотеке время исполнения, практически вся информация о родовых типах «стирается» при генерации байткода. Другими словами, компилятор генерирует почти такой же код, который вы написали бы вручную без использования родовых типов и приведений типов, после проверки вашей программы на независимость от типа. В отличие от С++ List и List являются одним и тем же классом (хотя и имеют различные типы, являющиеся подтипами List — отличие более важное в JDK 5.0, чем в предыдущих версиях языка).
Одним из побочных эффектов механизма стирания является неспособность класса реализовать оба интерфейса Comparable и Comparable, поскольку оба они на самом деле являются одним и тем же интерфейсом, указывая на один и тот же метод compareTo(). Может показаться более благоразумным объявить класс DecimalString, совместимый как со Strings, так и с Numbers, но с точки зрения компилятора Java вы пытались бы объявить один и тот же метод дважды:
public class DecimalString implements Comparable, Comparable {… } // нет
Еще одним следствием механизма стирания является бессмысленность использования приведений типов и instanceof с параметрами родовых типов. В следующем фрагменте кода независимость от типа совершенно не улучшается:
public T naiveCast(T t, Object o) { return (T) o; }
Компилятор просто выдаст не отмеченное предупреждение о преобразовании, поскольку не знает о том, является приведение типов безопасным или нет. Метод naiveCast() фактически не выполняет какого-либо приведения типов. T просто замещается его «стирателем» (Object), а переданный объект будет приведен в Object — не то что задумывалось.
Механизм стирания также ответственен за описанные выше проблемы создания – вы не можете создать объект родового типа, поскольку компилятор не знает, какой конструктор вызвать. Если ваш родовой класс требует создания объектов, чей тип указан параметрами родового типа, его конструкторы должны принимать литерал класса (Foo.class) и сохранять его, для того чтобы экземпляры могли быть созданы отображением.
Одним из побочных эффектов механизма стирания является неспособность класса реализовать оба интерфейса Comparable и Comparable, поскольку оба они на самом деле являются одним и тем же интерфейсом, указывая на один и тот же метод compareTo(). Может показаться более благоразумным объявить класс DecimalString, совместимый как со Strings, так и с Numbers, но с точки зрения компилятора Java вы пытались бы объявить один и тот же метод дважды:
public class DecimalString implements Comparable, Comparable {… } // нет
Еще одним следствием механизма стирания является бессмысленность использования приведений типов и instanceof с параметрами родовых типов. В следующем фрагменте кода независимость от типа совершенно не улучшается:
public T naiveCast(T t, Object o) { return (T) o; }
Компилятор просто выдаст не отмеченное предупреждение о преобразовании, поскольку не знает о том, является приведение типов безопасным или нет. Метод naiveCast() фактически не выполняет какого-либо приведения типов. T просто замещается его «стирателем» (Object), а переданный объект будет приведен в Object — не то что задумывалось.
Механизм стирания также ответственен за описанные выше проблемы создания – вы не можете создать объект родового типа, поскольку компилятор не знает, какой конструктор вызвать. Если ваш родовой класс требует создания объектов, чей тип указан параметрами родового типа, его конструкторы должны принимать литерал класса (Foo.class) и сохранять его, для того чтобы экземпляры могли быть созданы отображением.
Поскольку родовые типы реализуются практически полностью в компиляторе Java, а не в библиотеке время исполнения, практически вся информация о родовых типах «стирается» при генерации байткода. Другими словами, компилятор генерирует почти такой же код, который вы написали бы вручную без использования родовых типов и приведений типов, после проверки вашей программы на независимость от типа. В отличие от С++ List<Integer> и List<String> являются одним и тем же классом (хотя и имеют различные типы, являющиеся подтипами List<?> — отличие более важное в JDK 5.0, чем в предыдущих версиях языка).
Одним из побочных эффектов механизма стирания является неспособность класса реализовать оба интерфейса Comparable<String> и Comparable<Number>, поскольку оба они на самом деле являются одним и тем же интерфейсом, указывая на один и тот же метод compareTo(). Может показаться более благоразумным объявить класс DecimalString, совместимый как со Strings, так и с Numbers, но с точки зрения компилятора Java вы пытались бы объявить один и тот же метод дважды:
public class DecimalString implements Comparable<Number>, Comparable<String> {… } // нет
Еще одним следствием механизма стирания является бессмысленность использования приведений типов и instanceof с параметрами родовых типов. В следующем фрагменте кода независимость от типа совершенно не улучшается:
public <T> T naiveCast(T t, Object o) { return (T) o; }
Компилятор просто выдаст не отмеченное предупреждение о преобразовании, поскольку не знает о том, является приведение типов безопасным или нет. Метод naiveCast() фактически не выполняет какого-либо приведения типов. T просто замещается его «стирателем» (Object), а переданный объект будет приведен в Object — не то что задумывалось.
Механизм стирания также ответственен за описанные выше проблемы создания – вы не можете создать объект родового типа, поскольку компилятор не знает, какой конструктор вызвать. Если ваш родовой класс требует создания объектов, чей тип указан параметрами родового типа, его конструкторы должны принимать литерал класса (Foo.class) и сохранять его, для того чтобы экземпляры могли быть созданы отображением.
Одним из побочных эффектов механизма стирания является неспособность класса реализовать оба интерфейса Comparable<String> и Comparable<Number>, поскольку оба они на самом деле являются одним и тем же интерфейсом, указывая на один и тот же метод compareTo(). Может показаться более благоразумным объявить класс DecimalString, совместимый как со Strings, так и с Numbers, но с точки зрения компилятора Java вы пытались бы объявить один и тот же метод дважды:
public class DecimalString implements Comparable<Number>, Comparable<String> {… } // нет
Еще одним следствием механизма стирания является бессмысленность использования приведений типов и instanceof с параметрами родовых типов. В следующем фрагменте кода независимость от типа совершенно не улучшается:
public <T> T naiveCast(T t, Object o) { return (T) o; }
Компилятор просто выдаст не отмеченное предупреждение о преобразовании, поскольку не знает о том, является приведение типов безопасным или нет. Метод naiveCast() фактически не выполняет какого-либо приведения типов. T просто замещается его «стирателем» (Object), а переданный объект будет приведен в Object — не то что задумывалось.
Механизм стирания также ответственен за описанные выше проблемы создания – вы не можете создать объект родового типа, поскольку компилятор не знает, какой конструктор вызвать. Если ваш родовой класс требует создания объектов, чей тип указан параметрами родового типа, его конструкторы должны принимать литерал класса (Foo.class) и сохранять его, для того чтобы экземпляры могли быть созданы отображением.
К чему весь этот текст? :)
Да, описанные проблемы имеют место быть. Но информацию о том, какую параметризацию имеет, например, List, выступающий в качестве типа поля класса, можно.
P.S.: А копирайт ставить надо бы:
Да, описанные проблемы имеют место быть. Но информацию о том, какую параметризацию имеет, например, List, выступающий в качестве типа поля класса, можно.
P.S.: А копирайт ставить надо бы:
если уж ставить копирайты… то хотя бы на ibm а не на javagu
www.ibm.com/developerworks/ru/library/j-jtp01255/index.html
www.ibm.com/developerworks/ru/library/j-jtp01255/index.html
Это наследование от type erasure (от BaseClass без параметров). А внутри type erasure не используются параметризованные типы.
java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.8
The type of a constructor (§8.8), instance method (§8.8, §9.4), or non-static field (§8.3) M of a raw type C that is not inherited from its superclasses or superinterfaces is the erasure of its type in the generic declaration corresponding to C.
The type of a constructor (§8.8), instance method (§8.8, §9.4), or non-static field (§8.3) M of a raw type C that is not inherited from its superclasses or superinterfaces is the erasure of its type in the generic declaration corresponding to C.
Правильно я понимаю, что Вы имеете в виду: если наследоваться без параметров, то компилятор «забывает» любую параметризацию внутри класса BaseClass, поэтому он считает что я пытаюсь реализовать метод test с типизированными параметрами, тогда как надо реализовывать просто test(Class clazz)?
Если так, то ситуация понятна, но это не нормальное поведение, т.к. нарушается обратная совместимость. Допустим у меня есть Collection<E> и MyArrayList<E> и внутри MyArrayList есть параметризованный абстрактный метод. А мой клиент отнаследовался от MyArrayList когда в нем еще ничего не было параметризовано. Получается в новой версии код клиента сломается, т.е. в механизме type erasure есть проблемы с обратной совместимостью… Или я что-то упускаю?
Если так, то ситуация понятна, но это не нормальное поведение, т.к. нарушается обратная совместимость. Допустим у меня есть Collection<E> и MyArrayList<E> и внутри MyArrayList есть параметризованный абстрактный метод. А мой клиент отнаследовался от MyArrayList когда в нем еще ничего не было параметризовано. Получается в новой версии код клиента сломается, т.е. в механизме type erasure есть проблемы с обратной совместимостью… Или я что-то упускаю?
Я понял подвох собственного вопроса. Если код наследника старый, то и метод не будет параметризован, а тогда все в порядке.
И все же, получается, если я обрезаю параметризацию у класса, то автоматом обрезается и параметризация внутри его методов? Это как-то неправильно, хотя и документированно.
И все же, получается, если я обрезаю параметризацию у класса, то автоматом обрезается и параметризация внутри его методов? Это как-то неправильно, хотя и документированно.
Я бы сказал так: все в порядке, но поведение компилятора в этой ситуации могло бы быть более умным. Ошибка не вполне соответствует проблеме, хотя почему она такая — теперь понятно.
Ну да, так и есть. По сути, тут наследование не от исходного BaseClass, а от другого, raw-типа со стертыми параметрами. В raw-классе параметры типов стираются еще и у его собственных методов, полей и конструкторов. Поэтому абстрактный метод в этом raw-типе выглядит как test(Class clazz), и реализовывать надо именно такой метод.
Насчет обратной совместимости — тоже правильно, эту упячку придумали для того, чтобы можно было параметризовывать старый код, не затрагивая клиентов этого кода.
Насчет обратной совместимости — тоже правильно, эту упячку придумали для того, чтобы можно было параметризовывать старый код, не затрагивая клиентов этого кода.
С.м. language specifications:
java.sun.com/docs/books/jls/third_edition/html/classes.html#8.4.2
и еще кое что про erasure:
www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ109
Все таки не состыковка выходит, может я что-то не до понял;)
java.sun.com/docs/books/jls/third_edition/html/classes.html#8.4.2
и еще кое что про erasure:
www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ109
Все таки не состыковка выходит, может я что-то не до понял;)
Вроде все нормально. В 8.4.2 приводится пример наследования от обыкновенного, не-raw-типа с generic-методом. В посте — наследование от raw-типа со стертыми сигнатурами всех методов.
Вдогонку к предыдущему комментарию:
(Notion of subsignature) allows a method whose signature does not use generic types to override any generified version of that method. А с MyClass получается наоборот — мы пытаемся generified-версией метода переопределить raw-версию.
(Notion of subsignature) allows a method whose signature does not use generic types to override any generified version of that method. А с MyClass получается наоборот — мы пытаемся generified-версией метода переопределить raw-версию.
Вы не правы, что это чисто академический интерес представляет. Я похоже пару раз пересекался с вариациями на это поведение в реальных проектах. Но никогда не углублялся в расследования.
Вспомнил, наконец, где я видел логин amima. Привет от екатеринбуржского я.офиса:)
Sign up to leave a comment.
Необычное поведение generics