Java Basics: Generic

Generic

Generic wildcards

  • unbound - List<?>. Фактически, любой тип данных кроме примитивов. Используется в том случае, если надо разрешить использовать любой тип данных:
1
2
3
List<String> list = ...;
List<Object> objects = list; // compile error
List<?> validObjects = list;
  • upper-bound - List<? extends Number>. Любой наследник, а так же сам целевой тип. Для данного типа (де-факто) запрещена любая вставка (кроме null) - для вставки используется List<? super Number>. При этом, первичная вставка допускается:
1
2
3
4
5
6
Map<String, ? extends String> map = new HashMap() {{
put("x", "1");
put("y", "2");
}};
map.putAll(Map.of(...)); // compile error
map.put("z", "3"); // compile error

Перелистывание (например, через for-each) возможно с определением целевого типа:

1
2
for(String val : map.values()) { ... }
for(Map.Entry<String, ? extends String> entry : map.entrySet()) {}
  • lower-bound - List<? super Number>. Любой класс, от которого целевой наследуется, или сам целевой. Данный вид wildcard не может быть использован в методе. Вставка доступна, а получение - доступно только с принудительным преобразованием:
1
2
3
4
5
6
List<? super Number> nums = new ArrayList<>() {{
add(1);
}};
nums.add(2);
Number num1 = nums.get(1); // compile error
Number num2 = (Number) nums.get(1);

Ковариантность

Если объект, или массив объектов, или какой-то типа данных может быть применен к другому (родительскому) типу, то это называется ковариантность:

1
2
3
String[] a = {"a", "b", "c"};
Object[] b = a;
b[0] = 123; // ошибка ArrayStoreException;

PECS (Producer Extends Consumer Super)

Был описан выше, но для повторения:

  • если <? extends ...> - это только чтение (Producer); для коллекций он ничего в себя не принимает, только отдает;
  • если <? super ...> - это только хранение (Consumer); в случае коллекций он только принимает и ничего не отдает.

Важно! В случае unbound, это де-факто <? extends Object>, а значит допускается только чтение! Если предполагается и читать и записывать, лучше не использовать wildcards.

Переменные типа

1
public <T extends Comparable<T>> T method(...) { ... }

В примере выше присутствует конструкция, называемая, recursive bound - т.е. когда при определении Generic где-то в нем используется целевой класс - т.е. в нашем случае T используется в Comparable<T>. Это действие ограничено только правилами наследования. Так же, в примере выше нельзя использовать <T super ...>.

Multiple bound

Множественные ограничения, которые накладывают дополнительные ограничения на тип:

1
public <T extends String & Comparable<? super T>> T method(...) {...}

Важно! Второй и последующими границами могут быть только интерфейсы. Например:

1
public <T extends Number & Comparable<T> & CharSequence>void test(T item)

Type erasure

В действительности, никаких типизаций не существует в скомпилированном коде - компилятор перед использованием Generic типа выполняет принудительный type-cast:

1
2
List<String> list = ...;
String s = (String) list.get(0); // в коде: list.get(0);

В таком случае могут возникать непредвиденные ситуации:

1
2
3
4
5
6
public class MyClass implements Comparable<MyClass> {
@Override
public int compareTo(MyClass that) {...}

public int compareTo(Object that) {...}
}

В примере выше, компилятор из метода 1 создает фоновый метод compareTo(Object), который мы пытаемся создать. И поскольку фоновый и созданный имеют одинаковую сигнатуру, они будут друг с другом конфликтовать.

Причем, если порядок определения методов будет соответствовать указанному выше, ошибка будет при определении класса с указанием дубликата метода compareTo. Если дефиницию методов поменять местами, то ошибка будет на первом методе (с сигнатурой Object), сигнализирующая о дублировании метода compareTo.

Method ref.

Используется, как правило, в функциональных интерфейсах как средство для быстрого вызова метода без загромождения кода. Существует 4 вида ссылок на методы:

  • Статические. Передача параметра осуществляется в целевой статический метод класса.
1
Consumer<List<Integer>> sortMethod = Collections::sort;
  • Вызов для объекта
1
2
Var str = "abc";
Predicate<String> checker = str::startsWith;
1
2
Random rnd = new Random();
Supplier<Integer> generated = rnd::nextInt;
  • Метод класса
1
2
Predicate<String> checker = String::isEmpty;
BiPredicate<String, String> checker = String::startsWith;
  • Конструктор
1
2
3
Supplier<List<String>> listGenerator = ArrayList::new;
Function<Integer, List<String>> initialListGenerator = ArrayList::new;
initialListGenerator.apply(20); // == new ArrayList(20)
 Comments
Comment plugin failed to load
Loading comment plugin