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

Почему некоторые утверждают, что реализация дженериков Java плоха?

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

Извините мою неопытность, но что сделало бы их лучше?

4b9b3361

Ответ 1

Плохо:

  • Информация о типе теряется во время компиляции, поэтому во время выполнения вы не можете определить, какой тип "означал"
  • Нельзя использовать для типов значений (это biggie - в .NET a List<byte> действительно поддерживается byte[], например, и не требуется бокс)
  • Синтаксис для вызова общих методов отстой (IMO)
  • Синтаксис для ограничений может запутаться
  • Подстановочные знаки обычно запутываются
  • Различные ограничения из-за вышеприведенного литья и т.д.

Хорошо:

  • Подстановочные знаки позволяют указывать ковариацию/контравариантность на стороне вызова, что очень удобно во многих ситуациях.
  • Это лучше, чем ничего!

Ответ 2

Самая большая проблема заключается в том, что дженерики Java - это всего лишь компиляция, и вы можете подорвать ее во время выполнения. С# похвалил, потому что он больше проверяет время выполнения. В этом сообщении есть очень хорошее обсуждение, и оно связано с другими обсуждениями.

Ответ 3

  • Реализация времени выполнения (т.е. не стирание стилей);
  • Возможность использования примитивных типов (это связано с (1));
  • В то время как подстановочные знаки полезны, синтаксис и знание того, когда его использовать, - это то, что пень много людей. и
  • Отсутствие улучшения производительности (из-за (1); дженерики Java - синтаксический сахар для объектов castingi).

(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-дженерики лучше.

Ответ 4

Основная проблема заключается в том, что Java на самом деле не имеет генериков во время выполнения. Это функция времени компиляции.

Когда вы создаете общий класс в Java, они используют метод под названием "Тип Erasure", чтобы фактически удалить все общие типы из класса и по существу заменить их на Object. Миллионная версия дженериков заключается в том, что компилятор просто вставляет броски в указанный общий тип всякий раз, когда он появляется в теле метода.

У этого много недостатков. Один из самых больших ИМХО - это то, что вы не можете использовать рефлексию для проверки типичного типа. Типы не являются общими в байтовом коде и поэтому не могут быть проверены как обобщенные.

Отличный обзор различий здесь: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html

Ответ 5

Я собираюсь выбросить действительно спорное мнение. Дженерики усложняют язык и усложняют код. Например, допустим, что у меня есть карта, которая отображает строку в список строк. В старые времена я мог бы объявить это просто как

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

все крутилы исчезнут, и я действительно не думаю, что буду вводить в свой код большой новый источник ошибок.

Ответ 6

Другим побочным эффектом для них является время компиляции и не время запуска, так это то, что вы не можете вызвать конструктор родового типа. Поэтому вы не можете использовать их для реализации общего factory...


   public class MyClass {
     public T getStuff() {
       return new T();
     }
    }

- jeffk ++

Ответ 7

Игнорирование беспорядка стирания всего типа, как указано, только дженерики не работают.

Это компилируется:

List<Integer> x = Collections.emptyList();

Но это синтаксическая ошибка:

foo(Collections.emptyList());

Где foo определяется как:

void foo(List<Integer> x) { /* method body not important */ }

Таким образом, зависит ли проверка типа выражения от того, назначена ли она локальной переменной или фактическому параметру вызова метода. Как это безумие?

Ответ 8

Введение дженериков в Java было сложной задачей, поскольку архитекторы пытались сбалансировать функциональность, простоту использования и обратную совместимость с устаревшим кодом. Вполне ожидалось, что должны быть достигнуты компромиссы.

Есть некоторые, которые также считают, что реализация дженериков в Java повысила сложность языка до неприемлемого уровня (см. Кен Арнольд Общие понятия, вредные для здоровья "). Angelika Langer Часто задаваемые вопросы по обобщениям дает довольно хорошую идею о том, насколько сложными могут стать.

Ответ 9

Генерические средства 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");

Ответ 10

Java не применяет Generics во время выполнения, только во время компиляции.

Это означает, что вы можете делать интересные вещи, например, добавлять неправильные типы в общие коллекции.

Ответ 11

Мне жаль, что это была вики, поэтому я мог бы добавить к другим людям... но...

Проблемы:

  • Тип Erasure (отсутствие доступности во время выполнения)
  • Поддержка примитивных типов
  • Несовместимость с аннотациями (они оба были добавлены в 1.5, я все еще не уверен, почему аннотации не позволяют использовать дженерики, кроме того, что они бросаются в функции)
  • Несовместимость с массивами. (Иногда я действительно хочу делать что-то вроде класса <? Extends MyObject > [], но мне не разрешено)
  • Синтаксис и поведение подмножества Wierd
  • Тот факт, что общая поддержка несовместима с Java-классами. Они добавили его к большинству методов коллекций, но время от времени вы запускаете экземпляр, где его нет.

Ответ 12

Генераторы Java являются только компиляцией и скомпилированы в не общий код. В С# фактическая скомпилированная MSIL является общей. Это имеет огромное значение для производительности, поскольку Java все еще работает во время выполнения. Подробнее см. здесь.

Ответ 13

Если вы слушаете Java Posse # 279 - Интервью с Джо Дарси и Алексом Бакли, они говорят об этой проблеме. Это также ссылается на сообщение блога Neal Gafter под названием Reified Generics для Java, в котором говорится:

Многие люди недовольны ограничения, вызванные generics реализованы на Java. В частности, они недовольны тем, что параметры типового типа не reified: они недоступны в во время выполнения. Внедрены обобщения используя стирание, в котором общий тип параметры просто удаляются при во время выполнения.

Это сообщение в блоге, ссылается на более старую запись, Puzzling Through Erasure: раздел ответов, в котором подчеркивается важность совместимости с миграцией в требованиях.

Цель состояла в том, чтобы обеспечить назад совместимость как источника, так и объектный код, а также миграция совместимость.