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

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

Ответ 1

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

Ответ 2

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

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

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

Я не знаю.

Ответ 3

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

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

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

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

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

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

Вот пример создания Map<String, Integer> с a 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:

Во-первых, generics и autoboxing примитивов допускают следующие строки:

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

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

Далее, для каждого цикла все три из них:

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

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

Generics может быть клеем, который объединяет новые функции, представленные на 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;
}

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

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

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

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

for (Object o : set) {

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

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

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

  total += (Integer)o;

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

Этот интерфейс говорит, что для некоторых двух типов A и B существует функция (называемая f), которая принимает A и возвращает 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-дженериков.

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

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

Ответ 6

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

Ответ 7

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

  • Общий ввод текста при создании класса:

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

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

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

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

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

Ответ 8

Чтобы дать хороший пример. Представьте, что у вас есть класс под названием 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 добавит его, но Generic List из Foo вызовет исключение.

Пример 2.

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

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

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

Ответ 9

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

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

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

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

Ответ 10

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

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

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 выше. Действительно элегантный!

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

Ответ 11

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

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

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

Ответ 12

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

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

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

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

Ответ 13

Я знаю, что это вопрос С#, но 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.

Ответ 14

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

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

Ответ 15

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

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

Это просто простой пример использования универсальных методов. Существует множество других опрятных вещей, которые вы можете сделать с помощью общих методов. Самое крутое, на мой взгляд, это тип вывода с помощью дженериков. Возьмем следующий пример (взятый из Josh Bloch 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>>).

Ответ 16

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

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

Ответ 17

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

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

Ответ 18

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

Ответ 19

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

Ответ 20

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

Ответ 21

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

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

в отличие от

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

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

Ответ 22

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

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

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

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

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

Ответ 23

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

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

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

Ответ 24

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

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

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

Ответ 26

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

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();
}

или

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

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

Ответ 27

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