Сегодня хотел бы рассказать вам о generics в Java и его некоторых особенностях. Данная возможность появилась в Java с версии J2SE5 и с тех пор полностью изменила работу с Collections. С помощью данного подхода мы можем использовать различные структуры и контейнеры с разными типами данных, не изменяя их описания.
Впрочем, рассмотрим простой пример с Generics и без них для общего понимания.
Это пример, собственно, с Generis. Тут представлен список строк, в который мы добавляем две произвольные строки. В данном случае обобщенным типом является класс ArrayList (вместо этого T мы можем подставить любой тип, который не является примитивным типом).
Давайте рассмотрим код, который вероятно писали до Generics:
Как видим, в принципе разницы никакой нет, единственное, что требуется — выполнять явное преобразование с типа Object в тип String.
Однако, компилятору не важно каким способом вы будете пользоваться, оба примера будут одинаково представлены в байт-коде. По сути, Generic-версия изначально представиться в (2) а потом уже будет транслирована в байт-код. Это так называемый процесc erasure(стирание).
Преимущество (1) в том, что проверка на ошибки будет выполнена компилятором на этапе компиляции, в отличии от (2), которая не будет произведена.
В результате в (3) может возникнуть Runtime error:
Exception in thread «main» java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Wildcards вместе с extends. Иногда в колекцию нам требуется сохранить не только определенно одни типы, а еще и их подтипы, для этих целей и используються WildCards (можно просто Маски).
Давайте рассмотрим пример:
Метод addAll() позволяет добавлять элементы одной колекции в другую. Выражение “? extends E” означает, что мы можем добавлять подтипы типа Е в коллекцию.
В данном примере мы создали пустой список, в который добавили список элементов Integer и Double. Это действие не вызовет ошибок компиляции, так как ints имеет тип List, что является подтипом Collection<? extends Number>. То же самое с dbls.
WildCards вместе с super. Так же, как и c extends, WildCard с super имеет вид: “? super T “, пример c Generic методом:
“? super T “ означает, что dst список может вместить в себе элементы, которые являются супертипами типа Т.
The Get and Put principle. Вопрос: когда же нам использовать маски с super и маски с extends? Правило очень простое: используйте extends, когда нужно получить значения со структуры, и super, когда нужно поместить значания в структуру. Если же добавляем и извлекаем элементы, то вообще не используем wildcards.
По сути, использование данного принципа можно увидеть в предыдущем примере.
— Generics позволяют уменьшить количество ошибок в преобразованиях типов в коде за счет проверки их во время компиляции;
— Делают код чище и удобно читаемым.
Литература: M. Naftalin, P. Wadler — Java Generics and Collections
PS: Статья не претендует на полное руководство, время от времени буду ее дополнять.
Впрочем, рассмотрим простой пример с Generics и без них для общего понимания.
(1)
List<String> words = new ArrayList<String>();
words.add("Hello ");
words.add("world!");
String s = words.get(0)+words.get(1);
System.out.println(s.equals("Hello world!"));
Это пример, собственно, с Generis. Тут представлен список строк, в который мы добавляем две произвольные строки. В данном случае обобщенным типом является класс ArrayList (вместо этого T мы можем подставить любой тип, который не является примитивным типом).
Давайте рассмотрим код, который вероятно писали до Generics:
(2)
List words = new ArrayList();
words.add("Hello ");
words.add("world!");
String s = ((String)words.get(0))+((String)words.get(1))
System.out.println(s.equals("Hello world!"));
Как видим, в принципе разницы никакой нет, единственное, что требуется — выполнять явное преобразование с типа Object в тип String.
Однако, компилятору не важно каким способом вы будете пользоваться, оба примера будут одинаково представлены в байт-коде. По сути, Generic-версия изначально представиться в (2) а потом уже будет транслирована в байт-код. Это так называемый процесc erasure(стирание).
Преимущество (1) в том, что проверка на ошибки будет выполнена компилятором на этапе компиляции, в отличии от (2), которая не будет произведена.
В результате в (3) может возникнуть Runtime error:
Exception in thread «main» java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
(3)
List words = new ArrayList();
words.add("Hello ");
words.add(1);
String s = ((String)words.get(0))+((String)words.get(1));
Wildcards
Wildcards вместе с extends. Иногда в колекцию нам требуется сохранить не только определенно одни типы, а еще и их подтипы, для этих целей и используються WildCards (можно просто Маски).
Давайте рассмотрим пример:
interface Collection<E> {
...
public boolean addAll(Collection<? extends E> c);
...
}
Метод addAll() позволяет добавлять элементы одной колекции в другую. Выражение “? extends E” означает, что мы можем добавлять подтипы типа Е в коллекцию.
List<Number> nums = new ArrayList<Number>();
List<Integer> ints = Arrays.asList(1, 2);
List<Double> dbls = Arrays.asList(2.78, 3.14);
nums.addAll(ints);
nums.addAll(dbls);
assert nums.toString().equals("[1, 2, 2.78, 3.14]");
В данном примере мы создали пустой список, в который добавили список элементов Integer и Double. Это действие не вызовет ошибок компиляции, так как ints имеет тип List, что является подтипом Collection<? extends Number>. То же самое с dbls.
WildCards вместе с super. Так же, как и c extends, WildCard с super имеет вид: “? super T “, пример c Generic методом:
public static <T> void copy(List<? super T> dst, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dst.set(i, src.get(i));
}
}
//-------------------------------------------------------------------------
List<Object> objs = Arrays.<Object>asList(2, 3.14, "four");
List<Integer> ints = Arrays.asList(5, 6);
Collections.copy(objs, ints);
assert objs.toString().equals("[5, 6, four]");
“? super T “ означает, что dst список может вместить в себе элементы, которые являются супертипами типа Т.
The Get and Put principle. Вопрос: когда же нам использовать маски с super и маски с extends? Правило очень простое: используйте extends, когда нужно получить значения со структуры, и super, когда нужно поместить значания в структуру. Если же добавляем и извлекаем элементы, то вообще не используем wildcards.
По сути, использование данного принципа можно увидеть в предыдущем примере.
public static <T> void copy(List<? super T> dest, List<? extends T> src)
Итоги
— Generics позволяют уменьшить количество ошибок в преобразованиях типов в коде за счет проверки их во время компиляции;
— Делают код чище и удобно читаемым.
Литература: M. Naftalin, P. Wadler — Java Generics and Collections
PS: Статья не претендует на полное руководство, время от времени буду ее дополнять.
Комментарии (3)
bromzh
15.01.2016 11:35+6С разморозкой! А у нас уже 11 лет прошло с релиза 5-й версии. Вы удивитесь, столько нового в java с тех пор появилось…
eaa
Это все, что Вы хотели рассказать о Generics?
В самой тоненькой книжке и то больше написано, не говоря уже о документации…
Pyshankov
Не все же сразу. Generics не могут быть покрыты в теме одной статьи. Время от времени буду дополнять ее.