Подстановочные знаки по сравнению с общими методами

Есть ли какое-либо практическое различие между следующими подходами для печати всех элементов в диапазоне?

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
13
задан fredoverflow 2 May 2010 в 09:44
поделиться

3 ответа

В вашем примере универсальный тип используется ровно в одном месте подписи. В этом сценарии тип T не имеет преимущества перед подстановочным знаком для вызывающего . В вашем примере тип также не имеет преимущества для разработчика метода.

Я считаю, что вызывающему абоненту легче понять версию с подстановочными знаками, поскольку в ней явно сказано «Меня вообще не волнует тип» .

В вашем примере проверка действительно лишняя. Это потребовалось бы, если бы T был ограничен, как в T extends Number . Затем требуется контрольная таблица для Number , поскольку локальная переменная x будет иметь тип Number , но Iterator.next () метод по-прежнему возвращает Объект . Похоже, компилятор Java не пытается оптимизировать приведение типов. JIT, вероятно, сделает это во время выполнения.

ОБНОВЛЕНИЕ:

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

Пограничный случай - это когда подпись имеет тип только в одном месте, но реализация требует, чтобы он был универсальным типом, а не подстановочным знаком. Подумайте о методе void swap (List list, int a, int b) . Ему нужно удалить элементы из списка и вернуть их.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){
    ...
}
7
ответ дан 2 December 2019 в 01:31
поделиться

Второй более гибкий. Лучший пример: он что-то говорит о типе. Будет ли это полезно для вас, зависит от того, что делает функция.

Второй показывает его полезность, когда вы хотите что-то вернуть из метода:

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 .

2
ответ дан 2 December 2019 в 01:31
поделиться

Не совсем. Результирующий байт-код должен быть практически идентичным.

0
ответ дан 2 December 2019 в 01:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: