Generic. Oбобщенные типы

 

Generic (дженерики — обобщенные типы)

Параметризация типов

  • Класс или интерфейс может быть объявлен обобщенным (generic):
    • class List< T >
    • class Map< K, V >
    • class NumberList< T extends Number >
    • class X< T extends Y & Z >
  • Здесь T, K, V - параметры типа, а сам класс задает параметризованный тип.
  • Параметр типа может использоваться внутри нестатических членов класса:
    • Тип поля, параметра, переменной
    • Возвращаемый тип метода
    • Подставляться параметром другого типа
  • Для использования параметризованного типа необходимо предоставить все параметры (с помощью явных не примитивных типов, других параметров типов либо маски)

Пример параметризованного класса

import java.util.NoSuchElementException;

static class Option<T> {
    T value;

    public Option(T value) {
        this.value = value;
    }

    public T get() {
        if (value == null) throw new NoSuchElementException();
        return value;
    }

    public void set(T newValue) {
        value = newValue;
    }

    public T orElse(T other) {
        return value == null ? other : value;
    }

    public boolean isPresent() {
        return value != null;
    }
}

Работа с параметризованным классом

public class Main {
    public static void main(String[] args) {
        Option<String> present = new Option<>("yes");
        Option<String> absent = new Option<>(null);
        System.out.println(present.isPresent());
        System.out.println(present.get());
        System.out.println(absent.isPresent());
        System.out.println(absent.orElse("no"));
    }
}

Маскировочный символ (wildcard)

(“?” = “? extends Object”)

public class Main {
    public static void main(String[] args) {
        /* "?" вариативность в точке использования */
        Option<?> present = new Option<>("yes");
        System.out.println(present.isPresent());
        Object value = present.get();
        System.out.println(value);
        /* set only null */
        present.set(null);
        /* not work */
        present.set(123); 
    }
}
public class Main {
    public static void main(String[] args) {
        Option<? extends Number> number = new Option<>(123);
        Number n = number.get();
        /* set only null */
        present.set(null);
        /* not work */
        present.set(987);
    }
}

Наследуем

class NumberOption<N extends Number> extends Option<N> {
    public NumberOption(N value) {
        super(value);
    }
}
class IntegerOption extends Option<Integer> {
    public IntegerOption(Integer value) {
        super(value);
    }
}

class Main {
    public static void main(String[] args) {
        NumberOoption<?> number = new NumberOption<>(123);
        Number n = number.get();
        /* not work */
        number.set(987);
        
        IntegerOption integer = new IntegerOption(123);
        Integer i = integer.get();
        integer.set(987);
        
        number = integer;
    }
}

Параметризованные методы и конструкторы

class OptionUtils {
    static <T> void setNotNull(Option<? super T> option, T value) {
        if (value == null) throw new IllegalArgumentException();
        option.set(value);
    }
}
class Main {
    public static void main(String[] args) {
        OptionUtils.<Number>setNotNull(n, 123);
    }
}

!!! Не использовать массивы параметризованных объектов

Varargs + generics:

Полезно, но осторожно

class Main {
    /* Проверка. Встречается ли value в списке options */
    @SafeVarargs
    static <T> boolean isOneOf(T value, T... options) {
        for (T option : options) {
            if (Objects.equals(value, option)) return true;
        }
        return false;
    }

    public static void main(String[] args) {
        isOneOf(option, new Option("foo"), new Option("bar"));
    }
}