Я натрапив на такий код Java:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
Яка різниця між усіма трьома вищезазначеними та чим вони називають цей тип оголошень класу чи інтерфейсу на Java?
Я натрапив на такий код Java:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
Яка різниця між усіма трьома вищезазначеними та чим вони називають цей тип оголошень класу чи інтерфейсу на Java?
Відповіді:
Ну, немає різниці між першими двома - вони просто використовують різні назви для параметра типу ( E
або T
).
Третя не є дійсною заявою - ?
використовується як підстановка, яка використовується при наданні аргументу типу , наприклад, List<?> foo = ...
засоби, що foo
посилаються на список певного типу, але ми не знаємо, що.
Все це - дженерики , що є досить величезною темою. Ви можете дізнатися про це за допомогою наступних ресурсів, хоча є і більше доступних:
T
і E
- вони просто ідентифікатори. Можна написати, KeyValuePair<K, V>
наприклад. ?
Хоча має особливе значення.
Це більше умовності, ніж будь-що інше.
T
мається на увазі як Тип E
мається на увазі як Елемент ( List<E>
: список елементів) K
є ключем (в a Map<K,V>
) V
є значення (як повернене значення або відображене значення) Вони повністю взаємозамінні (незважаючи на конфлікти в одній і тій же декларації).
Попередні відповіді пояснюють параметри типу (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()
обмежень компілятор може бути впевнений, що тип є безпечним.
Змінна типу <T> може бути будь-яким непримітивним типом, який ви вказуєте: будь-який тип класу, будь-який тип інтерфейсу, будь-який тип масиву або навіть інша змінна тип.
Найбільш часто використовувані назви параметрів типу:
У Java 7 дозволено інстанціювати так:
Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6
Найбільш часто використовувані назви параметрів типу:
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
компілятор зробить захоплення для кожного підключення (наприклад, знак питання у списку), коли він складається з функції:
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())
}