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
(它可以,因为 Item
在 extends Item
的 S
范围内)
以下 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
(它可以,因为Item
在extends Item
的S
范围内)