Сегодня хотел бы рассказать вам о generics в Java и его некоторых особенностях. Данная возможность появилась в Java с версии J2SE5 и с тех пор полностью изменила работу с Collections. С помощью данного подхода мы можем использовать различные структуры и контейнеры с разными типами данных, не изменяя их описания.

Впрочем, рассмотрим простой пример с 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)


  1. eaa
    15.01.2016 11:20
    +8

    Это все, что Вы хотели рассказать о Generics?
    В самой тоненькой книжке и то больше написано, не говоря уже о документации…


    1. Pyshankov
      15.01.2016 14:14
      -4

      Не все же сразу. Generics не могут быть покрыты в теме одной статьи. Время от времени буду дополнять ее.


  1. bromzh
    15.01.2016 11:35
    +6

    С разморозкой! А у нас уже 11 лет прошло с релиза 5-й версии. Вы удивитесь, столько нового в java с тех пор появилось…