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


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

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

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

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

EDIT: быстрое обновление для этого поскольку ответы, похоже, в основном касаются необходимости пройти в 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 101

13 ответов:

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

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 опущен для краткости)

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

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

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

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

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

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

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

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

"овеществленный" просто означает реальный и обычно просто означает противоположность стирания типа.

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

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

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

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

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

class ParametericException<T> extends Exception // compile error

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

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

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

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

сериализация была бы более простой с овеществлением. То, что мы хотели бы это

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

что нужно делать

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


например, это отлично работает в Java Дженерики (тривиальный пример):

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, этот принимает любой объект "Class". И это только крошечное расширение предыдущего кода:

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

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

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

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

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