Это возникло в качестве вопроса, который я недавно задал в интервью, поскольку кандидат хотел бы видеть добавление к языку Java. Обычно болью называют то, что в Java нет обобщенных дженериков, но, когда меня подталкивали, кандидат не мог сказать мне, чего бы он мог достичь, если бы они были там.
Очевидно, что из-за того, что в Java допустимы необработанные типы (и небезопасные проверки), можно разрушить обобщенные типы и получить в результате a, List<Integer>
который (например) фактически содержит String
s. Очевидно, что это можно было бы сделать невозможным, если бы информация о типе была переработана; но должно быть нечто большее !
Могут ли люди публиковать примеры того, что они действительно хотели бы делать , если бы были доступны обобщенные дженерики? Я имею в виду, очевидно, что вы могли получить тип a List
во время выполнения - но что бы вы с ним сделали?
public <T> void foo(List<T> l) {
if (l.getGenericType() == Integer.class) {
//yeah baby! err, what now?
РЕДАКТИРОВАТЬ : быстрое обновление этого, поскольку ответы, похоже, в основном связаны с необходимостью передать a Class
в качестве параметра (например EnumSet.noneOf(TimeUnit.class)
). Я искал что-то вроде того, где это просто невозможно . Например:
List<?> l1 = api.gimmeAList();
List<?> l2 = api.gimmeAnotherList();
if (l1.getGenericType().isAssignableFrom(l2.getGenericType())) {
l1.addAll(l2); //why on earth would I be doing this anyway?
источник
findClass()
что пришлось бы игнорировать параметризацию, ноdefineClass()
не смог). И, как мы знаем, для The Powers That Be обратная совместимость имеет первостепенное значение.Ответы:
Из тех нескольких раз, когда я сталкивался с этой «необходимостью», в конечном итоге она сводится к следующей конструкции:
Это работает в C # при условии, что у
T
него есть конструктор по умолчанию . Вы даже можете получить тип среды выполненияtypeof(T)
и получить конструкторыType.GetConstructor()
.Распространенным решением Java было бы передать
Class<T>
аргумент as.(его необязательно передавать в качестве аргумента конструктора, поскольку аргумент метода также подходит, приведенное выше является лишь примером, также
try-catch
опущено для краткости)Для всех других конструкций универсального типа фактический тип может быть легко определен с небольшой помощью отражения. Приведенные ниже вопросы и ответы иллюстрируют варианты использования и возможности:
источник
T.class
илиT.getClass()
, чтобы вы могли получить доступ ко всем его полям, конструкторам и методам. Это также делает невозможным строительство.Что меня чаще всего раздражает, так это невозможность использовать преимущества множественной диспетчеризации для нескольких универсальных типов. Следующее невозможно, и во многих случаях это было бы лучшим решением:
источник
public void my_method(List input) {}
. Однако я никогда не сталкивался с этой потребностью просто потому, что у них не было того же имени. Если у них то же имя, я бы сомневался,public <T extends Object> void my_method(List<T> input) {}
не лучшая ли идея.myStringsMethod(List<String> input)
иmyIntegersMethod(List<Integer> input)
даже если бы перегрузка для такого случая была возможна в Java.<algorithm>
C ++.На ум приходит безопасность типов . Преобразование в параметризованный тип всегда будет небезопасным без обобщенных дженериков:
Кроме того, абстракции будут меньше просачиваться - по крайней мере, те, которые могут быть заинтересованы в информации времени выполнения об их параметрах типа. Сегодня, если вам нужна какая-либо информация времени выполнения о типе одного из общих параметров, вы также должны передать его
Class
. Таким образом, ваш внешний интерфейс зависит от вашей реализации (независимо от того, используете ли вы RTTI для своих параметров или нет).источник
ParametrizedList
который копирует данные в типах проверки исходной коллекции. Это немного похоже,Collections.checkedList
но для начала можно засеять коллекцией.T.class.getAnnotation(MyAnnotation.class)
(гдеT
- универсальный тип), не изменяя внешний интерфейс.Вы сможете создавать в своем коде универсальные массивы.
источник
String[] strings = new String[1]; Object[] objects = strings; objects[0] = new Object();
Прекрасно компилируется на обоих языках. Работает notsofine.Это старый вопрос, на него есть масса ответов, но я думаю, что существующие ответы не соответствуют действительности.
«овеществленный» просто означает настоящий и обычно означает противоположность стирания шрифта.
Большая проблема, связанная с Java Generics:
void method(List<A> l)
иmethod(List<B> l)
. Это происходит из-за стирания шрифта, но это очень мелочь.источник
deserialize(thingy, List<Integer>.class)
С овеществлением сериализация была бы более простой. Мы бы хотели
Что нам нужно сделать
выглядит некрасиво и прикольно работает.
Бывают также случаи, когда было бы действительно полезно сказать что-то вроде
Эти штуки кусаются не часто, но кусаются, когда случаются.
источник
Мое знакомство с Java Geneircs довольно ограничено, и помимо пунктов, которые уже упоминались в других ответах, есть сценарий, объясненный в книге Java Generics and Collections Морисом Нафталином и Филипом Уолдером, где овеществленные дженерики полезны.
Поскольку типы не реифицируемы, невозможно иметь параметризованные исключения.
Например, декларация формы ниже недействительна.
Это связано с тем, что предложение catch проверяет, соответствует ли выброшенное исключение заданному типу. Эта проверка аналогична проверке, выполняемой тестом экземпляра, и, поскольку тип не подлежит повторной проверке, указанная выше форма оператора недействительна.
Если бы приведенный выше код был действителен, тогда была бы возможна обработка исключений следующим образом:
В книге также упоминается, что если общие шаблоны Java определены аналогично тому, как определены шаблоны C ++ (расширение), это может привести к более эффективной реализации, поскольку это дает больше возможностей для оптимизации. Но не предлагает никаких объяснений, кроме этого, поэтому любые объяснения (указатели) от знающих людей были бы полезны.
источник
Массивы, вероятно, были бы намного лучше с дженериками, если бы они были преобразованы.
источник
List<String>
не являетсяList<Object>
.У меня есть оболочка, которая представляет набор результатов jdbc в качестве итератора (это означает, что я могу намного проще выполнять модульное тестирование операций, связанных с базой данных, с помощью внедрения зависимостей).
API выглядит как
Iterator<T>
T - это некоторый тип, который может быть построен с использованием только строк в конструкторе. Затем Iterator просматривает строки, возвращаемые из запроса sql, и затем пытается сопоставить их с конструктором типа T.В текущем способе реализации дженериков я также должен передать класс объектов, которые я буду создавать из своего набора результатов. Если я правильно понимаю, если бы дженерики были реифицируются, я мог бы просто вызвать T.getClass (), чтобы получить его конструкторы, и тогда не пришлось бы приводить результат Class.newInstance (), что было бы намного аккуратнее.
В принципе, я думаю, что это упрощает написание API (в отличие от простого написания приложения), потому что вы можете сделать гораздо больше из объектов, и, следовательно, потребуется меньшая конфигурация ... Я не оценил значение аннотаций, пока я не видел, как они используются в таких вещах, как spring или xstream, а не в кучах config.
источник
Приятно было бы избежать бокса для примитивных (значений) типов. Это в некоторой степени связано с жалобой на массивы, которую подняли другие, и в случаях, когда использование памяти ограничено, это может действительно иметь существенное значение.
Также существует несколько типов проблем при написании фреймворка, когда важна способность отражать параметризованный тип. Конечно, это можно обойти, передав объект класса во время выполнения, но это скрывает API и накладывает дополнительную нагрузку на пользователя фреймворка.
источник
new T[]
там, где T имеет примитивный тип!Это не значит, что вы добьетесь чего-то экстраординарного. Просто будет проще понять. Стирание типов кажется трудным для новичков, и в конечном итоге требует понимания того, как работает компилятор.
Я считаю, что дженерики - это просто дополнение, которое избавляет от лишнего приведения типов.
источник
То, что упускают из виду все ответы здесь, что постоянно является для меня головной болью, заключается в том, что поскольку типы стираются, вы не можете дважды наследовать общий интерфейс. Это может быть проблемой, если вы хотите создавать мелкозернистые интерфейсы.
источник
Вот один, который меня поймал сегодня: без реификации, если вы напишете метод, который принимает список универсальных элементов varargs ... вызывающие могут ДУМАТЬ, что они безопасны по типу, но случайно передают любую старую грязь и взорвут ваш метод.
Кажется маловероятным, что это произойдет? ... Конечно, пока ... вы не используете Class в качестве типа данных. На этом этапе ваш вызывающий с радостью отправит вам множество объектов Class, но простая опечатка отправит вам объекты Class, которые не соответствуют T, и произойдет катастрофа.
(NB: я, возможно, ошибся здесь, но поискав в Google "generics varargs", вышеприведенное, похоже, именно то, что вы ожидаете. То, что делает это практической проблемой, - это использование Class, я думаю - вызывающие абоненты кажутся быть менее осторожным :()
Например, я использую парадигму, в которой объекты класса используются в качестве ключа на картах (это сложнее, чем простая карта, но концептуально именно это и происходит).
например, это отлично работает в Java Generics (тривиальный пример):
например, без повторения в Java Generics, этот принимает ЛЮБОЙ объект «Класс». И это лишь крошечное расширение предыдущего кода:
Вышеупомянутые методы должны быть записаны тысячи раз в отдельном проекте, поэтому вероятность человеческой ошибки становится высокой. Отладка ошибок оказывается "неинтересной". Я сейчас пытаюсь найти альтернативу, но не питаю особых надежд.
источник