В чем разница между Java vararg с прямым типом и универсальным символом c через extends? - PullRequest
3 голосов
/ 02 февраля 2020

Чем отличаются следующие 2 Java описания методов:

public <S extends Item> void withExtra1(S... extra) {
    Collections.addAll(pool, extra);
}

и:

public void withExtra2(Item... extra) {
    Collections.addAll(pool, extra);
}

Ответы [ 3 ]

4 голосов
/ 02 февраля 2020

Это не так, поскольку S будет стерто до Item, обе подписи заканчиваются на Item....

2 голосов
/ 02 февраля 2020

Java spe c говорит

Java компилятор должен выводить информацию подписи generi c для любого класса, интерфейса, конструктора или члена, чья подпись generi c в Java язык программирования будет включать ссылки на переменные типа или параметризованные типы.

Если вы проверите байт-код, то увидите, что метод с обобщениями имеет другую сигнатуру:

  public <S extends Item> void withExtra1(S...);
    descriptor: ([LItem;)V
    flags: (0x0081) ACC_PUBLIC, ACC_VARARGS
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: getfield      #2                  // Field pool:Ljava/util/List;
         4: aload_1
         5: invokestatic  #3                  // Method java/util/Collections.addAll:(Ljava/util/Collection;[Ljava/lang/Object;)Z
         8: pop
         9: return
      LineNumberTable:
        line 9: 0
        line 10: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   LItem;
            0      10     1 extra   [LItem;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            0      10     1 extra   [TS;
    Signature: #23                          // <S:LItem;>([TS;)V


  public void withExtra2(Item...);
    descriptor: ([LItem;)V
    flags: (0x0081) ACC_PUBLIC, ACC_VARARGS
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: getfield      #2                  // Field pool:Ljava/util/List;
         4: aload_1
         5: invokestatic  #3                  // Method java/util/Collections.addAll:(Ljava/util/Collection;[Ljava/lang/Object;)Z
         8: pop
         9: return
      LineNumberTable:
        line 13: 0
        line 14: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   LItem;
            0      10     1 extra   [LItem;

#23 в пуле констант: #23 = Utf8 <S:LItem;>([TS;)V

Вы можете получить доступ к этой информации во время выполнения, используя отражение:

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Item {

    List<Item> pool;

    public static void main(String[] args) {
        for (var m : Item.class.getMethods())
            System.out.println(m.getName() + " " +
                    Arrays.toString(m.getGenericParameterTypes()));
    }


    public <S extends Item> void withExtra1(S... extra) {
        Collections.addAll(pool, extra);
    }

    public void withExtra2(Item... extra) {
        Collections.addAll(pool, extra);
    }

}

стандартный вывод:

main [class [Ljava.lang.String;]
withExtra1 [S[]]
withExtra2 [class [LItem;]
wait [long]
wait [long, int]
wait []
equals [class java.lang.Object]
toString []
hashCode []
getClass []
notify []
notifyAll []
1 голос
/ 18 февраля 2020

Две подписи эквивалентны, при условии, что вызывающая сторона не предоставляет явного свидетеля типа. Любой набор аргументов, которые могут быть приняты одной подписью, принимается другой:

  • Любой набор аргументов, который принимается первой подписью, должен состоять из выражений, имеющих тип, равный или подтип S, а поскольку S равно или является подтипом Item, все выражения имеют тип, равный или подтип Item, и принимаются второй сигнатурой.
  • Любой набор аргументов, который принимается второй сигнатурой, должен состоять из выражений, имеющих тип, равный или подтип Item, и может быть передан первой сигнатуре, если S выводится как Item (что может, поскольку Item находится в пределах S от extends Item)
...