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


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

28 76

28 ответов:

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

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

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

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 раза эффективнее, и мне нужно поддерживать только одну копию. Почему бы вам не хотеть поддерживать меньше код?

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

Я нет.

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

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

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

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

современные IDE облегчают написание кода с помощью дженериков.

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

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

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

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

Map<String, Integer> m = new HaCtrl+пробел

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

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

ключевое преимущество дженериков приходит от того, как он хорошо играет с новыми функциями Java 5. вот пример подбрасывания целых чисел в A 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-каждый цикл принимает все три из них:

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

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

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

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

Edit-пример без дженериков

ниже приводится иллюстрация выше Set пример без использования дженериков. Это возможно, но не совсем приятно:

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

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

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

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

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

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

for (Object o : set) {

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

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

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

  total += (Integer)o;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

лучшее преимущество для дженериков-это повторное использование кода. Допустим, у вас есть много бизнес-объектов, и вы собираетесь написать очень похожий код для каждого объекта, чтобы выполнять одни и те же действия. (Т. е. операции 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 выше. Поистине шикарно!

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

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

  • общий ввод в создании класса:

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

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

    новый компаратор { public int compareTo (объект o){ если (o instanceof classIcareAbout)...

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

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

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

Пример 1 Теперь вы хотите иметь коллекцию объектов Foo. У вас есть два варианта, список или 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() на каждом. Поскольку hte ArrayList заполнен объектами, вы должны бросить их, прежде чем сможете вызвать bar. Но поскольку общий список Foo может содержать только Foo, вы можете вызвать Bar() непосредственно на них.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 2nd Edition Джоша Блоха):

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

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

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

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

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

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

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

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

единственная причина, по которой они обеспечивают безопасность типа

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

в отличие от,

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

как простой пример.

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

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

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

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

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

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

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

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

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

С помощью дженериков мои реализации этого DAOs заставляют разработчика передавать им только те сущности, для которых они предназначены, просто подкласс 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
}

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

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

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

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

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

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

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

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

теперь никто не может установить sth, кроме MyObject.

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

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

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

Я как-то выступал на эту тему. Вы можете найти мои слайды, код и аудиозапись на http://www.adventuresinsoftware.com/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();
}

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

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