直接类型的Java vararg与通过扩展的通配符泛型之间有什么区别?

时间:2020-02-02 15:15:23

标签: java generics variadic-functions

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

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

和:

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

3 个答案:

答案 0 :(得分:4)

它们不会,因为S将被擦除为Item,所以两个签名都以Item...结尾。

答案 1 :(得分:2)

Java规范说

Java编译器必须为任何类,接口,构造函数或成员输出通用签名信息,这些类,接口,构造函数或成员的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 []

答案 2 :(得分:1)

这两个签名是等效的,假设调用方未提供明确的类型见证人。一个签名可以接受的任何一组参数都被另一个签名接受:

  • 第一个签名接受的任何参数集都必须包含类型等于S或子类型为S且等于{{ 1}},所有表达式的类型都等于Item或子类型,并且被第二个签名接受。
  • 第二个签名接受的任何参数集必须由类型等于Item或子类型Item的表达式组成,并且如果推断出S则可以传递给第一个签名成为Item(由于ItemS的{​​{1}}范围之内,所以可以)