it-swarm-ru.tech

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

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

79
MrBoJangles
  • Позволяет вам писать код/​​использовать библиотечные методы, которые являются типобезопасными, то есть список <строка> гарантированно будет списком строк.
  • В результате использования обобщений компилятор может выполнять проверки кода во время компиляции для обеспечения безопасности типов, то есть вы пытаетесь поместить int в этот список строк? Использование ArrayList приведет к менее прозрачной ошибке времени выполнения.
  • Быстрее, чем использование объектов, так как это позволяет избежать упаковки/распаковки (где .net необходимо преобразовать типы значений в ссылочные типы или наоборот ) или преобразования объектов в требуемый ссылочный тип.
  • Позволяет вам писать код, который применим ко многим типам с одинаковым базовым поведением, то есть Dictionary <string, int> использует тот же базовый код, что и Dictionary <DateTime, double>; используя дженерики, команда разработчиков фреймворка должна была написать только один фрагмент кода, чтобы достичь обоих результатов с вышеупомянутыми преимуществами.
120
ljs

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

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

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> и любой другой связанный класс для создания безопасных для типов версий?

Я не.

46
James Schek

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

Но я подозреваю, что вы полностью осведомлены об этом.

Каждый раз, когда я смотрю на дженерики, у меня болит голова. Я считаю, что лучшая часть 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+Space

Правда, мне нужно было выбрать 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, который, как известно, содержит Integers. Попытка добавить String может привести к ошибке компиляции.

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

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

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

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

И, честно говоря, как видно из примера 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 - это то, что added входит в набор.

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

В приведенных выше строках autoboxing находится в игре - примитивные значения int10 и 42 автоматически помещаются в объекты Integer, которые добавляются в Set. Однако имейте в виду, что объекты Integer обрабатываются как Objects, так как нет информации о типе, которая помогла бы компилятору узнать, какого типа должен ожидать 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. В этом случае мы знаем, что это всегда будет работать, но ручное приведение типов всегда заставляет меня чувствовать, что это хрупкий код, который может быть поврежден, если в другом месте будут сделаны незначительные изменения. (Я чувствую, что каждый typecast - это ClassCastException, ожидающий, но я отвлекся ...)

Integer теперь распаковывается в int и может выполнять добавление в переменную inttotal.

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

20
coobird

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

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

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

15
Tom Hawtin - tackline

Обобщения в 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".

Я написал статью об этих пределах обобщений Java здесь.

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

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

10
Apocalisp

Дженерики избегают снижения производительности бокса и распаковки. По сути, посмотрите на ArrayList vs List <T>. Оба выполняют одни и те же основные вещи, но List <T> будет намного быстрее, потому что вам не нужно боксировать с/на объект.

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

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

    открытый класс Foo <T> {public T get () ...

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

    new Comparator {public int compareTo (Object o) {if (o instanceof classIcareAbout) ...

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

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

5
Steve B.

Чтобы дать хороший пример. Представьте, что у вас есть класс под названием 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 может содержать только Foos, вы можете вызывать Bar () непосредственно для них.

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

foreach(Foo f in fl)
{
   f.Bar();
}
5
Peter Lange

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

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

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

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

5
Tom Kidd

Лучшее преимущество для 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 выше. Действительно элегантно!

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

5
Dean Poulin

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

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

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

4
Bert F

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

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, что было бы все, что вы могли бы сделать без обобщений.

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

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

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

3
jigawot

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

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

2
Demi

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

2
Anonymous Coward

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

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

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

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

2
Kevin Pang

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

Коллекции Java используют generics , поскольку 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.

2
etchasketch

Я использую их, например, в 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
}

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

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

1
victor hugo

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

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

1
Mitchel Sellers

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

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

1
Steve Landey

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

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

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

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

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

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

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

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

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

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

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

1
stdout

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

1
Chris Pietschmann

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

1
gt124

Единственная причина в том, что они обеспечивают тип безопасности

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

в отличие от

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

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

1
Vin

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

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

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

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

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

1
Thomas Danecker

Несколько вещей для добавления/расширения (если говорить с точки зрения .NET):

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

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

1
SpongeJim

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

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

против

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

или же

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

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

0
Will Hartung

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

0
Eric Schneider

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

0
Michael L Perry