Я иногда слышал, что с помощью дженериков Java не справлялся с этим. (ближайшая ссылка, здесь)
Извините мою неопытность, но что сделало бы их лучше?
Я иногда слышал, что с помощью дженериков Java не справлялся с этим. (ближайшая ссылка, здесь)
Извините мою неопытность, но что сделало бы их лучше?
Плохо:
List<byte>
действительно поддерживается byte[]
, например, и не требуется бокс)Хорошо:
Самая большая проблема заключается в том, что дженерики Java - это всего лишь компиляция, и вы можете подорвать ее во время выполнения. С# похвалил, потому что он больше проверяет время выполнения. В этом сообщении есть очень хорошее обсуждение, и оно связано с другими обсуждениями.
(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-дженерики лучше.
Основная проблема заключается в том, что Java на самом деле не имеет генериков во время выполнения. Это функция времени компиляции.
Когда вы создаете общий класс в Java, они используют метод под названием "Тип Erasure", чтобы фактически удалить все общие типы из класса и по существу заменить их на Object. Миллионная версия дженериков заключается в том, что компилятор просто вставляет броски в указанный общий тип всякий раз, когда он появляется в теле метода.
У этого много недостатков. Один из самых больших ИМХО - это то, что вы не можете использовать рефлексию для проверки типичного типа. Типы не являются общими в байтовом коде и поэтому не могут быть проверены как обобщенные.
Отличный обзор различий здесь: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
Я собираюсь выбросить действительно спорное мнение. Дженерики усложняют язык и усложняют код. Например, допустим, что у меня есть карта, которая отображает строку в список строк. В старые времена я мог бы объявить это просто как
Map someMap;
Теперь я должен объявить его как
Map<String, List<String>> someMap;
И каждый раз, когда я передаю его в какой-то метод, я должен повторить это большое длинное выражение снова и снова. На мой взгляд, все лишнее написание отвлекает разработчика и выводит его из "зоны". Кроме того, когда код заполнен множеством трещин, иногда ему трудно вернуться к нему позже и быстро просеять всю трещину, чтобы найти важную логику.
Java уже имеет плохую репутацию за то, что она является одним из наиболее часто используемых языков, и дженерики просто добавляют к этой проблеме.
И что вы действительно купите за эту дополнительную многословие? Сколько раз у вас действительно были проблемы, когда кто-то помещал Integer в коллекцию, которая должна была содержать Strings, или где кто-то пытался вытащить String из коллекции целых чисел? За 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");
все крутилы исчезнут, и я действительно не думаю, что буду вводить в свой код большой новый источник ошибок.
Другим побочным эффектом для них является время компиляции и не время запуска, так это то, что вы не можете вызвать конструктор родового типа. Поэтому вы не можете использовать их для реализации общего factory...
public class MyClass {
public T getStuff() {
return new T();
}
}
- jeffk ++
Игнорирование беспорядка стирания всего типа, как указано, только дженерики не работают.
Это компилируется:
List<Integer> x = Collections.emptyList();
Но это синтаксическая ошибка:
foo(Collections.emptyList());
Где foo определяется как:
void foo(List<Integer> x) { /* method body not important */ }
Таким образом, зависит ли проверка типа выражения от того, назначена ли она локальной переменной или фактическому параметру вызова метода. Как это безумие?
Введение дженериков в Java было сложной задачей, поскольку архитекторы пытались сбалансировать функциональность, простоту использования и обратную совместимость с устаревшим кодом. Вполне ожидалось, что должны быть достигнуты компромиссы.
Есть некоторые, которые также считают, что реализация дженериков в Java повысила сложность языка до неприемлемого уровня (см. Кен Арнольд Общие понятия, вредные для здоровья "). Angelika Langer Часто задаваемые вопросы по обобщениям дает довольно хорошую идею о том, насколько сложными могут стать.
Генерические средства 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");
Java не применяет Generics во время выполнения, только во время компиляции.
Это означает, что вы можете делать интересные вещи, например, добавлять неправильные типы в общие коллекции.
Мне жаль, что это была вики, поэтому я мог бы добавить к другим людям... но...
Проблемы:
<
? Extends MyObject >
[], но мне не разрешено)Генераторы Java являются только компиляцией и скомпилированы в не общий код. В С# фактическая скомпилированная MSIL является общей. Это имеет огромное значение для производительности, поскольку Java все еще работает во время выполнения. Подробнее см. здесь.
Если вы слушаете Java Posse # 279 - Интервью с Джо Дарси и Алексом Бакли, они говорят об этой проблеме. Это также ссылается на сообщение блога Neal Gafter под названием Reified Generics для Java, в котором говорится:
Многие люди недовольны ограничения, вызванные generics реализованы на Java. В частности, они недовольны тем, что параметры типового типа не reified: они недоступны в во время выполнения. Внедрены обобщения используя стирание, в котором общий тип параметры просто удаляются при во время выполнения.
Это сообщение в блоге, ссылается на более старую запись, Puzzling Through Erasure: раздел ответов, в котором подчеркивается важность совместимости с миграцией в требованиях.
Цель состояла в том, чтобы обеспечить назад совместимость как источника, так и объектный код, а также миграция совместимость.