Я подумал, что предлагаю этот софтбол любому, кто захочет попасть в него из парка. Что такое дженерики, каковы преимущества дженериков, почему, где и как их использовать? Пожалуйста, оставьте это довольно простым. Спасибо.
Чем хороши дженерики, зачем их использовать?
Ответы (29)
- Позволяет писать код / использовать методы библиотеки, которые являются типобезопасными, т.е. List ‹string› гарантированно будет списком строк.
- В результате использования дженериков компилятор может выполнять проверки кода во время компиляции на предмет безопасности типов, т.е. пытаетесь ли вы поместить int в этот список строк? Использование ArrayList приведет к тому, что это будет менее прозрачная ошибка времени выполнения.
- Быстрее, чем использование объектов, так как он либо позволяет избежать упаковки / распаковки (где .net должен преобразовать типы значений в ссылочные типы или наоборот) или приведение объектов к требуемому ссылочному типу.
- Позволяет писать код, который применим ко многим типам с одинаковым базовым поведением, например, строка Dictionary ‹, 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, так как проверка типов выполняется во время компиляции. Это уменьшит вероятность появления ClassCastException
s во время выполнения и может привести к более надежному коду.
Но я подозреваю, что вы это прекрасно знаете.
Каждый раз, когда я смотрю на Generics, у меня болит голова. Я считаю, что лучшая часть 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
, содержащие Integer
, используются в цикле for-each. Каждый элемент объявлен как int
, и это разрешено, поскольку Integer
распаковывается обратно в примитив int
. И тот факт, что происходит распаковка, известен, потому что для указания того, что в Set
было Integer
, использовались общие.
Обобщения могут быть связующим звеном, которое объединяет новые функции, представленные в 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
обрабатываются как Object
, поскольку нет информации о типе, которая могла бы помочь компилятору узнать, какой тип должен ожидать Set
.
for (Object o : set) {
Это самая важная часть. Причина, по которой цикл for-each работает, заключается в том, что Set
реализует Iterable
, который возвращает Iterator
с информацией о типе, если таковая имеется. (Iterator<T>
, то есть.)
Однако, поскольку информации о типе нет, Set
вернет Iterator
, который вернет значения в Set
как Object
s, и поэтому элемент, извлекаемый в цикле 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
s. Итак, 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».
I написал статью об этих ограничениях дженериков Java здесь.
Одним из огромных преимуществ дженериков является то, что они позволяют избежать создания подклассов. Создание подклассов, как правило, приводит к появлению хрупких иерархий классов, которые неудобно расширять, и классов, которые трудно понять по отдельности, не глядя на всю иерархию.
До появления дженериков у вас могли быть такие классы, как Widget
, расширенные на FooWidget
, BarWidget
и BazWidget
, с дженериками у вас может быть один общий класс Widget<A>
, который принимает Foo
, Bar
или Baz
в своем конструкторе, чтобы дать вам Widget<Foo>
, Widget<Bar>
и Widget<Baz>
.
Дженерики избегают снижения производительности, связанного с упаковкой и распаковкой. В основном посмотрите на ArrayList vs List ‹T›. Оба делают одни и те же основные функции, но List ‹T› будет намного быстрее, потому что вам не нужно вставлять в / из объекта.
Лучшее преимущество Generics - повторное использование кода. Допустим, у вас много бизнес-объектов, и вы собираетесь написать ОЧЕНЬ похожий код для каждой сущности, чтобы выполнять одни и те же действия. (I.E Linq to SQL-операции).
С помощью дженериков вы можете создать класс, который сможет работать с любым из типов, которые наследуются от данного базового класса, или реализовать данный интерфейс следующим образом:
public interface IEntity
{
}
public class Employee : IEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int EmployeeID { get; set; }
}
public class Company : IEntity
{
public string Name { get; set; }
public string TaxID { get; set }
}
public class DataService<ENTITY, DATACONTEXT>
where ENTITY : class, IEntity, new()
where DATACONTEXT : DataContext, new()
{
public void Create(List<ENTITY> entities)
{
using (DATACONTEXT db = new DATACONTEXT())
{
Table<ENTITY> table = db.GetTable<ENTITY>();
foreach (ENTITY entity in entities)
table.InsertOnSubmit (entity);
db.SubmitChanges();
}
}
}
public class MyTest
{
public void DoSomething()
{
var dataService = new DataService<Employee, MyDataContext>();
dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
var otherDataService = new DataService<Company, MyDataContext>();
otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });
}
}
Обратите внимание на повторное использование одного и того же сервиса с учетом разных типов в методе DoSomething выше. Поистине элегантно!
Есть много других веских причин использовать дженерики в своей работе, это мой любимый.
Мне они просто нравятся, потому что они дают вам быстрый способ определить собственный тип (поскольку я все равно их использую).
Так, например, вместо того, чтобы определять структуру, состоящую из строки и целого числа, а затем реализовывать целый набор объектов и методов о том, как получить доступ к массиву этих структур и т. Д., Вы можете просто создать Dictionary
Dictionary<int, string> dictionary = new Dictionary<int, string>();
А компилятор / IDE делает остальную тяжелую работу. В частности, словарь позволяет использовать первый тип в качестве ключа (без повторяющихся значений).
Типизированные коллекции - даже если вы не хотите их использовать, вам, вероятно, придется иметь дело с ними из других библиотек, других источников.
Общая типизация при создании класса:
публичный класс Foo ‹T> {public T get () ...
Избегание кастинга - мне всегда не нравились такие вещи, как
новый компаратор {public int compareTo (Object o) {if (o instanceof 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 () для каждого из них. Поскольку список ArrayList заполнен объектами, вы должны преобразовать их, прежде чем вы сможете вызвать bar. Но поскольку общий список Foo может содержать только Foo, вы можете вызывать Bar () непосредственно для них.
foreach(object o in al)
{
Foo f = (Foo)o;
f.Bar();
}
foreach(Foo f in fl)
{
f.Bar();
}
Разве вы никогда не писали метод (или класс), в котором ключевая концепция метода / класса не была жестко привязана к конкретному типу данных параметров / переменных экземпляра (вспомните связанный список, функции max / min, двоичный поиск , и т.д.).
Разве вы никогда не хотели, чтобы вы могли повторно использовать алгоритм / код, не прибегая к повторному использованию вырезания и вставки или компрометации строгой типизации (например, я хочу 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>>
).
Throwable
из тела метода, который имеет определенные объявленные исключения. Альтернативный вариант - написать отдельные методы для возврата каждого типа исключения или выполнить приведение самостоятельно с помощью неуниверсального метода, который возвращает Throwable
. Первое слишком многословно и совершенно бесполезно, а второму компилятор не поможет. Используя дженерики, компилятор автоматически вставит для вас правильное приведение. Итак, вопрос: стоит ли это сложности?
- person jigawot; 05.06.2009
Основное преимущество, как указывает Митчел, - строгая типизация без необходимости определять несколько классов.
Таким образом вы можете делать что-то вроде:
List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();
Без дженериков вам пришлось бы привести blah [0] к правильному типу, чтобы получить доступ к его функциям.
jvm все равно приводит ... он неявно создает код, который обрабатывает общий тип как "Object" и создает приведение к желаемому экземпляру. Дженерики 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 в ответ на вопрос «почему я должен использовать дженерики?»:
"Generics предоставляет вам способ сообщить тип коллекции компилятору, чтобы его можно было проверить. Как только компилятор узнает тип элемента коллекции, компилятор может проверить, что вы использовали эту коллекцию последовательно и можете вставить правильное приведение значений, извлекаемых из коллекции ... Код, использующий универсальные шаблоны, более ясен и безопасен .... компилятор может проверить во время компиляции, что ограничения типа не нарушаются во время выполнения [курсив мой]. Поскольку программа компилируется без предупреждений, мы можем с уверенностью заявить, что она не вызовет исключение ClassCastException во время выполнения. Конечным результатом использования универсальных шаблонов, особенно в больших программах, является улучшенная читаемость и надежность сильный>. [курсив мой] "
Универсальные шаблоны позволяют создавать строго типизированные объекты, но при этом необязательно определять конкретный тип. Я думаю, что лучший полезный пример - это List и подобные классы.
Используя общий список, вы можете иметь список List List, что хотите, и вы всегда можете ссылаться на строгую типизацию, вам не нужно преобразовывать или что-то подобное, как с массивом или стандартным списком.
Обобщения позволяют использовать строгую типизацию для объектов и структур данных, которые должны содержать любой объект. Он также устраняет утомительные и дорогостоящие преобразования типов при извлечении объектов из общих структур (упаковка / распаковка).
Одним из примеров использования обоих является связанный список. Какая польза была бы от класса связанного списка, если бы он мог использовать только объект Foo? Чтобы реализовать связанный список, который может обрабатывать любой тип объекта, связанный список и узлы в гипотетическом внутреннем классе узла должны быть универсальными, если вы хотите, чтобы список содержал только один тип объекта.
Если ваша коллекция содержит типы значений, им не нужно упаковывать / распаковывать объекты при вставке в коллекцию, поэтому ваша производительность резко возрастает. Классные надстройки, такие как resharper, могут сгенерировать для вас больше кода, например циклы foreach.
Еще одно преимущество использования Generics (особенно с коллекциями / списками) - это проверка типа во время компиляции. Это действительно полезно при использовании универсального списка вместо списка объектов.
Основная причина заключается в том, что они обеспечивают безопасность типов
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
}
Моя маленькая структура более надежна (есть такие вещи, как фильтрация, отложенная загрузка, поиск). Я просто упростил здесь, чтобы дать вам пример
Я, как и Стив, и вы, вначале сказал "Слишком беспорядочно и сложно", но теперь я вижу его преимущества.
Очевидные преимущества, такие как «безопасность типов» и «отсутствие приведений», уже упоминались, так что, возможно, я могу поговорить о некоторых других «преимуществах», которые, я надеюсь, помогут.
Прежде всего, дженерики - это не зависящая от языка концепция, и, IMO, это может иметь больше смысла, если вы одновременно думаете о регулярном (во время выполнения) полиморфизме.
Например, полиморфизм, который мы знаем из объектно-ориентированного дизайна, имеет понятие времени выполнения, в котором вызывающий объект определяется во время выполнения по мере выполнения программы, и соответствующий метод вызывается соответственно в зависимости от типа среды выполнения. В дженериках идея в чем-то похожа, но все происходит во время компиляции. Что это значит и как вы этим пользуетесь?
(Давайте придерживаться общих методов, чтобы сохранить его компактность). Это означает, что вы все еще можете использовать один и тот же метод для отдельных классов (как вы делали ранее в полиморфных классах), но на этот раз они автоматически генерируются компилятором в зависимости от установленных типов. во время компиляции. Вы параметризуете свои методы по типу, который вы указываете во время компиляции. Таким образом, вместо того, чтобы писать методы с нуля для каждого отдельного типа, как это делается при полиморфизме во время выполнения (переопределение метода), вы позволяете компиляторам выполнять работу во время компиляции. Это имеет очевидное преимущество, поскольку вам не нужно делать вывод обо всех возможных типах, которые могут использоваться в вашей системе, что делает ее гораздо более масштабируемой без изменения кода.
Классы работают примерно так же. Вы параметризуете тип, и код генерируется компилятором.
Как только вы получите представление о «времени компиляции», вы можете использовать «ограниченные» типы и ограничить то, что может быть передано как параметризованный тип через классы / методы. Таким образом, вы можете контролировать, через что нужно проходить, что является мощной вещью, особенно если у вас есть структура, потребляемая другими людьми.
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();
}
vs
List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
Stuff stuff = (Stuff)i.next();
stuff.do();
}
or
List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
Stuff stuff = (Stuff)stuffList.get(i);
stuff.do();
}
Одно только это стоит предельной «стоимости» дженериков, и вам не нужно быть универсальным гуру, чтобы использовать это и получить пользу.
Обобщения также дают вам возможность создавать больше повторно используемых объектов / методов, при этом обеспечивая поддержку конкретного типа. В некоторых случаях вы также получаете большую производительность. Я не знаю полной спецификации Java Generics, но в .NET я могу указать ограничения для параметра Type, например, реализует интерфейс, конструктор и деривацию.
Предоставление программистам возможности реализовать универсальные алгоритмы. Используя универсальные шаблоны, программисты могут реализовать универсальные алгоритмы, которые работают с коллекциями разных типов, могут быть настроены, являются типобезопасными и более удобными для чтения.
Более строгие проверки типов во время компиляции - компилятор Java применяет строгую проверку типов к универсальному коду и выдает ошибки, если код нарушает безопасность типов. Исправлять ошибки времени компиляции проще, чем исправлять ошибки времени выполнения, которые бывает сложно найти.
Устранение слепков.
write code which is applicable to many types with the same underlying behavior
- person LCJ   schedule 18.12.2013