Java generics bounds type

Являются ли следующие две подписи одинаковыми?

public static <T> void work(Class<T> type, T instance);

и

public static <T, S extends T> void work(Class<T> type, S instance);

Ответ 1

Нет, две подписи не совпадают. Из Java Language Spec, глава 8:

Два метода имеют одну и ту же подпись, если они имеют одинаковые имена и типы аргументов.

Два объявления метода или конструктора M и N имеют одинаковые типы аргументов, если выполняются все следующие условия:

  • Они имеют одинаковое количество формальных параметров (возможно, ноль)
  • Они имеют одинаковое количество параметров типа (возможно, ноль)

...

Поскольку ваши два метода не имеют одинакового количества параметров типа, подписи не совпадают.

В практических случаях, когда методы вызываются с неявными параметрами типа, они могут рассматриваться как взаимозаменяемые. Но это только на исходном уровне, никогда на двоичном уровне. Другими словами, если у вас была версия типа work() с одним типом в классе Foo и она была вызвана методом в классе Bar, а затем вы переключились на версию с двумя типами параметров work() и перекомпилировать Foo, вам также потребуется перекомпилировать Bar.

Изменить

@onepotato спрашивает:

Если у них нет одинаковых подписей, то почему, когда я копирую и вставляю их в один класс Eclipse, скажите мне, что они имеют одну и ту же подпись метода?

Существует разница между двумя сигнатурами, которые равны, и двумя сигнатурами, конфликтующими ( "эквивалентными" ). Две подписи конфликтуют, если одна из них является подсудностью другой. Это объясняется далее в том же разделе:

Две сигнатуры метода m1 и m2 эквивалентны переопределению, если m1 является поднаклейкой m2 или m2, является поднапряжением m1.

Ошибка компиляции для объявления двух методов с эквивалентными эквивалентами в классе.

Подпись метода m1 является подсигналом сигнатуры метода m2, если:

  • m2 имеет ту же подпись, что и m1, или
  • подпись m1 такая же, как стирание (§4.6) подписи m2.

Ответ 2

Просто глядя на байт-код, мы видим, что они этого не делают:

Для первого:

// access flags 0x9
// signature <T:Ljava/lang/Object;>(Ljava/lang/Class<TT;>;TT;)V
// declaration: void work<T>(java.lang.Class<T>, T)
public static work(Ljava/lang/Class;Ljava/lang/Object;)V
 L0
  LINENUMBER 86 L0
  RETURN
 L1
  LOCALVARIABLE type Ljava/lang/Class; L0 L1 0
  // signature Ljava/lang/Class<TT;>;
  // declaration: java.lang.Class<T>
  LOCALVARIABLE instance Ljava/lang/Object; L0 L1 1
  // signature TT;
  // declaration: T
  MAXSTACK = 0
  MAXLOCALS = 2

Для второго:

// access flags 0x9
// signature <T:Ljava/lang/Object;S:TT;>(Ljava/lang/Class<TT;>;TS;)V
// declaration: void work<T, ST>( extends java.lang.Class<T>, S)
public static work(Ljava/lang/Class;Ljava/lang/Object;)V
 L0
  LINENUMBER 86 L0
  RETURN
 L1
  LOCALVARIABLE type Ljava/lang/Class; L0 L1 0
  // signature Ljava/lang/Class<TT;>;
  // declaration: java.lang.Class<T>
  LOCALVARIABLE instance Ljava/lang/Object; L0 L1 1
  // signature TS;
  // declaration: S
  MAXSTACK = 0
  MAXLOCALS = 2

Ответ 3

Для любого набора аргументов, если допустимый выбор T существует для

<T> void work(Class<T> type, T instance)

то действительный выбор T и S существует для

<T, S extends T> void work(Class<T> type, S instance)

и наоборот.

Итак, с теоретической точки зрения они эквивалентны. Однако из-за ограниченного вывода может случиться так, что если аргументы типа не заданы, они будут компилироваться, а другие - не для определенных аргументов. В таком случае всегда можно явно указать допустимый набор аргументов типа для случая, который не компилируется, чтобы его компилировать.

Поскольку они эквивалентны, API всегда должен выбирать более простой вид, т.е. без S.