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

c# generics types

59580 просмотра

28 ответа

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

Автор: MrBoJangles Источник Размещён: 04.10.2019 12:01

Ответы (28)


120 плюса

Решение
  • Позволяет вам писать код / ​​использовать библиотечные методы, которые являются типобезопасными, т.е. List гарантированно будет списком строк.
  • В результате использования обобщений компилятор может выполнять проверки кода во время компиляции для обеспечения безопасности типов, т.е. вы пытаетесь поместить int в этот список строк? Использование ArrayList приведет к менее прозрачной ошибке времени выполнения.
  • Быстрее, чем использование объектов, так как это позволяет избежать упаковки / распаковки (где .net необходимо преобразовать типы значений в ссылочные типы или наоборот ) или преобразования объектов в требуемый ссылочный тип.
  • Позволяет вам писать код, который применим ко многим типам с одинаковым базовым поведением, то есть Dictionary использует тот же базовый код, что и Dictionary ; используя дженерики, команда разработчиков фреймворка должна была написать только один фрагмент кода, чтобы достичь обоих результатов с вышеупомянутыми преимуществами.
Автор: ljs Размещён: 16.09.2008 10:04

46 плюса

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

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

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>или a, Reference<T>которые должны взаимодействовать с другими классами. Вы действительно хотите расширить Callable<T>и Future<T>все остальные связанные классы для создания безопасных для типов версий?

Я не.

Автор: James Schek Размещён: 04.06.2009 11:56

20 плюса

Отсутствие необходимости в typecast является одним из самых больших преимуществ универсальных шаблонов 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. Вот пример добавления целых чисел в 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-each принимает все три из них:

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

Во-первых, Setсодержащие Integers используются в цикле for-each. Каждый элемент объявляется как a, intи это разрешено, поскольку the Integerunboxed обратно в примитив 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. Следовательно, в этом примере a Object- это то, что вводится addв набор.

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

В приведенных выше строках autoboxing находится в игре - примитивное intзначение 10и 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;

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

IntegerТеперь Unboxed в intи разрешено выполнять добавление в intпеременной total.

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

Автор: coobird Размещён: 05.06.2009 12:24

15 плюса

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

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

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

Автор: Tom Hawtin - tackline Размещён: 04.06.2009 11:43

10 плюса

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

Автор: Apocalisp Размещён: 05.06.2009 12:39

8 плюса

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

Автор: Darren Kopp Размещён: 16.09.2008 10:00

5 плюса

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

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

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

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

Автор: Tom Kidd Размещён: 16.09.2008 10:02

5 плюса

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

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

Автор: Dean Poulin Размещён: 17.09.2008 04:03

5 плюса

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

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

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

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

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

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

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

Автор: Steve B. Размещён: 04.06.2009 11:39

5 плюса

Чтобы дать хороший пример. Представьте, что у вас есть класс под названием 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();
}
Автор: Peter Lange Размещён: 04.06.2009 11:46

4 плюса

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

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

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

Автор: Bert F Размещён: 05.06.2009 12:33

3 плюса

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

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

Дело в том, что вы не потеряете тип, передав его через метод. Вы можете бросить правильный тип исключения вместо просто a 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>>).

Автор: jigawot Размещён: 05.06.2009 12:24

2 плюса

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

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

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

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

Автор: Kevin Pang Размещён: 16.09.2008 10:04

2 плюса

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

Автор: Anonymous Coward Размещён: 16.09.2008 10:26

2 плюса

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

Автор: etchasketch Размещён: 17.09.2008 02:28

2 плюса

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

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

Автор: Demi Размещён: 04.06.2009 11:49

1 плюс

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

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

Автор: Mitchel Sellers Размещён: 16.09.2008 09:59

1 плюс

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

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

Автор: Steve Landey Размещён: 16.09.2008 10:00

1 плюс

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

Автор: gt124 Размещён: 16.09.2008 10:01

1 плюс

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

Автор: Chris Pietschmann Размещён: 16.09.2008 10:29

1 плюс

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

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

в отличие от

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

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

Автор: Vin Размещён: 16.09.2008 10:30

1 плюс

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

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

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

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

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

Автор: Thomas Danecker Размещён: 16.09.2008 10:32

1 плюс

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

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

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

Автор: SpongeJim Размещён: 17.09.2008 01:50

1 плюс

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

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

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

Автор: victor hugo Размещён: 04.06.2009 11:42

1 плюс

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

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

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

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

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

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

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

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

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

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

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

Автор: stdout Размещён: 29.06.2018 02:21

0 плюса

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

Автор: Michael L Perry Размещён: 17.09.2008 02:36

0 плюса

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

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

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

Автор: Will Hartung Размещён: 04.06.2009 11:43

0 плюса

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

Автор: Eric Schneider Размещён: 05.06.2009 12:00
Вопросы из категории :
32x32