Яка різниця між "E", "T" і "?" для дженерики Java?


261

Я натрапив на такий код Java:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

Яка різниця між усіма трьома вищезазначеними та чим вони називають цей тип оголошень класу чи інтерфейсу на Java?


1
Я сумніваюся, що є якась різниця. Я думаю, що це лише назва цього параметра параметра. І чи є останній навіть дійсним?
CodesInChaos

Відповіді:


231

Ну, немає різниці між першими двома - вони просто використовують різні назви для параметра типу ( Eабо T).

Третя не є дійсною заявою - ?використовується як підстановка, яка використовується при наданні аргументу типу , наприклад, List<?> foo = ...засоби, що fooпосилаються на список певного типу, але ми не знаємо, що.

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


1
Схоже, посилання на PDF порушено. Я виявив , що , як видається, скопіювати тут , але я не можу бути 100% впевнений , так як я не знаю , що оригінал виглядав.
Іван

2
@John: Так, це один. Відредагуватимемо посилання, чи то одне, чи то Oracle ...
Джон Скіт

Чи є щось, крім T, E і? використовується в дженериках? Якщо так, що вони і що вони означають?
sofs1

1
@ Sofs1: Там нічого особливого Tі E- вони просто ідентифікатори. Можна написати, KeyValuePair<K, V>наприклад. ?Хоча має особливе значення.
Джон Скіт

215

Це більше умовності, ніж будь-що інше.

  • T мається на увазі як Тип
  • Eмається на увазі як Елемент ( List<E>: список елементів)
  • Kє ключем (в a Map<K,V>)
  • V є значення (як повернене значення або відображене значення)

Вони повністю взаємозамінні (незважаючи на конфлікти в одній і тій же декларації).


20
Буква між <> - це лише ім'я. Те, що ви описуєте у своїй відповіді, - це лише умовності. Це навіть не повинно бути одним великим літером; ви можете використовувати будь-яке ім’я, яке вам подобається, так само, як ви можете давати класи, змінні тощо будь-яке ім’я.
Jesper

Більш детальний і зрозумілий опис доступний у цій статті oracle.com/technetwork/articles/java/…
fgul

6
Ви не пояснили знаку питання. Захищений.
shinzou

129

Попередні відповіді пояснюють параметри типу (T, E та ін.), Але не пояснюють підстановку "?" Або відмінності між ними, тому я вирішу це питання.

По-перше, просто, щоб було зрозуміло: параметри підстановки та типу не однакові. Якщо параметри типу визначають різновид змінної (наприклад, T), яка представляє тип для області, підстановка не робить: підстановка просто визначає набір допустимих типів, який ви можете використовувати для загального типу. Без будь-якого обмеження ( extendsабо super) підстановка означає "використовувати тут будь-який тип".

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

public void foo(List<?> listOfAnyType) {...}  // pass a List of any type

ніколи

public <?> ? bar(? someType) {...}  // error. Must use type params here

або

public class MyGeneric ? {      // error
    public ? getFoo() { ... }   // error
    ...
}

Це стає більш заплутаним, де вони перетинаються. Наприклад:

List<T> fooList;  // A list which will be of type T, when T is chosen.
                  // Requires T was defined above in this scope
List<?> barList;  // A list of some type, decided elsewhere. You can do
                  // this anywhere, no T required.

Що можливо з визначеннями методів, багато перекривається. Наступні, функціонально, ідентичні:

public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething)  {...}

Отже, якщо є перекриття, навіщо використовувати те чи інше? Іноді, чесно кажучи, просто стиль: деякі люди кажуть, що якщо вам не потрібен парам типу, вам слід використовувати підстановку просто, щоб зробити код простішим / читабельнішим. Одне головне відмінність, яке я пояснив вище: парами типу визначають змінну типу (наприклад, T), яку ви можете використовувати в іншому місці; wildcard не робить. Інакше існують дві великі відмінності між типом парам і підстановкою:

Параметри типу можуть мати кілька обмежуючих класів; підстановка не може:

public class Foo <T extends Comparable<T> & Cloneable> {...}

Підстановка може мати нижні межі; Параметри типу не можуть:

public void bar(List<? super Integer> list) {...}

У вищесказаному List<? super Integer>визначається Integerнижня межа на підстановці, тобто тип списку повинен бути цілим чи надтиповим типом Integer. Обмеження загального типу - це те, що я хочу детально висвітлити. Якщо коротко, це дозволяє визначити, які типи можуть бути родовими. Це дає можливість лікувати дженерики поліморфно. Наприклад, з:

public void foo(List<? extends Number> numbers) {...}

Ви можете передати List<Integer>, List<Float>, List<Byte>і т.д. для numbers. Без обмеження типів це не спрацює - саме так і є дженерики.

Нарешті, ось визначення методу, який використовує підстановку, щоб зробити щось, на що я не думаю, що ви можете зробити інший спосіб:

public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
    numberSuper.add(elem);
}

numberSuperможе бути списком чисел або будь-яким супертипом числа (наприклад, List<Object>), а також elemповинен бути числом або будь-яким підтипом. З урахуванням усіх .add()обмежень компілятор може бути впевнений, що тип є безпечним.


"public void foo (Список <? розширює число> числа) {...}" повинен "розширюється" бути "супер"?
1a1a11a

1
Ні. Суть цього прикладу полягає в тому, щоб показати підпис, який поліморфно підтримує Список числа та підтипи Числа. Для цього ви використовуєте "розширення". Тобто, "передайте мені список чисел або все, що розширює число" (Список <Ініціатор>, Список <Поплавок>, що завгодно). Метод, подібний до цього, може потім повторити список і, для кожного елемента, "e", виконати, наприклад, e.floatValue (). Не має значення, який підтип (розширення) номера ви передасте - ви завжди зможете ".floatValue ()", оскільки .floatValue () - це метод числа.
Хокі Паркер

У вашому останньому прикладі "Список <? Супер номер>" може бути просто "Список <Число>", оскільки метод не дозволяє нічого більш загального.
jessarah

@jessarah nope. Можливо, мій приклад незрозумілий, але я згадую в прикладі, що adder () міг би взяти список <Object> (Object - це суперклас числа). Якщо ви хочете, щоб це могло зробити, він повинен мати підпис "Список <? Супер номер>". В цьому і полягає суть "супер".
Хокі Паркер

2
Ця відповідь дуже гарна в поясненні відмінностей між символами підстановки та типовими параметрами, з цією відповіддю має бути одне спеціальне запитання. Останнім часом я заглиблююсь у дженерики, і ця відповідь мені дуже допомогла зібрати речі, багато точних відомостей, коротше, дякую!
Testo Testini

27

Змінна типу <T> може бути будь-яким непримітивним типом, який ви вказуєте: будь-який тип класу, будь-який тип інтерфейсу, будь-який тип масиву або навіть інша змінна тип.

Найбільш часто використовувані назви параметрів типу:

  • Е - Елемент (широко використовується рамками колекцій Java)
  • К - Ключ
  • N - номер
  • T - Тип
  • V - значення

У Java 7 дозволено інстанціювати так:

Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6

3

Найбільш часто використовувані назви параметрів типу:

E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types

Ви побачите ці імена, які використовуються в API Java SE


2

компілятор зробить захоплення для кожного підключення (наприклад, знак питання у списку), коли він складається з функції:

foo(List<?> list) {
    list.put(list.get()) // ERROR: capture and Object are not identical type.
}

Однак загальний тип типу V був би нормальним, і це зробило загальний метод :

<V>void foo(List<V> list) {
    list.put(list.get())
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.