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

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

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 включает дополнительный проверочный листинг в Object (см. строку 16), что для меня кажется довольно глупым - не все-таки объект Object?

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

Ответ 1

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

Я нахожу, что подстановочная версия легче понять для вызывающего, так как он явно говорит "Меня вообще не интересует тип" .

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

UPDATE:

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

Приграничный случай заключается в том, что подпись имеет тип только в одном месте, но для реализации требуется, чтобы это был общий тип, а не шаблон. Подумайте о методе void swap(List<T> 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){
    ...
}

Ответ 2

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

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

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<?>.

Ответ 3

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