Коли користуватися загальними методами та коли використовувати загальну картку?


122

Я читаю про загальні методи з OracleDocGenericMethod . Я досить розгублений у порівнянні, коли йдеться про те, коли використовувати wild-card і коли використовувати загальні методи. Цитування з документа.

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) {
    ...
}

Документ відмовляє від другої декларації та сприяє використанню першого синтаксису? Яка різниця між першою та другою деклараціями? Здається, що обидва роблять те саме?

Чи може хтось поставити світло на цю область.

Відповіді:


173

Є певні місця, де підстановки та параметри типу роблять те саме. Але є й певні місця, де вам доведеться використовувати параметри типу.

  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)

це не буде працювати, як очікувалося. У 2-му випадку ви можете пройти List<Integer>і 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

Список літератури:


1
Це дивна відповідь. Це не пояснює, для чого взагалі потрібно використовувати ?. Ви можете переписати його як "загальнодоступна статична <T1 розширює число, T2 розширює номер> недійсна копія (Список <T1> dest, Список <T2> src), і в цьому випадку стане очевидним, що відбувається.
кан

@kan. Ну це справжнє питання. Ви можете використовувати параметр типу для примусового використання одного типу, але ви не можете цього зробити з символами. Використання двох типів для параметра типу - різна річ.
Rohit Jain

1
@benz. Ви не можете визначити нижні межі в Listпараметрі типу type. List<T super Integer>недійсний і не компілюється.
Rohit Jain

2
@benz. Запрошуємо вас :) Я настійно пропоную вам пройти посилання, яке я опублікував наприкінці. Це найкращий ресурс на Generics, який ви отримаєте.
Rohit Jain

3
@ jorgen.ringen <T extends X & Y>-> кілька меж.
Rohit Jain

12

Розглянемо наступний приклад із програмування Java Programming Джеймса Гослінга 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
}

Обидва вищевказані методи мають однаковий функціонал. Отже, що є кращим? Відповідь друга. Власними словами автора:

"Загальне правило - використовувати символи, коли ви можете, тому що код із символами загального типу, як правило, читабельніший, ніж код з декількома параметрами типу. Вирішуючи, чи потрібна вам змінна типу, запитайте себе, чи використовується ця змінна тип для відновлення двох або більше параметрів, або співвідносити тип параметра з типом повернення. Якщо відповідь "ні", то підказки має бути достатньо. "

Примітка. У книзі наведено лише другий метод, а назва параметра параметра "S" замість "T". Першого методу немає в книзі.


я проголосував за цитату книги, це прямо і стисло
Курапіка

9

У вашому першому запитанні: це означає, що якщо існує зв'язок між типом параметра і типом повернення методу, то використовуйте загальний.

Наприклад:

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

Тут ви витягуєте частину Т, дотримуючись певних критеріїв. Якщо Т 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параметр. Цей список може містити кілька елементів різних класів до тих пір, поки всі вони поширюють базовий клас Т.

якщо у вас були:

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. */

1
List<? extends Fruit> basket = new ArrayList<? extends Fruit>();Не є дійсним синтаксисом. Ви повинні інстанціювати ArrayList без меж.
Арнольд Пісторій

Не можна додати яблуко до кошика у наведеному вище прикладі, оскільки кошик може бути списком груш. Неправильний приклад AFAIK. І не компілюється також.
Khanna111

1
@ArnoldPistorius Це мене бентежило. Я перевірив документацію API ArrayList і в ній підписаний конструктор ArrayList(Collection<? extends E> c). Чи можете ви пояснити мені, чому ви це сказали?
Курапіка

@Kurapika Можливо, я використовував стару версію Java? Коментар був опублікований майже 3 роки тому.
Арнольд Пісторій

2

Метод підстановки також є загальним - ви можете назвати його з деяким діапазоном типів.

<T>Синтаксис визначає ім'я змінної типу. Якщо змінна типу має будь-яке використання (наприклад, у впровадженні методу або як обмеження для іншого типу), то має сенс називати її, інакше ви могли б використовувати її ?як анонімну змінну. Отже, виглядає як просто скоромовка.

Більше того, ?синтаксис неможливо уникнути, коли ви оголошуєте поле:

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

3
Хіба це не повинно бути коментарем?
Buhake Sindi

@BuhakeSindi Вибачте, що незрозуміло? Чому -1? Я думаю, це відповідає на питання.
кан

2

Я спробую відповісти на ваше питання по черзі.

Хіба ми не вважаємо, що підказки (Collection<? extends E> c);також підтримують різновид поліморфізму?

Ні. Причина полягає в тому, що обмежений підстановочний код не має визначеного типу параметра. Це невідомо. Все, що він «знає», - це те, що «стримування» має такий типE (що б не було визначено). Отже, він не може перевірити та виправдати, чи відповідає вказане значення обмеженому типу.

Отже, не має сенсу мати поліморфні форми поведінки на підстановках.

Документ відмовляє від другої декларації та сприяє використанню першого синтаксису? Яка різниця між першою та другою деклараціями? Здається, що обидва роблять те саме?

Перший варіант краще в цьому випадку, як Tзавжди обмежений, і source, безумовно, матиме значення (невідомих), що підкласи T.

Отже, припустимо, що ви хочете скопіювати весь список номерів, перший варіант буде

Collections.copy(List<Number> dest, List<? extends Number> src);

src, по суті, може приймати List<Double>і List<Float>т. д., оскільки існує верхня межа параметризованого типу, знайденого в dest.

2-й варіант змусить вас прив’язати Sдо кожного типу, який ви хочете скопіювати, як-от так

//For double 
Collections.copy(List<Number> dest, List<Double> src); //Double extends Number.

//For int
Collections.copy(List<Number> dest, List<Integer> src); //Integer extends Number.

As S- це параметризований тип, який потребує прив'язки.

Я сподіваюся, що це допомагає.


Чи можете ви пояснити, що ви маєте на увазі в своєму останньому абзаці
benz

Той, що заявляє 2-й варіант, змусить вас зв’язати один ...... чи можете ви детальніше розробити це
benz

<S extends T>заявляє, що Sце параметризований тип, який є підкласом T, тому для нього потрібен параметризований тип (без підстановки), який є підкласом T.
Buhake Sindi

2

Ще одна відмінність, яка тут не вказана.

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o); // correct
    }
}

Але наступне призведе до помилки часу компіляції.

static <T> void fromArrayToCollection(T[] a, Collection<?> c) {
    for (T o : a) {
        c.add(o); // compile time error
    }
}

0

Наскільки я розумію, існує лише один випадок використання, коли підкреслювальні строго потрібні (тобто можна висловити щось, що ви не можете виразити, використовуючи явні параметри типу). Це коли потрібно вказати нижню межу.

Окрім цього, все ж подстановочні символи служать для написання більш короткого коду, як це описано в наступних твердженнях у згаданому вами документі:

Загальні методи дозволяють використовувати параметри типу для вираження залежностей між типами одного або декількох аргументів методу та / або його типу повернення. Якщо такої залежності немає, не слід застосовувати загальний метод.

[...]

Використання підстановних знаків чіткіше і стисліше, ніж декларування явних параметрів типу, і тому слід віддавати перевагу, коли це можливо.

[...]

Відмінні знаки також мають перевагу в тому, що вони можуть використовуватися поза підписами методів, як типи полів, локальних змінних та масивів.


0

В основному -> Запропоновані підстановки застосовують загальну інформацію на рівні параметрів / аргументів негенеричного методу. Примітка. Він також може бути виконаний у genericMethod за замовчуванням, але тут замість? ми можемо використовувати сам T.

пакетна дженерика;

public class DemoWildCard {


    public static void main(String[] args) {
        DemoWildCard obj = new DemoWildCard();

        obj.display(new Person<Integer>());
        obj.display(new Person<String>());

    }

    void display(Person<?> person) {
        //allows person of Integer,String or anything
        //This cannnot be done if we use T, because in that case we have to make this method itself generic
        System.out.println(person);
    }

}

class Person<T>{

}

SO wildcard має такі конкретні випадки використання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.