Чем хороши дженерики, зачем их использовать?

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


person Community    schedule 16.09.2008    source источник
comment
Дубликат http://stackoverflow.com/questions/77632/what-is-cool-about-generics-why-use-them. Но простой ответ: даже если коллекции не являются частью вашего API, мне не нравится ненужное приведение типов даже во внутренней реализации.   -  person Matthew Flaschen    schedule 05.06.2009
comment
Вопрос очень похож, но я не думаю, что принятый ответ отвечает на него.   -  person Tom Hawtin - tackline    schedule 05.06.2009
comment
Также проверьте stackoverflow.com/questions/520527   -  person Clint Miller    schedule 05.06.2009
comment
@MatthewFlaschen странно, ваш дубликат наводит на этот вопрос ... глюк в матрице!   -  person jcollum    schedule 17.03.2012
comment
@jcollum, я думаю, что исходный вопрос, который я опубликовал в этом комментарии, был объединен с этим вопросом.   -  person Matthew Flaschen    schedule 17.03.2012
comment
Как упоминал @Ijs - write code which is applicable to many types with the same underlying behavior   -  person LCJ    schedule 18.12.2013
comment
Ссылка на дженерики: blogs.msdn.com/kirillosenkov/archive/2008/08/19/.   -  person MrBoJangles    schedule 10.03.2014


Ответы (29)


  • Позволяет писать код / ​​использовать методы библиотеки, которые являются типобезопасными, т.е. List ‹string› гарантированно будет списком строк.
  • В результате использования дженериков компилятор может выполнять проверки кода во время компиляции на предмет безопасности типов, т.е. пытаетесь ли вы поместить int в этот список строк? Использование ArrayList приведет к тому, что это будет менее прозрачная ошибка времени выполнения.
  • Быстрее, чем использование объектов, так как он либо позволяет избежать упаковки / распаковки (где .net должен преобразовать типы значений в ссылочные типы или наоборот) или приведение объектов к требуемому ссылочному типу.
  • Позволяет писать код, который применим ко многим типам с одинаковым базовым поведением, например, строка Dictionary ‹, int› использует тот же базовый код, что и Dictionary ‹DateTime, double›; Используя дженерики, команде фреймворка нужно было написать только один фрагмент кода для достижения обоих результатов с вышеупомянутыми преимуществами.
person ljs    schedule 16.09.2008
comment
Ах, очень мило, и мне нравятся списки. Я думаю, что это наиболее полный, но лаконичный ответ на данный момент. - person MrBoJangles; 17.09.2008
comment
все объяснение находится в контексте коллекций. Я ищу более широкий взгляд. Есть предположения? - person jungle_mole; 13.11.2015
comment
хороший ответ, я просто хочу добавить 2 других преимущества, общее ограничение имеет 2 преимущества помимо 1. Вы можете использовать свойства ограниченного типа в универсальном типе (например, где T: IComparable обеспечивает использование CompareTo) 2- Человек, который напишет код после того, как вы узнаете, что делать - person Hamit YILDIRIM; 17.05.2019

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

Вместо создания:

class MyObjectList  {
   MyObject get(int index) {...}
}
class MyOtherObjectList  {
   MyOtherObject get(int index) {...}
}
class AnotherObjectList  {
   AnotherObject get(int index) {...}
}

Я могу создать один многоразовый класс ... (в случае, если вы по какой-то причине не хотите использовать необработанную коллекцию)

class MyList<T> {
   T get(int index) { ... }
}

Теперь я в 3 раза эффективнее, и мне нужно поддерживать только одну копию. Почему вы НЕ ХОТИТЕ поддерживать меньше кода?

Это также верно для классов, не являющихся коллекциями, таких как Callable<T> или Reference<T>, которые должны взаимодействовать с другими классами. Вы действительно хотите расширить Callable<T> и Future<T> и все другие связанные классы для создания типобезопасных версий?

Я не.

person James Schek    schedule 04.06.2009
comment
Я не уверен, что понимаю. Я не предлагаю вам создавать оболочки MyObject, это было бы ужасно. Я предлагаю вам написать объект Garage, если ваша коллекция объектов представляет собой коллекцию автомобилей. У него не было бы get (car), у него были бы такие методы, как buyFuel (100), которые покупали бы топливо на 100 долларов и распределяли его между машинами, которые в нем больше всего нуждаются. Я говорю о настоящих бизнес-классах, а не только обертках. Например, вы почти никогда не должны получать (автомобиль) или перебирать их за пределами коллекции - вы не запрашиваете у объекта его членов, вместо этого вы просите его выполнить операцию за вас. - person Bill K; 05.06.2009
comment
Даже в вашем гараже у вас должна быть безопасность типов. Так что либо у вас есть List ‹Cars›, ListOfCars, либо вы транслируете везде. Я не чувствую необходимости указывать тип чаще, чем необходимо. - person James Schek; 05.06.2009
comment
Я согласен, что безопасность типов была бы хороша, но она гораздо менее полезна, поскольку ограничивается гаражным классом. Тогда он инкапсулируется и легко контролируется, поэтому я хочу сказать, что он дает вам это небольшое преимущество за счет нового синтаксиса. Это все равно что изменить конституцию США, сделав проезд на красный свет незаконным - да, это всегда должно быть незаконным, но оправдывает ли это поправку? Я также думаю, что это побуждает людей НЕ использовать инкапсуляцию в этом случае, что на самом деле сравнительно вредно. - person Bill K; 05.06.2009
comment
Билл - у вас может быть точка зрения об отказе от инкапсуляции, но остальная часть ваших аргументов - плохое сравнение. Стоимость изучения нового синтаксиса в этом случае минимальна (сравните это с лямбдами в C #); сравнивать это с поправкой к Конституции бессмысленно. Конституция не имеет намерения определять код трафика. Это больше похоже на переход на печатную версию шрифта с засечками на плакатной доске, а не на рукописную копию на пергаменте. Это то же значение, но оно намного яснее и легче для чтения. - person James Schek; 15.06.2009
comment
Этот пример делает концепцию более практичной. Спасибо за это. - person RayLoveless; 23.05.2018
comment
любя первое предложение. В самом деле, мне очень нравится, как вы начинаете свой ответ. На самом деле, мне очень понравилось ваше представление - person joel; 26.05.2020

Отсутствие необходимости в приведении типов - одно из самых больших преимуществ дженериков Java, так как проверка типов выполняется во время компиляции. Это уменьшит вероятность появления ClassCastExceptions во время выполнения и может привести к более надежному коду.

Но я подозреваю, что вы это прекрасно знаете.

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

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

Современные IDE упрощают написание кода с помощью универсальных шаблонов.

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

Вот пример создания Map<String, Integer> с HashMap. Мне нужно было ввести следующий код:

Map<String, Integer> m = new HashMap<String, Integer>();

И действительно, это очень много для того, чтобы создать новый HashMap. Однако на самом деле мне нужно было набрать столько, прежде чем Eclipse узнал, что мне нужно:

Map<String, Integer> m = new Ha Ctrl + Пробел

Да, мне действительно нужно было выбрать HashMap из списка кандидатов, но в основном IDE знала, что добавить, включая общие типы. С правильными инструментами использование дженериков не так уж и плохо.

Кроме того, поскольку типы известны, при извлечении элементов из общей коллекции среда IDE будет действовать так, как будто этот объект уже является объектом своего объявленного типа - нет необходимости выполнять приведение для среды IDE, чтобы узнать, какой тип объекта. является.

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

Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);

int total = 0;
for (int i : set) {
  total += i;
}

В этом фрагменте кода присутствуют три новые функции Java 5:

Во-первых, дженерики и автобокс примитивов позволяют использовать следующие строки:

set.add(10);
set.add(42);

Целое число 10 автоматически преобразуется в Integer со значением 10. (И то же самое для 42). Затем этот Integer бросается в Set, который, как известно, содержит Integer. Попытка добавить String вызовет ошибку компиляции.

Далее, for-each цикл принимает все три из них:

for (int i : set) {
  total += i;
}

Во-первых, Set, содержащие Integer, используются в цикле for-each. Каждый элемент объявлен как int, и это разрешено, поскольку Integer распаковывается обратно в примитив int. И тот факт, что происходит распаковка, известен, потому что для указания того, что в Set было Integer, использовались общие.

Обобщения могут быть связующим звеном, которое объединяет новые функции, представленные в Java 5, и просто делает кодирование более простым и безопасным. И в большинстве случаев IDE достаточно умны, чтобы помочь вам с хорошими предложениями, так что, как правило, вам не нужно намного больше печатать.

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

Изменить - пример без универсальных шаблонов

Ниже приведен пример Set выше без использования универсальных типов. Возможно, но не совсем приятно:

Set set = new HashSet();
set.add(10);
set.add(42);

int total = 0;
for (Object o : set) {
  total += (Integer)o;
}

(Примечание. Приведенный выше код генерирует предупреждение о непроверенном преобразовании во время компиляции.)

При использовании неуниверсальных коллекций типы, которые вводятся в коллекцию, являются объектами типа Object. Следовательно, в этом примере Object - это то, что add добавляется в набор.

set.add(10);
set.add(42);

В приведенных выше строках задействовано автобоксирование - примитивные int значения 10 и 42 автоматически упаковываются в Integer объекты, которые добавляются к Set. Однако имейте в виду, что объекты Integer обрабатываются как Object, поскольку нет информации о типе, которая могла бы помочь компилятору узнать, какой тип должен ожидать Set.

for (Object o : set) {

Это самая важная часть. Причина, по которой цикл for-each работает, заключается в том, что Set реализует Iterable, который возвращает Iterator с информацией о типе, если таковая имеется. (Iterator<T>, то есть.)

Однако, поскольку информации о типе нет, Set вернет Iterator, который вернет значения в Set как Objects, и поэтому элемент, извлекаемый в цикле for-each должен иметь типа Object.

Теперь, когда Object извлечен из Set, его необходимо вручную преобразовать в Integer, чтобы выполнить добавление:

  total += (Integer)o;

Здесь приведение типа выполняется от Object к Integer. В этом случае мы знаем, что это всегда будет работать, но ручное приведение типов всегда заставляет меня думать, что это хрупкий код, который может быть поврежден, если незначительное изменение будет сделано в другом месте. (Я чувствую, что каждое приведение типов - это ClassCastException, ожидающее своего появления, но я отвлекаюсь ...)

Integer теперь распакован в int, и ему разрешено выполнять добавление в int переменную total.

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

person coobird    schedule 05.06.2009
comment
Интеграция с расширенным циклом for действительно хороша (хотя я думаю, что этот проклятый цикл сломан, потому что я не могу использовать тот же синтаксис с необщей коллекцией - он должен просто использовать cast / autobox для int, у него есть вся необходимая информация!), но вы правы, помощь в IDE, наверное, хороша. Я до сих пор не знаю, достаточно ли этого, чтобы оправдать дополнительный синтаксис, но, по крайней мере, я могу извлечь выгоду из этого (что отвечает на мой вопрос). - person Bill K; 05.06.2009
comment
@Bill K: Я добавил пример использования функций Java 5 без использования дженериков. - person coobird; 05.06.2009
comment
Это хороший момент, я не использовал это (я уже упоминал, что застрял на 1.4 и более ранних версиях). Я согласен с тем, что есть преимущества, но выводы, к которым я пришел, таковы: A) они имеют тенденцию быть весьма выгодными для людей, которые неправильно используют объектно-ориентированный объект, и менее полезны для тех, кто это делает, и B) преимущества для вас. Перечисленные преимущества являются достоинствами, но вряд ли их достаточно, чтобы оправдать даже небольшое изменение синтаксиса, не говоря уже о крупных изменениях, необходимых для того, чтобы действительно разобраться в дженериках, реализованных в Java. Но, по крайней мере, преимущества есть. - person Bill K; 05.06.2009

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

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

Хранение коллекций объекта в себе - неплохой стиль (но тогда общий стиль состоит в том, чтобы эффективно игнорировать инкапсуляцию). Это скорее зависит от того, что вы делаете. Передачу коллекций в «алгоритмы» немного легче проверить (во время компиляции или до нее) с помощью универсальных шаблонов.

person Tom Hawtin - tackline    schedule 04.06.2009
comment
Это не только задокументировано (что само по себе является огромной победой), но и обеспечивается компилятором. - person James Schek; 05.06.2009
comment
Хороший момент, но разве это не достигается путем включения коллекции в класс, который определяет коллекцию этого типа объекта? - person Bill K; 05.06.2009
comment
Джеймс: Да, в том-то и дело, что это задокументировано в коде. - person Tom Hawtin - tackline; 05.06.2009
comment
Я не думаю, что знаю хорошие библиотеки, которые используют коллекции для алгоритмов (возможно, подразумевая, что вы говорите о функциях, что наводит меня на мысль, что они должны быть методом в объекте коллекции). Я думаю, что JDK почти всегда извлекает хороший чистый массив, который является копией данных, так что с ними нельзя связываться - по крайней мере, чаще, чем нет. - person Bill K; 05.06.2009
comment
Кроме того, нет объекта, точно описывающего, как используется коллекция, и ограничивающего его использовать НАМНОГО лучшую форму документации в коде? Я считаю, что информация, содержащаяся в дженериках, чрезвычайно избыточна в хорошем коде. - person Bill K; 05.06.2009
comment
Том - извините, просто пытаюсь отличиться от тех, кто думает о коде как о венгерском именовании ... - person James Schek; 05.06.2009

Обобщения в Java упрощают параметрический полиморфизм. С помощью параметров типа вы можете передавать аргументы типам. Подобно тому, как метод, подобный String foo(String s), моделирует некоторое поведение не только для конкретной строки, но и для любой строки s, так и тип, подобный List<T>, моделирует некоторое поведение не только для определенного типа, но для любого типа . List<T> говорит, что для любого типа T существует тип List, элементами которого являются Ts. Итак, List на самом деле является конструктором типа. Он принимает тип в качестве аргумента и в результате создает другой тип.

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

public interface F<A, B> {
  public B f(A a);
}

Этот интерфейс сообщает, что для некоторых двух типов, A и B, существует функция (называемая f), которая принимает A и возвращает B. Когда вы реализуете этот интерфейс, A и B могут быть любыми типами, которые вы хотите, если вы предоставляете функцию f, которая принимает первое и возвращает второе. Вот пример реализации интерфейса:

F<Integer, String> intToString = new F<Integer, String>() {
  public String f(int i) {
    return String.valueOf(i);
  }
}

До создания дженериков полиморфизм достигался путем создания подклассов с использованием ключевого слова extends. С помощью дженериков мы фактически можем отказаться от создания подклассов и вместо этого использовать параметрический полиморфизм. Например, рассмотрим параметризованный (общий) класс, используемый для вычисления хэш-кодов для любого типа. Вместо того, чтобы переопределять Object.hashCode (), мы бы использовали такой универсальный класс:

public final class Hash<A> {
  private final F<A, Integer> hashFunction;

  public Hash(final F<A, Integer> f) {
    this.hashFunction = f;
  }

  public int hash(A a) {
    return hashFunction.f(a);
  }
}

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

Однако дженерики Java не идеальны. Вы можете абстрагироваться от типов, но, например, вы не можете абстрагироваться от конструкторов типов. То есть вы можете сказать «для любого типа T», но не можете сказать «для любого типа T, который принимает параметр типа A».

I написал статью об этих ограничениях дженериков Java здесь.

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

До появления дженериков у вас могли быть такие классы, как Widget, расширенные на FooWidget, BarWidget и BazWidget, с дженериками у вас может быть один общий класс Widget<A>, который принимает Foo, Bar или Baz в своем конструкторе, чтобы дать вам Widget<Foo>, Widget<Bar> и Widget<Baz>.

person Apocalisp    schedule 05.06.2009
comment
Было бы неплохо насчет подкласса, если бы у Java не было интерфейсов. Разве не было бы правильно, если бы FooWidget, BarWidtet и BazWidget реализуют Widget? Хотя я могу ошибаться в этом ... Но это действительно хороший момент, если я ошибаюсь. - person Bill K; 05.06.2009
comment
Зачем вам нужен динамический универсальный класс для вычисления хеш-значений? Не могла бы статическая функция с соответствующими параметрами выполнять работу более эффективно? - person Ant_222; 24.01.2018

Дженерики избегают снижения производительности, связанного с упаковкой и распаковкой. В основном посмотрите на ArrayList vs List ‹T›. Оба делают одни и те же основные функции, но List ‹T› будет намного быстрее, потому что вам не нужно вставлять в / из объекта.

person Darren Kopp    schedule 16.09.2008
comment
В этом суть, не так ли? Хороший. - person MrBoJangles; 17.09.2008
comment
Ну не совсем; они полезны для безопасности типов + избегая приведения типов ссылок, вообще без упаковки / распаковки. - person ljs; 17.09.2008
comment
Итак, я предполагаю, что следующий вопрос: зачем мне вообще использовать ArrayList? И, рискуя ответить, я бы сказал, что списки ArrayList хороши, если вы не ожидаете, что их значения будут часто упаковывать и распаковывать. Комментарии? - person MrBoJangles; 17.09.2008
comment
Нет, они не работают с .net 2; полезно только для обратной совместимости. Списки ArrayList работают медленнее, небезопасны по типу и требуют либо упаковки / распаковки, либо приведения. - person ljs; 17.09.2008
comment
Если вы не сохраняете типы значений (например, целые числа или структуры), при использовании ArrayList упаковки нет - классы уже являются «объектами». Использование List ‹T› по-прежнему намного лучше из-за типа безопасности, и если вы действительно хотите хранить объекты, то лучше использовать List ‹object›. - person Wilka; 17.09.2008
comment
Да, но тогда есть приведение, если вы используете ссылочные типы, что, как я полагаю, также снижает производительность. Не говоря уже о том, что это не типобезопасность + расширение кода! - person ljs; 17.09.2008

Лучшее преимущество Generics - повторное использование кода. Допустим, у вас много бизнес-объектов, и вы собираетесь написать ОЧЕНЬ похожий код для каждой сущности, чтобы выполнять одни и те же действия. (I.E Linq to SQL-операции).

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

public interface IEntity
{

}

public class Employee : IEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int EmployeeID { get; set; }
}

public class Company : IEntity
{
    public string Name { get; set; }
    public string TaxID { get; set }
}

public class DataService<ENTITY, DATACONTEXT>
    where ENTITY : class, IEntity, new()
    where DATACONTEXT : DataContext, new()
{

    public void Create(List<ENTITY> entities)
    {
        using (DATACONTEXT db = new DATACONTEXT())
        {
            Table<ENTITY> table = db.GetTable<ENTITY>();

            foreach (ENTITY entity in entities)
                table.InsertOnSubmit (entity);

            db.SubmitChanges();
        }
    }
}

public class MyTest
{
    public void DoSomething()
    {
        var dataService = new DataService<Employee, MyDataContext>();
        dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
        var otherDataService = new DataService<Company, MyDataContext>();
            otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });

    }
}

Обратите внимание на повторное использование одного и того же сервиса с учетом разных типов в методе DoSomething выше. Поистине элегантно!

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

person Dean Poulin    schedule 17.09.2008

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

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

Dictionary<int, string> dictionary = new Dictionary<int, string>();

А компилятор / IDE делает остальную тяжелую работу. В частности, словарь позволяет использовать первый тип в качестве ключа (без повторяющихся значений).

person Tom Kidd    schedule 16.09.2008

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

  • Общая типизация при создании класса:

    публичный класс Foo ‹T> {public T get () ...

  • Избегание кастинга - мне всегда не нравились такие вещи, как

    новый компаратор {public int compareTo (Object o) {if (o instanceof classIcareAbout) ...

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

Моя первоначальная реакция на дженерики была похожа на вашу - «слишком беспорядочно, слишком сложно». Мой опыт показывает, что после некоторого использования их вы привыкаете к ним, и код без них кажется менее четко определенным и просто менее удобным. Кроме того, весь остальной мир Java использует их, так что в конечном итоге вам придется разобраться с программой, верно?

person Steve B.    schedule 04.06.2009
comment
чтобы быть точным в отношении предотвращения приведения: согласно документации Sun, приведение происходит, но выполняется компилятором. Вы просто можете избежать написания приведений в своем коде. - person Demi; 05.06.2009
comment
Возможно, мне кажется, что я застрял в длинной череде компаний, не желающих подниматься выше 1,4, так что кто знает, я могу уйти на пенсию, прежде чем дойду до 5. В последнее время я также думал, что разветвление SimpleJava может быть интересной концепцией - Java будет продолжать добавлять функции, и для большей части кода, который я видел, это будет плохо. Я уже имею дело с людьми, которые не умеют кодировать цикл - мне не хотелось бы видеть, как они пытаются внедрить дженерики в свои бессмысленно развернутые циклы. - person Bill K; 05.06.2009
comment
@Bill K: Мало кому нужно использовать всю мощь дженериков. Это необходимо только тогда, когда вы пишете универсальный класс. - person Eddie; 05.06.2009

Чтобы дать хороший пример. Представьте, что у вас есть класс под названием Foo

public class Foo
{
   public string Bar() { return "Bar"; }
}

Пример 1 Теперь вы хотите иметь коллекцию объектов Foo. У вас есть два варианта: LIst или ArrayList, оба работают одинаково.

Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();

//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());

В приведенном выше коде, если я попытаюсь добавить класс FireTruck вместо Foo, ArrayList добавит его, но общий список Foo вызовет исключение.

Пример второй.

Теперь у вас есть два списка массивов, и вы хотите вызвать функцию Bar () для каждого из них. Поскольку список ArrayList заполнен объектами, вы должны преобразовать их, прежде чем вы сможете вызвать bar. Но поскольку общий список Foo может содержать только Foo, вы можете вызывать Bar () непосредственно для них.

foreach(object o in al)
{
    Foo f = (Foo)o;
    f.Bar();
}

foreach(Foo f in fl)
{
   f.Bar();
}
person Peter Lange    schedule 04.06.2009
comment
Я предполагаю, что вы ответили, прежде чем читать вопрос. Это два случая, которые мне особо не помогают (описываются в вопросе). Есть ли другие случаи, когда он делает что-то полезное? - person Bill K; 05.06.2009

Разве вы никогда не писали метод (или класс), в котором ключевая концепция метода / класса не была жестко привязана к конкретному типу данных параметров / переменных экземпляра (вспомните связанный список, функции max / min, двоичный поиск , и т.д.).

Разве вы никогда не хотели, чтобы вы могли повторно использовать алгоритм / код, не прибегая к повторному использованию вырезания и вставки или компрометации строгой типизации (например, я хочу List строк, а не List вещей, которые я надеюсь такие струны!)?

Вот почему вам следует захотеть использовать дженерики (или что-то получше).

person Bert F    schedule 05.06.2009
comment
Мне никогда не приходилось копировать / вставлять повторное использование для такого рода вещей (не уверен, как это вообще могло произойти?) Вы реализуете интерфейс, который соответствует вашим потребностям, и используете этот интерфейс. Редкий случай, когда вы не можете этого сделать, - это когда вы пишете чистую утилиту (например, коллекции), и для них (как я описал в вопросе) это действительно никогда не было проблемой. Я иду на компромисс со строгой типизацией - как и вам, если вы используете отражение. Вы категорически отказываетесь использовать отражение, потому что у вас нет строгой типизации? (Если мне нужно использовать отражение, я просто инкапсулирую его как коллекцию). - person Bill K; 05.06.2009

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

private <T extends Throwable> T logAndReturn(T t) {
    logThrowable(t); // some logging method that takes a Throwable
    return t;
}

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

    ...
} catch (MyException e) {
    throw logAndReturn(e);
}

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

Это всего лишь простой пример использования универсальных методов. Есть еще немало других интересных вещей, которые вы можете сделать с помощью универсальных методов. Самым крутым, на мой взгляд, является определение типов с помощью дженериков. Возьмем следующий пример (взятый из книги Джоша Блоха «Эффективное Java 2-е издание»):

...
Map<String, Integer> myMap = createHashMap();
...
public <K, V> Map<K, V> createHashMap() {
    return new HashMap<K, V>();
}

Это мало что дает, но снижает беспорядок, когда общие типы длинные (или вложенные, т. Е. Map<String, List<String>>).

person jigawot    schedule 05.06.2009
comment
Я не думаю, что это правда в отношении исключений. Это заставляет меня задуматься, но я на 95% уверен, что вы получите точно такие же результаты вообще без использования дженериков (и более четкого кода). Информация о типе - это часть объекта, а не то, к чему вы его приводите. У меня есть искушение попробовать - может быть, завтра я на работе вернусь к вам ... Вот что я вам скажу, я собираюсь попробовать, и если вы правы, я собираюсь пройти через ваш список сообщений и проголосуйте за 5 ваших сообщений :) Если нет, вы можете подумать, что простая доступность дженериков заставила вас усложнить ваш код без каких-либо преимуществ. - person Bill K; 05.06.2009
comment
Верно, что это добавляет дополнительную сложность. Тем не менее, я думаю, что в этом есть какая-то польза. Проблема в том, что вы не можете выбросить простой старый Throwable из тела метода, который имеет определенные объявленные исключения. Альтернативный вариант - написать отдельные методы для возврата каждого типа исключения или выполнить приведение самостоятельно с помощью неуниверсального метода, который возвращает Throwable. Первое слишком многословно и совершенно бесполезно, а второму компилятор не поможет. Используя дженерики, компилятор автоматически вставит для вас правильное приведение. Итак, вопрос: стоит ли это сложности? - person jigawot; 05.06.2009
comment
Ох, я понял. Так что это позволяет избежать выхода гипса ... хороший момент. Я должен тебе 5. - person Bill K; 05.06.2009

Основное преимущество, как указывает Митчел, - строгая типизация без необходимости определять несколько классов.

Таким образом вы можете делать что-то вроде:

List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();

Без дженериков вам пришлось бы привести blah [0] к правильному типу, чтобы получить доступ к его функциям.

person Kevin Pang    schedule 16.09.2008
comment
Если бы у меня был выбор, я бы предпочел не бросать, чем бросать. - person MrBoJangles; 17.09.2008
comment
Сильная типизация - лучший аспект дженериков imho, особенно с учетом проверки типов во время компиляции, которая позволяет. - person ljs; 17.09.2008

jvm все равно приводит ... он неявно создает код, который обрабатывает общий тип как "Object" и создает приведение к желаемому экземпляру. Дженерики Java - это просто синтаксический сахар.

person Community    schedule 16.09.2008

Я знаю, что это вопрос C #, но общие шаблоны также используются в других языках, и их использование / цели очень похожи.

Коллекции Java используют универсальные шаблоны, начиная с Java 1.5. Итак, хорошее место для их использования - это когда вы создаете свой собственный объект, подобный коллекции.

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

class Pair<F, S> {
    public final F first;
    public final S second;

    public Pair(F f, S s)
    { 
        first = f;
        second = s;   
    }
}  

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

Для универсальных шаблонов также можно определить свои границы с помощью ключевых слов super и extends. Например, если вы хотите иметь дело с универсальным типом, но хотите убедиться, что он расширяет класс с именем Foo (который имеет метод setTitle):

public class FooManager <F extends Foo>{
    public void setTitle(F foo, String title) {
        foo.setTitle(title);
    }
}

Хотя это само по себе не очень интересно, полезно знать, что всякий раз, когда вы имеете дело с FooManager, вы знаете, что он будет обрабатывать типы MyClass и что MyClass расширяет Foo.

person etchasketch    schedule 17.09.2008

Из документации Sun Java в ответ на вопрос «почему я должен использовать дженерики?»:

"Generics предоставляет вам способ сообщить тип коллекции компилятору, чтобы его можно было проверить. Как только компилятор узнает тип элемента коллекции, компилятор может проверить, что вы использовали эту коллекцию последовательно и можете вставить правильное приведение значений, извлекаемых из коллекции ... Код, использующий универсальные шаблоны, более ясен и безопасен .... компилятор может проверить во время компиляции, что ограничения типа не нарушаются во время выполнения [курсив мой]. Поскольку программа компилируется без предупреждений, мы можем с уверенностью заявить, что она не вызовет исключение ClassCastException во время выполнения. Конечным результатом использования универсальных шаблонов, особенно в больших программах, является улучшенная читаемость и надежность . [курсив мой] "

person Demi    schedule 04.06.2009
comment
Я никогда не встречал случая, когда универсальная типизация коллекций помогла бы моему коду. Мои коллекции строго ограничены. Вот почему я сформулировал это «Может ли это мне помочь?». В качестве побочного вопроса, вы говорите, что до того, как вы начали использовать дженерики, такое случалось с вами время от времени? (добавление неправильного типа объекта в вашу коллекцию). Мне кажется, что это решение, в котором нет настоящих проблем, но, может быть, мне просто повезло. - person Bill K; 05.06.2009
comment
@Bill K: если код жестко контролируется (то есть используется только вами), возможно, вы никогда не столкнетесь с этим как с проблемой. Замечательно! Самые большие риски связаны с большими проектами, над которыми работают несколько человек, ИМО. Это подстраховка и лучшая практика. - person Demi; 05.06.2009
comment
@Bill K: Вот пример. Допустим, у вас есть метод, возвращающий ArrayList. Допустим, что ArrayList не является универсальной коллекцией. Чей-то код берет этот ArrayList и пытается выполнить некоторую операцию с его элементами. Единственная проблема в том, что вы заполнили ArrayList BillTypes, а потребитель пытается работать с ConsumerTypes. Это компилируется, но срывается во время выполнения. Если вы используете общие коллекции, вместо этого у вас будут ошибки компилятора, с которыми гораздо легче справиться. - person Demi; 05.06.2009
comment
@Bill K: Я работал с людьми с самыми разными уровнями навыков. Я не могу позволить себе роскошь выбирать, с кем делить проект. Таким образом, безопасность типов очень важна для меня. Да, я нашел (и исправил) ошибки, преобразовав код для использования Generics. - person Eddie; 05.06.2009

Универсальные шаблоны позволяют создавать строго типизированные объекты, но при этом необязательно определять конкретный тип. Я думаю, что лучший полезный пример - это List и подобные классы.

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

person Mitchel Sellers    schedule 16.09.2008

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

Одним из примеров использования обоих является связанный список. Какая польза была бы от класса связанного списка, если бы он мог использовать только объект Foo? Чтобы реализовать связанный список, который может обрабатывать любой тип объекта, связанный список и узлы в гипотетическом внутреннем классе узла должны быть универсальными, если вы хотите, чтобы список содержал только один тип объекта.

person Steve Landey    schedule 16.09.2008

Если ваша коллекция содержит типы значений, им не нужно упаковывать / распаковывать объекты при вставке в коллекцию, поэтому ваша производительность резко возрастает. Классные надстройки, такие как resharper, могут сгенерировать для вас больше кода, например циклы foreach.

person gt124    schedule 16.09.2008

Еще одно преимущество использования Generics (особенно с коллекциями / списками) - это проверка типа во время компиляции. Это действительно полезно при использовании универсального списка вместо списка объектов.

person Chris Pietschmann    schedule 16.09.2008

Основная причина заключается в том, что они обеспечивают безопасность типов

List<Customer> custCollection = new List<Customer>;

в отличие от,

object[] custCollection = new object[] { cust1, cust2 };

в качестве простого примера.

person Vin    schedule 16.09.2008
comment
Типовая безопасность, обсуждение само по себе. Может быть, кому-то стоит задать вопрос по этому поводу. - person MrBoJangles; 17.09.2008
comment
Да, но на что ты намекаешь? В чем весь смысл безопасности типов? - person Vin; 01.10.2008

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

Это дает вам несколько преимуществ:

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

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

  • Компилятор может выполнять больше оптимизаций, например, избегать боксов и т. Д.

person Thomas Danecker    schedule 16.09.2008

Пара вещей, которые нужно добавить / расширить (говоря с точки зрения .NET):

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

Общие аргументы для методов могут делать то же самое, но они также помогают применить принцип «Говори, не спрашивай», то есть «дай мне то, что я хочу, а если не можешь, скажи мне, почему».

person SpongeJim    schedule 17.09.2008

Я использую их, например, в GenericDao, реализованном с помощью SpringORM и Hibernate, которые выглядят так

public abstract class GenericDaoHibernateImpl<T> 
    extends HibernateDaoSupport {

    private Class<T> type;

    public GenericDaoHibernateImpl(Class<T> clazz) {
        type = clazz;
    }

    public void update(T object) {
        getHibernateTemplate().update(object);
    }

    @SuppressWarnings("unchecked")
    public Integer count() {
    return ((Integer) getHibernateTemplate().execute(
        new HibernateCallback() {
            public Object doInHibernate(Session session) {
                    // Code in Hibernate for getting the count
                }
        }));
    }
  .
  .
  .
}

Используя дженерики, мои реализации этих DAO вынуждают разработчика передавать им только те сущности, для которых они предназначены, просто создавая подкласс GenericDao.

public class UserDaoHibernateImpl extends GenericDaoHibernateImpl<User> {
    public UserDaoHibernateImpl() {
        super(User.class);     // This is for giving Hibernate a .class
                               // work with, as generics disappear at runtime
    }

    // Entity specific methods here
}

Моя маленькая структура более надежна (есть такие вещи, как фильтрация, отложенная загрузка, поиск). Я просто упростил здесь, чтобы дать вам пример

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

person victor hugo    schedule 04.06.2009

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

Прежде всего, дженерики - это не зависящая от языка концепция, и, IMO, это может иметь больше смысла, если вы одновременно думаете о регулярном (во время выполнения) полиморфизме.

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

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

Классы работают примерно так же. Вы параметризуете тип, и код генерируется компилятором.

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

public interface Foo<T extends MyObject> extends Hoo<T>{
    ...
}

Теперь никто не может устанавливать что-либо, кроме MyObject.

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

public <T extends MyObject> foo(T t1, T t2){
    ...
}   

Надеюсь, все это имеет смысл.

person Community    schedule 29.06.2018

Я как-то выступал на эту тему. Вы можете найти мои слайды, код и аудиозаписи по адресу http://www.adventuresinsoftware.com/generics/ < / а>.

person Michael L Perry    schedule 17.09.2008

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

List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
    stuff.do();
}

vs

List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
    Stuff stuff = (Stuff)i.next();
    stuff.do();
}

or

List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
    Stuff stuff = (Stuff)stuffList.get(i);
    stuff.do();
}

Одно только это стоит предельной «стоимости» дженериков, и вам не нужно быть универсальным гуру, чтобы использовать это и получить пользу.

person Will Hartung    schedule 04.06.2009
comment
Без дженериков вы можете: List stuffList = getStuff (); for (Материал объекта: stuffList) {((Материал) материал) .doStuff (); }, что не сильно отличается. - person Tom Hawtin - tackline; 05.06.2009
comment
Без дженериков я делаю stuffList.doAll (); Я сказал, что у меня всегда есть класс-оболочка, который как раз подходит для такого кода. В противном случае, как избежать копирования этого цикла в другом месте? Куда вы положите его для повторного использования? Класс-оболочка, вероятно, будет иметь какой-то цикл, но в этом классе, поскольку этот класс полностью посвящен материалу, использование цикла for, подобного предложенному выше Томом, довольно ясно / самодокументируется. - person Bill K; 05.06.2009
comment
@Jherico, это действительно интересно. Интересно, есть ли причина, по которой вы так думаете, и если я этого не понимаю, - что в природе людей есть что-то такое, что любой кастинг по какой-либо причине находит непристойным и готов пойти на неестественные меры (например, добавив гораздо более сложный синтаксис), чтобы этого избежать. Почему, если вам нужно что-то разыграть, вы уже проиграли? - person Bill K; 05.06.2009
comment
@Jherico: согласно документации Sun, дженерики предоставляют компилятору информацию о том, к чему приводить объекты. Поскольку преобразование для дженериков выполняет компилятор, а не пользователь, изменяет ли это ваш оператор? - person Demi; 05.06.2009
comment
Я преобразовал код из версии, предшествующей Java 1.5, в Generics, и в процессе обнаружил ошибки, когда в коллекцию был помещен неправильный тип объекта. Я также попадаю в ситуацию, когда вам нужно разыграть ваш потерянный лагерь, потому что, если вам нужно кастовать вручную, вы рискуете получить ClassCastException. С Generics компилятор делает это незаметно для вас, но вероятность ClassCastException way снижается из-за безопасности типов. Да, без Generics вы можете использовать Object, но для меня это ужасный запах кода, как и выдает Exception. - person Eddie; 05.06.2009
comment
@Eddie: согласен - это тоже проще писать и проще понять, IMO. @Bill K: вы действительно считаете общий синтаксис Java намного более сложным, чем неуниверсальный синтаксис? - person harto; 05.06.2009
comment
Я считаю, что это самая сложная часть Java для изучения / освоения. Есть несколько уникальных шаблонов синтаксиса Java, которые бросают новых программистов - внутренние классы, исключения, запоминание общедоступной static void main (String []) - хмм - тернарный оператор ... подробнее? Включите этот список, разве обобщения от FAR не являются самой сложной частью уникального синтаксиса в java? Я просто не вижу пользы от этой сложности, когда редкое удачно размещенное приведение представляет собой очень простой и понятный синтаксис, потребность которого в любом случае даже полностью не устраняется Generics (или нет? Если да, то я мог бы дать дженерикам немного больше свобода действий) - person Bill K; 05.06.2009
comment
Я думаю, что общий синтаксис СОВЕРШЕННО сложен, если взять все пространство. Но для ЭТОГО СЛУЧАЯ, который я считаю наиболее распространенным, синтаксис (для меня) довольно прост. По крайней мере, в Java ›› не является ключевым словом, как в C ++, поэтому List ‹Map ‹Integer›› является законным, тогда как в C ++ (в прошлом) этого не было, вам нужно было сделать List ‹Map ‹Integer››, возможно, они уже исправили это. - person Will Hartung; 06.06.2009

Обобщения также дают вам возможность создавать больше повторно используемых объектов / методов, при этом обеспечивая поддержку конкретного типа. В некоторых случаях вы также получаете большую производительность. Я не знаю полной спецификации Java Generics, но в .NET я могу указать ограничения для параметра Type, например, реализует интерфейс, конструктор и деривацию.

person Eric Schneider    schedule 05.06.2009

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

  2. Более строгие проверки типов во время компиляции - компилятор Java применяет строгую проверку типов к универсальному коду и выдает ошибки, если код нарушает безопасность типов. Исправлять ошибки времени компиляции проще, чем исправлять ошибки времени выполнения, которые бывает сложно найти.

  3. Устранение слепков.

person Community    schedule 30.10.2020