Почему мне должно быть интересно, чтобы Java не обновляла дженерики?

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

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

Могут ли люди публиковать примеры того, что они действительно хотели бы сделать, были ли доступны генерируемые генерики? Я имею в виду, очевидно, что вы можете получить тип 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?

Ответ 1

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

public class Foo<T> {

    private T t;

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

}

Это работает в С#, предполагая, что 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 для краткости опущен)

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

Ответ 2

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

public void my_method(List<String> input) { ... }
public void my_method(List<Integer> input) { ... }

Ответ 3

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

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 о своих параметрах или нет).

Ответ 4

В коде вы сможете создавать общие массивы.

public <T> static void DoStuff() {
    T[] myArray = new T[42]; // No can do
}

Ответ 5

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

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

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

  • Это ужасное требование к боксу и разрыв между примитивами и ссылочными типами. Это не связано напрямую с reification или стиранием типа. С#/ Scala исправить это.
  • Нет типов. По этой причине JavaFX 8 пришлось удалить "строителей". Абсолютно ничего общего с стиранием стилей. Scala исправляет это, не уверен в С#.
  • Отсутствие дисперсии бокового типа декларации. С# 4.0/ Scala имеют это. Абсолютно ничего общего с стиранием стилей.
  • Невозможно перегрузить void method(List<A> l) и method(List<B> l). Это происходит из-за стирания типа, но очень мелкое.
  • Нет поддержки отображения типа времени выполнения. Это сердце стирания типа. Если вам нравятся супер-продвинутые компиляторы, которые проверяют и доказывают как можно больше логики вашей программы во время компиляции, вы должны использовать отражение как можно меньше, и этот тип стирания не должен вас беспокоить. Если вам нравится более пятнистое, scripty, динамическое программирование типа и не заботятся о компиляторе, доказывающем, насколько логично ваша логика, насколько это возможно, тогда вам нужно улучшить отражение и стирание типа стирания.

Ответ 6

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

Так как типы не поддаются повторной аутентификации, исключений Parameterized невозможно.

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

class ParametericException<T> extends Exception // compile error

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

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

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

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

Ответ 7

Сериализация будет более простой с овеществлением. Мы хотим, чтобы

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

Нам нужно

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

выглядит уродливо и работает funkily.

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

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

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

Ответ 8

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

Ответ 9

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

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

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

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

Ответ 10

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

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

Ответ 11

Не то, чтобы вы достигли чего-то экстраординарного. Это будет проще понять. Тип стирания кажется трудным для начинающих, и в конечном итоге это требует понимания того, как работает компилятор.

Мое мнение, что дженерики просто лишние, что экономит много избыточного кастинга.

Ответ 12

Вот тот, который поймал меня сегодня: без овеществления, если вы напишете метод, который принимает список универсальных элементов varargs... вызывающие могут ДУМАТЬ, что они являются типичными, но случайно проходят в любой старой crud и взорвали ваш метод.

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

(NB: Возможно, я допустил ошибку здесь, но googling вокруг "generics varargs", выше, похоже, именно то, что вы ожидаете. Дело в том, что это практическая проблема - использование класса, я думаю - звонящие кажутся менее осторожными:()


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

например. это отлично работает в Java Generics (тривиальный пример):

public <T extends Component> Set<UUID> getEntitiesPossessingComponent( Class<T> componentType)
    {
        // find the entities that are mapped (somehow) from that class. Very type-safe
    }

например. без переопределения в Java Generics, этот принимает ЛЮБОЙ объект класса. И это лишь небольшое расширение предыдущего кода:

public <T extends Component> Set<UUID> getEntitiesPossessingComponents( Class<T>... componentType )
    {
        // find the entities that are mapped (somehow) to ALL of those classes
    }

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

Ответ 13

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

    public interface Service<KEY,VALUE> {
           VALUE get(KEY key);
    }

    public class PersonService implements Service<Long, Person>,
        Service<String, Person> //Can not do!!