Вопрос-ответ

When to use generic methods and when to use wild-card?

Когда использовать универсальные методы, а когда использовать подстановочную карту?

Я читаю об универсальных методах из OracleDocGenericMethod. Я довольно смущен сравнением, когда говорится, когда использовать шаблонную карту, а когда использовать универсальные методы. Цитирую из документа.


interface Collection<E> {
public boolean containsAll(Collection<?> c);
public boolean addAll(Collection<? extends E> c);
}

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


interface Collection<E> {
public <T> boolean containsAll(Collection<T> c);
public <T extends E> boolean addAll(Collection<T> c);
// Hey, type variables can have bounds too!
}

[…]
Это говорит нам о том, что аргумент типа используется для полиморфизма;
его единственный эффект заключается в том, что допускается использование множества реальных типов аргументов
на разных сайтах вызова. Если это так, то следует
использовать подстановочные знаки. Подстановочные знаки предназначены для поддержки гибкого подтипирования,
что мы и пытаемся здесь выразить.


Не кажется ли нам, что подстановочная карта, подобная (Collection<? extends E> c);, также поддерживает разновидность полиморфизма? Тогда почему использование универсального метода в этом случае считается нехорошим?

Продолжая, в нем говорится,


Универсальные методы позволяют использовать параметры типа для выражения зависимостей между типами одного или нескольких аргументов метода и / или его возвращаемого типа. Если такой зависимости нет, универсальный метод не следует использовать.


Что это значит?

Они представили пример


class Collections {
public static <T> void copy(List<T> dest, List<? extends T> src) {
...
}

[…]


Мы могли бы написать подпись для этого метода другим способом, вообще не используя подстановочные знаки:


class Collections {
public static <T, S extends T> void copy(List<T> dest, List<S> src) {
...
}

Документ не поощряет второе объявление и поощряет использование первого синтаксиса? В чем разница между первым и вторым объявлением? Кажется, что оба делают одно и то же?

Может кто-нибудь пролить свет на эту область.

Переведено автоматически
Ответ 1

Есть определенные места, где подстановочные знаки и параметры типа выполняют одно и то же действие. Но есть также определенные места, где вы должны использовать параметры типа.


  1. Если вы хотите установить некоторую взаимосвязь для различных типов аргументов метода, вы не можете сделать это с помощью подстановочных знаков, вы должны использовать параметры типа.

Взяв в качестве примера ваш метод, предположим, вы хотите убедиться, что src и dest список, передаваемый copy() методу, должен быть одного и того же параметризованного типа, вы можете сделать это с параметрами типа, например, так:

public static <T extends Number> void copy(List<T> dest, List<T> src)

Здесь вы убедитесь, что оба dest и src имеют одинаковый параметризованный тип для List. Итак, копировать элементы из src в dest безопасно.

Но, если вы продолжите изменять метод, чтобы использовать подстановочные знаки:

public static void copy(List<? extends Number> dest, List<? extends Number> src)

это будет работать не так, как ожидалось. Во втором случае вы можете передать List<Integer> and List<Float> как dest и src. Итак, перемещение элементов из src в dest больше не будет типобезопасным.
Если вам не нужна такая связь, то вы можете вообще не использовать параметры типа.

Некоторые другие различия между использованием подстановочных знаков и параметров типа заключаются в следующем:


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



  • Параметры типа поддерживают несколько границ, подстановочные знаки - нет.



  • Подстановочные знаки поддерживают как верхнюю, так и нижнюю границы, параметры типа поддерживают только верхние границы. Итак, если вы хотите определить метод, который принимает List тип Integer или это суперкласс, вы можете сделать:


     public void print(List<? super Integer> list)  // OK


но вы не можете использовать параметр типа:

     public <T super Integer> void print(List<T> list)  // Won't compile

Ссылки:

Ответ 2

Рассмотрим следующий пример из "Программирования на Java" Джеймса Гослинга, 4-е издание ниже, где мы хотим объединить 2 SinglyLinkQueue:

public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
// merge s element into d
}

public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
// merge s element into d
}

Оба вышеперечисленных метода обладают одинаковой функциональностью. Итак, какой из них предпочтительнее? Ответ 2-й. По собственным словам автора :

"Общее правило заключается в использовании подстановочных знаков, когда вы можете, потому что код с подстановочными знаками обычно более удобочитаем, чем код с параметрами нескольких типов. Решая, нужна ли вам переменная типа, спросите себя, используется ли эта переменная типа для связи двух или более параметров или для связи типа параметра с возвращаемым типом. Если ответ отрицательный, то подстановочного знака должно быть достаточно. "

Примечание: В книге указан только второй метод, а имя параметра типа - S вместо 'T'. Первого метода в книге нет.

Ответ 3

В вашем первом вопросе: это означает, что если существует связь между типом параметра и типом возвращаемого метода, тогда используйте универсальный.

Например:

public <T> T giveMeMaximum(Collection<T> items);
public <T> Collection<T> applyFilter(Collection<T> items);

Здесь вы извлекаете некоторые из T, следуя определенным критериям. Если T равно Long, ваши методы будут возвращать Long и Collection<Long>; фактический тип возвращаемого значения зависит от типа параметра, поэтому полезно и рекомендуется использовать универсальные типы.

Когда это не так, вы можете использовать подстановочные типы:

public int count(Collection<?> items);
public boolean containsDuplicate(Collection<?> items);

В этих двух примерах, независимо от типа элементов в коллекциях, возвращаемыми типами будут int и boolean.

В ваших примерах:

interface Collection<E> {
public boolean containsAll(Collection<?> c);
public boolean addAll(Collection<? extends E> c);
}

эти две функции будут возвращать логическое значение независимо от типов элементов в коллекциях. Во втором случае это ограничено экземплярами подкласса E.

Второй вопрос:

class Collections {
public static <T> void copy(List<T> dest, List<? extends T> src) {
...
}

Этот первый код позволяет передавать гетерогенный List<? extends T> src в качестве параметра. Этот список может содержать несколько элементов разных классов, если все они расширяют базовый класс T.

если бы у вас были:

interface Fruit{}

и

class Apple implements Fruit{}
class Pear implements Fruit{}
class Tomato implements Fruit{}

вы могли бы сделать

List<? extends Fruit> basket = new ArrayList<? extends Fruit>();
basket.add(new Apple());
basket.add(new Pear());
basket.add(new Tomato());
List<Fruit> fridge = new ArrayList<Fruit>();

Collections.copy(fridge, basket);// works

С другой стороны

class Collections {
public static <T, S extends T> void copy(List<T> dest, List<S> src) {
...
}

ограничивать List<S> src принадлежностью к одному определенному классу S, который является подклассом T. Список может содержать элементы только одного класса (в данном экземпляре S) и никакого другого класса, даже если они также реализуют T . Вы не сможете использовать мой предыдущий пример, но вы могли бы сделать:

List<Apple> basket = new ArrayList<Apple>();
basket.add(new Apple());
basket.add(new Apple());
basket.add(new Apple());
List<Fruit> fridge = new ArrayList<Fruit>();

Collections.copy(fridge, basket); /* works since the basket is defined as a List of apples and not a list of some fruits. */
Ответ 4

Метод подстановочного знака также является универсальным - вы могли бы вызывать его с некоторым диапазоном типов.

В <T> синтаксисе определяется имя переменной типа. Если переменная типа имеет какое-либо применение (например, в реализации метода или в качестве ограничения для другого типа), то имеет смысл дать ей имя, в противном случае вы могли бы использовать ?, как анонимную переменную. Итак, похоже, это просто короткий путь.

Более того, ? синтаксиса невозможно избежать при объявлении поля:

class NumberContainer
{
Set<? extends Number> numbers;
}
java generics