Почему меня должно волновать, что в Java нет усовершенствованных обобщений? - PullRequest
101 голосов
/ 18 декабря 2009

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

Очевидно, что необработанные типы допустимы в Java (и небезопасные проверки), возможно подорвать дженерики и в итоге получить List<Integer>, который (например) фактически содержит String с. Это явно можно было бы сделать невозможным, если бы информация о типах была усовершенствована; но должно быть и больше !

Могут ли люди опубликовать примеры вещей, которые они действительно хотели бы сделать , были ли доступны дженерики? Я имею в виду, очевидно, вы могли бы получить тип List во время выполнения - но что бы вы сделали с ним?

public <T> void foo(List<T> l) {
   if (l.getGenericType() == Integer.class) {
       //yeah baby! err, what now?

РЕДАКТИРОВАТЬ : Быстрое обновление этого, поскольку ответы, по-видимому, в основном касаются необходимости передачи 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?

Ответы [ 13 ]

99 голосов
/ 18 декабря 2009

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

public void my_method(List<String> input) { ... }
public void my_method(List<Integer> input) { ... }
81 голосов
/ 18 декабря 2009

Из тех немногих раз, когда я сталкивался с этой «потребностью», она в конечном итоге сводится к этой конструкции:

public class Foo<T> {

    private T t;

    public Foo() {
        this.t = new T(); // Help?
    }

}

Это работает в C #, предполагая, что T имеет конструктор по умолчанию . Вы даже можете получить тип времени выполнения по typeof(T) и получить конструкторы по Type.GetConstructor().

Распространенным решением Java было бы передать Class<T> в качестве аргумента.

public class Foo<T> {

    private T t;

    public Foo(Class<T> cls) throws Exception {
        this.t = cls.newInstance();
    }

}

(его необязательно передавать в качестве аргумента конструктора, так как аргумент метода тоже подойдет, приведенный выше пример только для примера, try-catch для краткости опущен)

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

34 голосов
/ 18 декабря 2009

Тип безопасности приходит на ум. Понижение в параметризованный тип всегда будет небезопасным без обобщенных обобщений:

List<String> myFriends = new ArrayList();
myFriends.add("Alice");
getSession().put("friends", myFriends);
// later, elsewhere
List<Friend> myFriends = (List<Friend>) getSession().get("friends");
myFriends.add(new Friend("Bob")); // works like a charm!
// and so...
List<String> myFriends = (List<String>) getSession().get("friends");
for (String friend : myFriends) print(friend); // ClassCastException, wtf!? 

Кроме того, абстракции будут пропускать меньше - по крайней мере те, которые могут быть заинтересованы в информации времени выполнения об их параметрах типа. Сегодня, если вам нужна какая-либо информация времени выполнения о типе одного из общих параметров, вы также должны передать его Class. Таким образом, ваш внешний интерфейс зависит от вашей реализации (используете ли вы RTTI для своих параметров или нет).

26 голосов
/ 18 декабря 2009

Вы сможете создавать универсальные массивы в своем коде.

public <T> static void DoStuff() {
    T[] myArray = new T[42]; // No can do
}
16 голосов
/ 26 марта 2014

Это старый вопрос, существует множество ответов, но я думаю, что существующие ответы не соответствуют действительности.

«reified» просто означает реальное и обычно означает противоположность стиранию типа.

Большая проблема, связанная с Java Generics:

  • Это ужасное требование бокса и разъединение между примитивами и ссылочными типами. Это не имеет прямого отношения к реификации или стиранию типов. C # / Scala это исправить.
  • Нет типов сам. По этой причине JavaFX 8 пришлось убрать «строителям». Абсолютно никакого отношения к стиранию типов. Scala исправляет это, не уверенный в C #.
  • Нет объявления типа дисперсии стороны. C # 4.0 / Scala есть это. Абсолютно никакого отношения к стиранию типов.
  • Невозможно перегрузить void method(List<A> l) и method(List<B> l). Это связано с стиранием типа, но очень мелкое.
  • Нет поддержки для отражения типа во время выполнения. Это сердце типа стирания. Если вам нравятся супер продвинутые компиляторы, которые проверяют и доказывают как можно больше логики вашей программы во время компиляции, вы должны использовать отражение как можно меньше, и этот тип стирания типов не должен вас беспокоить. Если вам нравится более нестабильное, нестандартное программирование и динамическое программирование типов, и вам не нужно заботиться о том, чтобы компилятор доказывал максимально возможную правильность вашей логики, тогда вам нужно лучшее отражение и исправление стирания типов.
12 голосов
/ 07 августа 2014

Сериализация будет проще с реификацией. Что бы мы хотели, это

deserialize(thingy, List<Integer>.class);

Что нам нужно сделать, это

deserialize(thing, new TypeReference<List<Integer>>(){});

выглядит некрасиво и работает весело.

Есть также случаи, когда было бы очень полезно сказать что-то вроде

public <T> void doThings(List<T> thingy) {
    if (T instanceof Q)
      doCrazyness();
  }

Эти вещи не часто кусаются, но они кусаются, когда случаются.

11 голосов
/ 18 декабря 2009

Мое знакомство с Java Geneircs довольно ограничено, и кроме вопросов, уже упомянутых в других ответах, есть сценарий, описанный в книге Generics и коллекции Java Мориса Нафталина и Филипп Уолдер, где полезные дженерики полезны.

Поскольку типы не подлежат повторному определению, невозможно иметь параметризованные исключения.

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

class ParametericException<T> extends Exception // compile error

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

Если приведенный выше код действителен, то обработка исключений следующим образом была бы возможна:

try {
     throw new ParametericException<Integer>(42);
} catch (ParametericException<Integer> e) { // compile error
  ...
}

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

8 голосов
/ 18 декабря 2009

Массивы, вероятно, будут намного лучше с дженериками, если они будут овеществлены.

5 голосов
/ 18 декабря 2009

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

Существует также несколько типов проблем при написании каркаса, где важно иметь возможность размышлять над параметризованным типом. Конечно, это можно обойти, передав объект класса во время выполнения, но это затеняет API и накладывает дополнительную нагрузку на пользователя платформы.

5 голосов
/ 18 декабря 2009

У меня есть оболочка, которая представляет набор результатов jdbc в качестве итератора (это означает, что я могу гораздо проще выполнить модульное тестирование операций, создаваемых базой данных, путем внедрения зависимостей).

API выглядит как Iterator<T>, где T - это некоторый тип, который может быть создан с использованием только строк в конструкторе. Затем Iterator просматривает строки, возвращаемые из запроса sql, и затем пытается сопоставить его с конструктором типа T.

В текущем способе реализации обобщений я также должен передать класс объектов, которые я буду создавать из моего набора результатов. Если я правильно понимаю, если бы дженерики были усовершенствованы, я мог бы просто вызвать T.getClass (), чтобы получить его конструкторы, и тогда не пришлось бы приводить результат Class.newInstance (), который был бы намного лучше.

В принципе, я думаю, что это облегчает написание API (в отличие от простого написания приложения), потому что вы можете вывести гораздо больше из объектов, и, следовательно, потребуется меньше настроек ... Я не оценил последствия аннотации, пока я не увидел, что они используются в таких вещах, как Spring или Xstream вместо групп конфигурации.

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