Я подумал, что предлагаю этот софтбол любому, кто захочет попасть в него из парка. Что такое дженерики, каковы преимущества дженериков, почему, где и как их использовать? Пожалуйста, оставьте это довольно простым. Спасибо.
Я подумал, что предлагаю этот софтбол любому, кто захочет попасть в него из парка. Что такое дженерики, каковы преимущества дженериков, почему, где и как их использовать? Пожалуйста, оставьте это довольно простым. Спасибо.
Ответы:
Я действительно ненавижу повторяться. Я ненавижу печатать одно и то же чаще, чем нужно. Я не люблю повторять вещи несколько раз с небольшими отличиями.
Вместо создания:
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>
каждый другой связанный класс для создания типобезопасных версий?
Я не.
Отсутствие необходимости в приведении типов - одно из самых больших преимуществ дженериков 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+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
что, как известно, содержит Integer
s. Попытка вставить String
вызовет ошибку компиляции.
Далее, for-each цикл принимает все три из них:
for (int i : set) {
total += i;
}
Во-первых, Set
содержащие Integer
s используются в цикле 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
. Следовательно, в этом примере a Object
- это то, что add
добавляется в набор.
set.add(10);
set.add(42);
В приведенных выше строках задействован автобоксинг - примитивное int
значение 10
и 42
автоматически упаковываются в Integer
объекты, которые добавляются в Set
. Однако имейте в виду, что Integer
объекты обрабатываются как Object
s, так как нет информации о типе, которая помогала бы компилятору узнать, какой тип Set
следует ожидать.
for (Object o : set) {
Это самая важная часть. Причина, по которой цикл for-each работает, заключается в том, что он Set
реализует Iterable
интерфейс, который возвращает информацию о Iterator
типе, если таковая имеется. ( Iterator<T>
, то есть.)
Однако, поскольку информации о типе нет, Set
будет возвращен, Iterator
который вернет значения в Set
as Object
s, и именно поэтому элемент, извлекаемый в цикле for-each, должен иметь тип Object
.
Теперь, когда объект получен Object
из Set
, его необходимо Integer
вручную преобразовать в объект, чтобы выполнить добавление:
total += (Integer)o;
Здесь выполняется приведение типа от an Object
к Integer
. В этом случае мы знаем, что это всегда будет работать, но ручное приведение типов всегда заставляет меня думать, что это хрупкий код, который может быть поврежден, если незначительное изменение будет сделано в другом месте. (Я чувствую, что каждое приведение типов - это ClassCastException
ожидание, но я отвлекаюсь ...)
Integer
Теперь Unboxed в 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».
Я написал статью об этих ограничениях дженериков Java здесь.
Одним из огромных преимуществ дженериков является то, что они позволяют избежать создания подклассов. Создание подклассов, как правило, приводит к появлению хрупких иерархий классов, которые неудобно расширять, и классов, которые трудно понять по отдельности, не глядя на всю иерархию.
Wereas до дженериков вы можете иметь классы , как Widget
продлен FooWidget
, BarWidget
и BazWidget
, с обобщениями вы можете иметь один общий класс , Widget<A>
который принимает Foo
, Bar
или Baz
в его конструктор , чтобы дать вам Widget<Foo>
, Widget<Bar>
и Widget<Baz>
.
Дженерики избегают снижения производительности, связанного с упаковкой и распаковкой. По сути, посмотрите на ArrayList vs List <T>. Оба делают одни и те же основные вещи, но List <T> будет намного быстрее, потому что вам не нужно вставлять в / из объекта.
Лучшее преимущество 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 выше. Действительно элегантно!
Есть много других веских причин использовать дженерики в своей работе, это мой любимый.
Мне они просто нравятся, потому что они дают вам быстрый способ определить собственный тип (поскольку я все равно их использую).
Так, например, вместо того, чтобы определять структуру, состоящую из строки и целого числа, а затем реализовывать целый набор объектов и методов для получения доступа к массиву этих структур и т. Д., Вы можете просто создать 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 добавит его, но Generic List of 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
. Первое слишком многословно и совершенно бесполезно, а второму компилятор не поможет. Используя дженерики, компилятор автоматически вставит для вас правильное приведение. Итак, вопрос: стоит ли это сложности?
Основное преимущество, как указывает Митчел, - строгая типизация без необходимости определять несколько классов.
Таким образом вы можете делать что-то вроде:
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 List, какой хотите, и вы всегда можете ссылаться на строгую типизацию, вам не нужно преобразовывать или что-то подобное, как с массивом или стандартным списком.
Универсальные шаблоны позволяют использовать строгую типизацию для объектов и структур данных, которые должны содержать любой объект. Он также устраняет утомительные и дорогостоящие преобразования типов при извлечении объектов из общих структур (упаковка / распаковка).
Одним из примеров использования обоих является связанный список. Какая польза была бы от класса связанного списка, если бы он мог использовать только объект Foo? Чтобы реализовать связанный список, который может обрабатывать любой тип объекта, связанный список и узлы в гипотетическом внутреннем классе узла должны быть универсальными, если вы хотите, чтобы список содержал только один тип объекта.
Еще одно преимущество использования Generics (особенно с Collections / Lists) - это проверка типа во время компиляции. Это действительно полезно при использовании универсального списка вместо списка объектов.
Единственная причина - они обеспечивают безопасность типов.
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();
}
против
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, например, реализует интерфейс, конструктор и деривацию.
Предоставление программистам возможности реализовать универсальные алгоритмы. Используя универсальные шаблоны, программисты могут реализовывать универсальные алгоритмы, которые работают с коллекциями разных типов, могут быть настроены, являются типобезопасными и удобными для чтения.
Более строгие проверки типов во время компиляции - компилятор Java применяет строгую проверку типов к универсальному коду и выдает ошибки, если код нарушает безопасность типов. Исправлять ошибки времени компиляции проще, чем исправлять ошибки времени выполнения, которые бывает сложно найти.
Устранение слепков.