Есть ли какое-либо практическое различие между следующими подходами для печати всех элементов в диапазоне?
public static void printA(Iterable<?> range)
{
for (Object o : range)
{
System.out.println(o);
}
}
public static <T> void printB(Iterable<T> range)
{
for (T x : range)
{
System.out.println(x);
}
}
По-видимому, printB
вовлекает дополнительный проверенный бросок для Возражения (см. строку 16), который кажется довольно глупым мне - не все Объект так или иначе?
public static void printA(java.lang.Iterable);
Code:
0: aload_0
1: invokeinterface #18, 1; //InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
6: astore_2
7: goto 24
10: aload_2
11: invokeinterface #24, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
16: astore_1
17: getstatic #30; //Field java/lang/System.out:Ljava/io/PrintStream;
20: aload_1
21: invokevirtual #36; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
24: aload_2
25: invokeinterface #42, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
30: ifne 10
33: return
public static void printB(java.lang.Iterable);
Code:
0: aload_0
1: invokeinterface #18, 1; //InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
6: astore_2
7: goto 27
10: aload_2
11: invokeinterface #24, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
16: checkcast #3; //class java/lang/Object
19: astore_1
20: getstatic #30; //Field java/lang/System.out:Ljava/io/PrintStream;
23: aload_1
24: invokevirtual #36; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
27: aload_2
28: invokeinterface #42, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
33: ifne 10
36: return
В вашем примере универсальный тип используется ровно в одном месте подписи. В этом сценарии тип T
не имеет преимущества перед подстановочным знаком для вызывающего . В вашем примере тип также не имеет преимущества для разработчика метода.
Я считаю, что вызывающему абоненту легче понять версию с подстановочными знаками, поскольку в ней явно сказано «Меня вообще не волнует тип» .
В вашем примере проверка
действительно лишняя. Это потребовалось бы, если бы T
был ограничен, как в T extends Number
. Затем требуется контрольная таблица для Number
, поскольку локальная переменная x
будет иметь тип Number
, но Iterator.next ()
метод по-прежнему возвращает Объект
. Похоже, компилятор Java не пытается оптимизировать приведение типов. JIT, вероятно, сделает это во время выполнения.
ОБНОВЛЕНИЕ:
Если общий тип используется в нескольких местах, как в ответе Клетуса, у вас нет другого выбора, кроме как использовать общий тип T
, иначе компилятор не видит связи между тип параметра / тип возвращаемого значения (любые два символа подстановки различны для компилятора).
Пограничный случай - это когда подпись имеет тип только в одном месте, но реализация требует, чтобы он был универсальным типом, а не подстановочным знаком. Подумайте о методе void swap (List
. Ему нужно удалить элементы из списка и вернуть их.IIRC, Эффективная Java предлагает использовать общедоступный метод с подстановочным знаком и внутренний вспомогательный метод с типом, содержащим фактическую реализацию. Таким образом, пользователь получает простой API, а разработчик по-прежнему сохраняет безопасность типов.
public void swap(List<?> list, int a, int b){
swapHelper(list, a, b);
}
private <T> void swapHelper(List<T> list, int a, int b){
...
}
Второй более гибкий. Лучший пример: он что-то говорит о типе. Будет ли это полезно для вас, зависит от того, что делает функция.
Второй показывает его полезность, когда вы хотите что-то вернуть из метода:
public static <T> List<T> reverse(List<T> list) {
for (int i=0; i<n/2; i++) {
T value = list.get(i);
list.set(i, list.get(list.size() - i - 1));
list.set(list.size() - i = 1, value);
}
return list;
}
Возможно, это лучший пример для копирования массива, но суть в том, что вы не можете сделать то же самое с List >
.
Не совсем. Результирующий байт-код должен быть практически идентичным.