Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
3. generics.pdf
Скачиваний:
10
Добавлен:
09.06.2015
Размер:
641.09 Кб
Скачать

Обобщённые методы

©

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.

/