Java vararg with direct type vs wildcard generic via extends 之间有什么区别?

What is the difference between Java vararg with direct type vs wildcard generic via extends?

以下 2 Java 方法声明有何不同:

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

和:

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

它们不会,因为 S 将被擦除为 Item,两个签名最终都为 Item...

Java 规范说

A Java compiler must output generic signature information for any class, interface, constructor or member whose generic signature in the Java programming language would include references to type variables or parameterized types.

如果您检查字节码,您可以看到带有泛型的方法具有不同的签名:

  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 []

这两个签名是等价的,假设调用者不提供显式类型见证。一个签名可以接受的任何一组参数都可以被另一个签名接受:

  • 第一个签名接受的任何参数集必须包含类型等于 S 或子类型的表达式,并且由于 S 等于或子类型Item,所有表达式的类型都等于Item或者是Item的子类型,并被第二个签名接受。
  • 第二个签名接受的任何参数集,必须包含类型等于 Item 或子类型 Item 的表达式,并且可以传递给第一个签名,如果 S被推断为 Item(它可以,因为 Itemextends ItemS 范围内)