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

StackOverflow https://stackoverflow.com/questions/77632

  •  09-06-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

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

Другие советы

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

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

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

Я не.

Отсутствие необходимости приведения типов — одно из самых больших преимуществ дженериков 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+Космос

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Изменить. Пример без дженериков.

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

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

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

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

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

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

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

А 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 чьи элементы Tс.Так 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-дженериков здесь.

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

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

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

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

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

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

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

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

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

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

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

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

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

    Новый компаратор {public int compareto (Object o) {if (o экземпляр Classicareabout) ...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

JVM все равно бросает...он неявно создает код, который рассматривает универсальный тип как «Объект» и создает приведение к желаемому экземпляру.Дженерики Java — это всего лишь синтаксический сахар.

Я знаю, что это вопрос 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 в ответ на вопрос «почему мне следует использовать дженерики?»:

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

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

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

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

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

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

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

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

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

в отличие от,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Занятия проходят примерно так же.Вы параметризуете тип, и компилятор генерирует код.

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

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

Теперь никто не может установить что-либо, кроме 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();
}

против

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 я могу указать ограничения для параметра Type, например, Implements a Interface, Constructor и Derivation.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top