пятница, 14 февраля 2014 г.

Шпаргалка по Generic

Generic тип - это параметризированный класс или интерфейс.

Формат определения generic класса:
class name<T1, T2, ..., Tn> { /* ... */ }

Переменная типа может быть любым не примитивным типом: какой-либо тип класса, интерфейса, массива.

Тип параметра должен начинаться с большой буквы, чтобы проще было отличать от обычных параметров. Вот общепринятые названия типов, используемые в Generic:
  • E - элемент (Element)  (используется исключительно the Java Collections Framework)
  • K - ключ (Key)
  • N - число (Number)
  • T - тип (Type)
  • V - значение (Value)
  • S,U,V etc. - 2ой, 3ий, 4ый типы

Преимущества кода с использованием Generic по сравнению с кодом без Generic:
  1. Более строгая проверка типов во время компиляции.
  2. Отсутствии необходимости приведения типов.
  3. Возможность реализации общих алгоритмов, не завязанных на конкретных типах.
Сырой тип или (Raw-type) - это название Generic класса или интерфейса без каких-либо параметров.

Пример.

Box<Integer> intBox = new Box<>(); //класс Box, параметризированный Integer
                                   //это просто пример создания экземпляра класса                                   //с типом Generic

//А если дальше по коду для того же класса class Box<T> создать вот такой эземпля//р это уже будет сырой тип
Box someBox = new Box();  

Но не Generic классы или интерфейсы это не сырой тип !!!!!

Raw-типы остались для совместимости с кодом, написанным с JDK < 5 версии (с 5 версии JDK и появились Generic). Сырые типы возвращают объект типа Object.

Для обратной совместимости возможно присвоить параметризрованный тип к Raw-типу. А если наоборот, то компилятор выдаст предупреждение!!!

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;               // OK

Box rawBox = new Box();           // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox;     // warning: unchecked conversion

Generic методы
Существует возможность использовать не только Generic-типы, но и generic-методы. Такими методами могут быть как статические, так и не статические методы.

public class Util {
    // Generic static method
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

А вызов метода будет выглядеть вот так вот:
boolean same = Util.<Integer, String>compare(p1, p2);

Конструкторы тоже могут быть Generic и даже определять свой собственный тип параметра как в Generic, так и в не Generic классах.

class MyClass<X> {
  <T> MyClass(T t) {
    // ...
  }
}

Bounded Type Parameters (не знаю как красиво это обозвать, пусть будут параметры с ограничениями на тип)

Формат определения Bounded Type Parameter:
Тип параметра(E, или K, или N, или T и т.д.) затем слово extends и после extends "верхний" тип, то есть родительский класс, с потомками которого мы будем вызывать метод.
То есть
extends Number - значит, что мы можем использовать в качестве аргумента сам Number и все его наследники: Byte, Integer, Double, Short, Float, Long.

public class Box<T> {

    private T t;          

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }

    public <U extends Number> void inspect(U u){
        System.out.println("T: " + t.getClass().getName());
        System.out.println("U: " + u.getClass().getName());
    }

    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<Integer>();
        integerBox.set(new Integer(10));
        integerBox.inspect("some text"); // error: this is still String!
    }
}

Также можно использовать множественное ограничение типа.
<T extends B1 & B2 & B3>

!!!Однако следует помнить такую вещь, если мы ограничиваем тип несколькими классами и интерфейсами, то на первом месте должен следовать класс, а затем уже интерфейсы, иначе будет ошибка компиляции.
 
Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }

class D <T extends A & B & C> { /* ... */ }
class D <T extends B & A & C> { /* ... */ }  // ошибка компиляции

Также следует знать, что если объявлен метод с сигнатурой, то в него нельзя передать параметры типа Box<Integer>, потому что Box<Integer> не является наследником Box<Number>.
 
public void boxTest(Box<Number> n) { /* ... */ }

"Вкусные вещи" с Generic
Есть такая вещь, как алгоритм определения типа. Работает он так: если в методe, у которого в качестве параметра для аргумента Generic передается конкретный тип, например Integer, или же сразу при вызове явно указан возвращаемый параметр, то компилятор Java позволяет не дублировать дважды типы, на которых вызывается generic метод (все потому что существует алгоритм распознавания типа). Например: 

List<String> listOne = Collections.<String>emptyList();//можно так не извращаться
List<String> listOne = Collections.emptyList(); //а написать просто так

А вот пример, когда без "свидетеля типа" нельзя обойтись (свидетель типа, это как раз тот <String> сверху Collections.<String>emptyList(), который вносит дополнительную ясность при определении типа):
 
void processStringList(List<String> stringList) {
    // process stringList
}

processStringList(Collections.emptyList()); // в Java7 в этой строке будет ошибка                                            // компиляции

Что-то вроде List<Object> cannot be converted to List<String>. Так как компилятор требует в значение параметра аргумента T, то перебор по подборке типов он начинает с Object.
Чтобы избежать ошибки компиляции в Java 6-7, необходимо написать так
processStringList(Collections.<String>emptyList());

Wildcards (или символы подстановки)
В Generic коде может встречаться символ "?", который обозначает неизвестный тип.

 Wildcards может использоваться в качестве:
  1. параметра метода;
  2. поля;
  3. локальной переменной;
  4. иногда возвращаемым типом (однако не очень хорошо так поступать, лучше чтобы тип возвращаемого параметра был известен)
Wildscards не может использоваться в качестве:
  1. аргумента при вызове Generic метода;
  2. создании экземпляра Generic класса.
Wildcards используют 3 вида ограничений:
  1. Upperbounded (пример
    List<? extends Foo>
    ) (ограничения верхней границей - родителем, т.е. в качестве аргументов могут выступать как заданные после extends родители, так и их потомки. Отличий от Bounded Type Parameters не было замечено, разве что область применения у wildcards уже).
  2. Unbound (пример List<?>)(неограниченные символы подстановки, используется в методах класса Generic типа, функциональность, которых не привязана к этому типу)
  3. Lower Bounded Wildcards (пример <? super A>) (ограничения заданным типом или всеми типами родителями заданного типа)
Рассмотрим подробнее эти виды ограничений.

Unbound Wildcards

Существует 2 сценария, при которых Unbound Wildcards могут быть полезны:
  1. Если Вы пишите метод, для реализации которого достаточно функциональности класса Object.
  2. Когда код в методе Generic класса не зависит от параметра типа.
public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

List<Integer> li = Arrays.asList(1, 2, 3);
List<String>  ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);
!!!Важно понимать,что List<?> и List<Object> это не одно и то же. Имея List<Object>, в него можно вставлять объекты типа Object или любого наследника Object. В List<?> можно вставить только null.

Lower Bounded Wildcards

Приведем пример метода, который работает с Integer, a также всеми родителями Integer (Number, Object)
public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}