Обобщённые методы
© |
NetCracker Technology Corp. |
/ |
Синтаксис
.
Синтаксис объявления обобщённого метода:
<R, T , T , ..., E extends Throwable> R method(T p , T p , ...) throws E {
...
}
Синтаксис вызова обобщённого метода:
instance.<Class , Class >method(arg , arg ); Collections.<String>emptyList();
List<Integer> list = Lists.newArrayList(); // Вывод типов
Никаких ограничений на область видимости или на статичность/нестатичность у обобщённых методов нет, это самые обычные методы.
Также обобщённые методы могут иметь переменное число параметров, в том числе, с типом параметра метода:
public <T> void doSomething(T... args) { ... }
.Дженерики + массивы = много подводных граблей.
© |
NetCracker Technology Corp. |
/ |
Применение обобщённых методов
.
Меньше, чем у классов, но всё равно достаточно:
. обобщённые фабричные методы:
public static <T extends Enum<T>> EnumSet<T> allOf(Class<T> clazz); public static <T> ImmutableList<T> of(T p , T p , T... other); public <T> T getInstance(Key<T> key);
. методы-преобразователи:
public static <T> Iterable<T> skip(Iterable<T> iterable, int n); public static <F, T> Iterable<T> transform(
Iterable<F> iterable, Function<F, T> function
);
. обобщённые утилитные методы:
public static <T extends Comparable<T>> T max(Iterable<T> xs) { ... }
В основном такие методы статические. Для нестатических тоже есть
.применения, но достаточно специфические (контейнеры DI, особые API).
© |
NetCracker Technology Corp. |
/ |
Wildcards
© |
NetCracker Technology Corp. |
/ |
Дженерики и наследование
.
В стандартной библиотеке Java имеется примерно следующее:
public abstract class Number { ... |
} |
|
public final class Integer extends Number { |
... } |
|
public final class Double extends Number { ... |
} |
Вопрос: является ли List<Integer> подклассом List<Number>? Можно ли сохранить объект List<Integer> в переменную типа List<Number>?
List<Integer> integers = new ArrayList<>();
List<Number> numbers = integers; // Что произойдёт?
.
© |
NetCracker Technology Corp. |
/ |
Дженерики и наследование
.
В стандартной библиотеке Java имеется примерно следующее:
public abstract class Number { ... |
} |
|
public final class Integer extends Number { |
... } |
|
public final class Double extends Number { ... |
} |
Вопрос: является ли List<Integer> подклассом List<Number>? Можно ли сохранить объект List<Integer> в переменную типа List<Number>?
List<Integer> integers = new ArrayList<>();
List<Number> numbers = integers; // Что произойдёт?
Ответ: нельзя. Код выше не компилируется.
.
© |
NetCracker Technology Corp. |
/ |
Дженерики и наследование
.
В стандартной библиотеке Java имеется примерно следующее:
public abstract class Number { ... |
} |
|
public final class Integer extends Number { |
... } |
|
public final class Double extends Number { ... |
} |
Вопрос: является ли List<Integer> подклассом List<Number>? Можно ли сохранить объект List<Integer> в переменную типа List<Number>?
List<Integer> integers = new ArrayList<>();
List<Number> numbers = integers; // Что произойдёт?
Ответ: нельзя. Код выше не компилируется.
Это защита от следующей ошибки:
List<Integer> integers = new ArrayList<>(); List<Number> numbers = integers; numbers.add((Double) . );
System.out.println(integers.get( )); // FFFFUUUUUU~~~
.
© |
NetCracker Technology Corp. |
/ |
Дженерики и наследование
.
Рассмотрим код:
public class Person { public String name; public int age;
}
public class Employee extends Person { public double salary;
}
public class PersonByNameComparator implements Comparator<Person> { @Override public int compare(Person left, Person right) {
return left.name.compareTo(right.name);
}
}
public class EmployeeBySalaryComparator implements Comparator<Employee> { @Override public int compare(Employee left, Employee right) {
return left.salary - right.salary;
}
}
.
© |
NetCracker Technology Corp. |
/ |
Дженерики и наследование
.
Рассмотрим код:
public class Person { ... }
public class Employee extends Person { ... }
public class PersonByNameComparator implements Comparator<Person> { ... } public class EmployeeBySalaryComparator implements Comparator<Employee> { ... }
public final class Sorting { private Sorting() { }
public static <T> void sort(List<T> list, Comparator<T> comparator) { ... }
}
List<Employee> employees = ...;
Sorting.sort(employees, new EmployeeBySalaryComparator());
Вопрос: является ли Comparator<Person> подклассом Comparator<Employee>?
Можно ли сохранить объект Comparator<Person> в переменную типа
Comparator<Employee>?
Sorting.sort(employees, new PersonByNameComparator());
.
© |
NetCracker Technology Corp. |
/ |
Дженерики и наследование
.
Рассмотрим код:
public class Person { ... }
public class Employee extends Person { ... }
public class PersonByNameComparator implements Comparator<Person> { ... } public class EmployeeBySalaryComparator implements Comparator<Employee> { ... }
public final class Sorting { private Sorting() { }
public static <T> void sort(List<T> list, Comparator<T> comparator) { ... }
}
List<Employee> employees = ...;
Sorting.sort(employees, new EmployeeBySalaryComparator());
Вопрос: является ли Comparator<Person> подклассом Comparator<Employee>?
Можно ли сохранить объект Comparator<Person> в переменную типа
Comparator<Employee>?
Sorting.sort(employees, new PersonByNameComparator());
.Ответ: нельзя. Код выше не компилируется.
© |
NetCracker Technology Corp. |
/ |
Ковариантность и контравариантность
.
Будем считать, что T T , если T является подклассом T или T совпадает с T .
В общем случае в Java если T T , то ни C<T > C<T >, ни C<T > C<T >.
Но на практике очень часто можно считать, что, например, List<Integer> List<Number> (если мы не меняем список). Аналогично,
можно считать, что Comparator<Person> Comparator<Employee>.
Свойство обобщённого типа T |
T |
) C<T > C<T > называется |
ковариантностью. |
|
|
Свойство обобщённого типа T |
T |
) C<T > C<T > называется |
контравариантностью. |
|
|
Если не выполняется ни первое, ни второе, то тип называется
инвариантным.
.
© |
NetCracker Technology Corp. |
/ |
Ковариантность и контравариантность
.
Ковариантность
Контравариантность
.
© |
NetCracker Technology Corp. |
/ |
Вариантность в Java — wildcards
.
Все обобщённые типы в Java инвариантны.
Однако существует возможность временно «включить» нужную вариантность для конкретной переменной:
List<Integer> integers = Arrays.asList( , , );
List<? extends Number> numbers = integers; // ok!
public final class Sorting { private Sorting() { } public static <T> void sort(
List<T> list,
Comparator<? super T> comparator ) { ... }
}
List<Employee> employees = ...;
Sorting.sort(employees, new PersonByNameComparator()); // ok!
Такой синтаксис называется wildcards with upper/lower bound —
подстановочные символы, шаблонные символы, шаблоны [и т.д.;
.нормального перевода нет :(] с верхней/нижней границей.
© |
NetCracker Technology Corp. |
/ |
Суть wildcard’ов
.
Важный момент: если у нас есть объявление вроде
List<? extends Number> numbers = ...;
это не значит, что надо рассуждать так:
.
Ага, список чего-то наследующего Number, значит, я могу положить туда
любой Number:
numbers.add((Integer) );
.numbers.add((Double) . );
Это неверно! Такой код даже не скомпилируется.
На самом деле, List<? extends Number> следует читать как «список элементов какого-то типа, наследующего Number, но мы не знаем, какого именно». Поэтому мы не можем добавлять новые элементы в список.
То же самое верно для wildcard’ов вида Consumer<? super T>.
.Wildcard-типы также называются экзистенциальными.
© |
NetCracker Technology Corp. |
/ |
Unbounded wildcards
.
Есть ещё один тип wildcard’ов — unbounded wildcards:
List<?> unknowns = new ArrayList<String>();
Class<?> clazz = this.getClass();
В большинстве случаев тип вида List<?> можно понимать как
List<? extends Object>, то есть, это wildcard с максимально возможной верхней границей. Даже IntelliJ IDEA, как правило, предлагает поменять
List<? extends Object> ! List<?>.
Отличия есть только в сильно продвинутых вариантах использования wildcard’ов, например, связанных со стиранием типов.
.
© |
NetCracker Technology Corp. |
/ |
Когда можно применять wildcard’ы
.
Использование типового параметра в обобщённом классе можно классифицировать по месту его использования:
. только в возвращаемых значениях методов:
public interface Provider<T> { T get();
}
. только в аргументах методов:
public interface Comparator<T> { int compare(T left, T right);
}
. и в аргументах, и в возвращаемых значениях:
public interface List<T> { T get(int index);
void add(T element);
}
.
© |
NetCracker Technology Corp. |
/ |
Когда можно применять wildcard’ы
.
Обобщённые классы первого типа называются producer’ами (производителями?), второго типа — consumer’ами (потребителями?).
Wildcard’ы с верхней границей (extends) можно безопасно применять только для типов-производителей:
Provider<? extends Number> provider = new Provider<Integer>() { ... };
Wildcard’ы с нижней границей (super) можно безопасно применять только для типов-потребителей:
Comparator<? super Employee> comparator = new Comparator<Person>() { ... };
Для типов-одновременно производителей и потребителей в общем случае wildcard’ы применять нельзя:
List<Number> numbers = new ArrayList<Number>();
Этой идиоме соответствует мнемоническое правило — «producer
extends, consumer super» (PECS).
.
© |
NetCracker Technology Corp. |
/ |
Когда можно применять wildcard’ы
.
Для типов вроде List<T> можно использовать wildcard’ы, но дозированно.
Например, если метод будет только итерироваться по списку, то extends-wildcard использовать можно:
public void printNames(List<? extends Person> persons) { for (Person person : persons) {
System.out.println(person.name);
}
}
С super-wildcard’ами сложнее (для списков), потому что список «в большей степени» ковариантен, чем контравариантен.
.
© |
NetCracker Technology Corp. |
/ |
Когда нельзя применять wildcard’ы
.
Wildcard-типы не являются полноценными типами, они больше похожи на интерфейсы.
Wildcard-типы нельзя
явно создавать: new ArrayList<? extends Number>();
использовать при создании массива:
new ArrayList<? extends Number>[ ]; исключение — неограниченные
wildcard’ы;
применять при обработке исключений;
использовать в instanceof: x instanceof List<? extends CharSequence>;
исключение — неограниченные wildcard’ы;
использовать в классовых литералах: List<?>.class.
.
© |
NetCracker Technology Corp. |
/ |
Когда нужно применять wildcard’ы
.
В основном ограниченные wildcard’ы нужно использовать, чтобы ваше API не было слишком ограниченным. Типичный пример — проход по коллекции. Из Effective Java:
public void pushAll(Iterable<E> src) { // Плохо for (E e : src) push(e);
}
public void pushAll(Iterable<? extends E> src) { // Хорошо for (E e : src) push(e);
}
public void popAll(Collection<E> dst) { // Плохо while (!isEmpty()) dst.add(pop());
}
public void popAll(Collection<? super E> dst) { // Хорошо while (!isEmpty()) dst.add(pop());
}
Отдельный случай — неограниченные wildcard’ы. Их следует использовать вместо сырых типов всегда, если нет необходимости
интеграции с [очень] старым кодом.
.
© |
NetCracker Technology Corp. |
/ |