Иногда я слышал, что с дженериками Java не справляется. (ближайшая ссылка здесь )
Простите за неопытность, но что могло бы сделать их лучше?
Иногда я слышал, что с дженериками Java не справляется. (ближайшая ссылка здесь )
Простите за неопытность, но что могло бы сделать их лучше?
Ответы:
Плохой:
List<byte>
действительно поддерживается byte[]
, и упаковка не требуется)Хорошо:
Самая большая проблема в том, что универсальные шаблоны Java используются только во время компиляции, и вы можете разрушить их во время выполнения. C # хвалят, потому что он больше проверяет во время выполнения. В этом посте есть действительно хорошее обсуждение , и оно связано с другими обсуждениями.
Class
объекты.
Основная проблема заключается в том, что Java фактически не имеет универсальных шаблонов во время выполнения. Это функция времени компиляции.
Когда вы создаете универсальный класс в Java, они используют метод под названием «Type Erasure», чтобы фактически удалить все общие типы из класса и по существу заменить их на Object. Версия универсальных шаблонов высотой в милю состоит в том, что компилятор просто вставляет приведение типов в указанный универсальный тип всякий раз, когда он появляется в теле метода.
У этого есть много минусов. ИМХО, одна из самых больших проблем заключается в том, что вы не можете использовать отражение для проверки универсального типа. Типы на самом деле не являются универсальными в байтовом коде и, следовательно, не могут рассматриваться как универсальные.
Отличный обзор различий здесь: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
(1) приводит к очень странному поведению. Лучший пример, который я могу придумать, - это. Предполагать:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
затем объявите две переменные:
MyClass<T> m1 = ...
MyClass m2 = ...
Теперь звоните getOtherStuff()
:
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
У второго аргумент универсального типа удален компилятором, потому что это необработанный тип (то есть параметризованный тип не предоставляется), хотя он не имеет ничего общего с параметризованным типом.
Я также упомяну мою любимую декларацию из JDK:
public class Enum<T extends Enum<T>>
Помимо подстановочных знаков (что является смешанным), я просто думаю, что дженерики .Net лучше.
public class Redundancy<R extends Redundancy<R>>
;)
The expression of type List needs unchecked conversion to conform to List<String>
Enum<T extends Enum<T>>
Сначала может показаться странным / избыточным, но на самом деле это довольно интересно, по крайней мере, в рамках ограничений Java / it generics. У перечислений есть статический values()
метод, дающий массив их элементов, типизированных как перечисление, а не Enum
, и этот тип определяется универсальным параметром, что означает, что вы хотите Enum<T>
. Конечно, такая типизация имеет смысл только в контексте перечислимого типа, и все перечисления являются подклассами Enum
, как вы хотите Enum<T extends Enum>
. Однако Java не любит смешивать необработанные типы с обобщенными, таким образом, Enum<T extends Enum<T>>
для согласованности.
Я собираюсь выбросить действительно спорное мнение. Обобщения усложняют язык и усложняют код. Например, предположим, что у меня есть карта, которая отображает строку в список строк. Раньше я мог объявить это просто как
Map someMap;
Теперь я должен объявить это как
Map<String, List<String>> someMap;
И каждый раз, когда я передаю его в какой-либо метод, мне приходится повторять это большое длинное объявление снова и снова. На мой взгляд, лишний набор текста отвлекает разработчика и выводит его из «зоны». Кроме того, когда код заполнен большим количеством мусора, иногда бывает трудно вернуться к нему позже и быстро просмотреть все ненужное, чтобы найти важную логику.
Java уже имеет плохую репутацию одного из самых многословных языков, широко используемых, и обобщения только усугубляют эту проблему.
И что вы действительно покупаете за такое многословие? Сколько раз у вас действительно были проблемы, когда кто-то помещал целое число в коллекцию, которая должна содержать строки, или когда кто-то пытался вытащить строку из коллекции целых чисел? За мой 10-летний опыт работы над созданием коммерческих Java-приложений это никогда не было большим источником ошибок. Итак, я не совсем уверен, что вы получаете за лишнее многословие. Это действительно просто кажется мне лишним бюрократическим багажом.
Теперь я собираюсь стать действительно спорным. Что я считаю самой большой проблемой коллекций в Java 1.4, так это необходимость везде приводить типы. Я рассматриваю эти приведенные типы как лишнюю, многословную ерунду, имеющую многие из тех же проблем, что и родовые типы. Так, например, я не могу просто сделать
List someList = someMap.get("some key");
я должен сделать
List someList = (List) someMap.get("some key");
Причина, конечно, в том, что get () возвращает объект, который является супертипом List. Таким образом, присвоение не может быть выполнено без приведения типа. Опять же, подумайте, сколько на самом деле вам выгодно это правило. По моему опыту, немного.
Я думаю, что Java была бы намного лучше, если бы 1) она не добавляла дженерики, а 2) вместо этого разрешала неявное приведение от супертипа к подтипу. Позвольте неправильным приведениям быть обнаруженными во время выполнения. Тогда я мог бы легко определить
Map someMap;
а позже делая
List someList = someMap.get("some key");
весь мусор исчезнет, и я действительно не думаю, что внесу в свой код новый большой источник ошибок.
Еще один побочный эффект от того, что они находятся во время компиляции, а не во время выполнения, заключается в том, что вы не можете вызвать конструктор универсального типа. Таким образом, вы не можете использовать их для реализации общей фабрики ...
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk ++
Дженерики Java проверяются на правильность во время компиляции, а затем вся информация о типах удаляется (процесс называется стиранием типа . Таким образом, родовой типList<Integer>
будет сокращен до своего необработанного типа , неуниверсального List
, который может содержать объекты произвольного класса.
Это приводит к возможности вставлять произвольные объекты в список во время выполнения, а также теперь невозможно определить, какие типы использовались в качестве общих параметров. Последнее, в свою очередь, приводит к
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
Я бы хотел, чтобы это была вики, чтобы я мог добавлять что-то другим ... но ...
Проблемы:
<
? Extends MyObject>
[], но мне это не разрешено)Игнорируя весь беспорядок стирания типов, указанные универсальные шаблоны просто не работают.
Это компилирует:
List<Integer> x = Collections.emptyList();
Но это синтаксическая ошибка:
foo(Collections.emptyList());
Где foo определяется как:
void foo(List<Integer> x) { /* method body not important */ }
Таким образом, проверка типа выражения зависит от того, присваивается ли он локальной переменной или фактическому параметру вызова метода. Насколько это безумно?
Внедрение универсальных шаблонов в Java было сложной задачей, поскольку архитекторы пытались сбалансировать функциональность, простоту использования и обратную совместимость с унаследованным кодом. Как и следовало ожидать, пришлось пойти на компромисс.
Некоторые также считают, что реализация универсальных шаблонов в Java повысила сложность языка до неприемлемого уровня (см. Статью Кена Арнольда « Обобщения, признанные вредными »). Часто задаваемые вопросы по обобщениям Анжелики Лангер дают довольно хорошее представление о том, насколько сложными могут быть вещи.
Java не применяет Generics во время выполнения, только во время компиляции.
Это означает, что вы можете делать интересные вещи, например добавлять неправильные типы в общие Коллекции.
Дженерики Java создаются только во время компиляции и компилируются в неуниверсальный код. В C # фактический скомпилированный MSIL является общим. Это имеет огромное значение для производительности, поскольку Java по-прежнему выполняет приведение типов во время выполнения. Подробнее см. Здесь .
Если вы послушаете Java Posse # 279 - Интервью с Джо Дарси и Алексом Бакли , они говорят об этой проблеме. Здесь также есть ссылка на сообщение в блоге Нила Гафтера под названием Reified Generics for Java, в котором говорится:
Многие люди недовольны ограничениями, вызванными тем, как дженерики реализованы в Java. В частности, они недовольны тем, что параметры универсального типа не уточняются: они недоступны во время выполнения. Универсальные типы реализуются с использованием стирания, при котором параметры универсального типа просто удаляются во время выполнения.
Это сообщение в блоге ссылается на более старую запись Puzzling Through Erasure: answer section , в которой подчеркивается пункт о совместимости миграции в требованиях.
Целью было обеспечить обратную совместимость как исходного, так и объектного кода, а также совместимость миграции.