Почему некоторые утверждают, что реализация дженериков в Java плохая? - PullRequest
109 голосов
/ 06 февраля 2009

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

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

Ответы [ 13 ]

139 голосов
/ 06 февраля 2009

Bad:

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

Хорошо:

  • Подстановочные знаки позволяют указывать ковариацию / контравариантность на вызывающей стороне, что во многих ситуациях очень аккуратно
  • Это лучше, чем ничего!
26 голосов
/ 06 февраля 2009

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

15 голосов
/ 06 февраля 2009

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

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

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

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

14 голосов
/ 06 февраля 2009
  1. Реализация времени выполнения (т.е. не стирание типа);
  2. Возможность использовать примитивные типы (это относится к (1));
  3. Хотя подстановочные знаки полезны, синтаксис и знание того, когда его использовать, - это то, что ставит в тупик многих людей. и
  4. Нет улучшения производительности (из-за (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(); 

Второй имеет свой аргумент универсального типа, удаленный компилятором, потому что это необработанный тип (что означает, что параметризованный тип не предоставляется), даже если он не имеет ничего для параметризованного типа. 1023 *

Я также упомяну мое любимое заявление от JDK:

public class Enum<T extends Enum<T>>

Помимо подстановочных знаков (которые представляют собой смешанный пакет), я просто думаю, что дженерики .Net лучше.

9 голосов
/ 07 февраля 2009

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

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 () возвращает Object, который является супертипом List. Таким образом, назначение не может быть выполнено без приведения типов. Опять же, подумайте о том, сколько это правило действительно покупает у вас. По моему опыту, не очень.

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

Map someMap;

и позже, делая

List someList = someMap.get("some key");

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

7 голосов
/ 07 февраля 2009

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


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

- jeffk ++

6 голосов
/ 06 февраля 2009

Обобщения 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");
5 голосов
/ 01 июня 2009

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

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

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

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

foo(Collections.emptyList());

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

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

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

4 голосов
/ 07 февраля 2009

Хотелось бы, чтобы это была вики, чтобы я мог добавить других людей ... но ...

Проблемы:

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

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

Некоторые считают, что реализация обобщений в Java повысила сложность языка до неприемлемого уровня (см. «1003 * Generics посчитал вредным» ) Кена Арнольда. Анжелика Лангер: часто задаваемые вопросы по общим вопросам дает довольно хорошее представление о том, насколько сложными могут стать вещи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...