问题描述
假设我有这样的记录(或任何其他记录):
record X(int i,int j) {
X(int i) {
this(i,0);
}
X() {
this(0,0);
}
X(String i,String j) {
this(Integer.parseInt(i),Integer.parseInt(j));
}
}
有没有办法通过反射找到这条记录的 canonical constructor,即在 RecordHeader
中隐式声明的那个?
解决方法
试试这个
static <T extends Record> Constructor<T> canonicalConstructorOfRecord(Class<T> recordClass)
throws NoSuchMethodException,SecurityException {
Class<?>[] componentTypes = Arrays.stream(recordClass.getRecordComponents())
.map(rc -> rc.getType())
.toArray(Class<?>[]::new);
return recordClass.getDeclaredConstructor(componentTypes);
}
和
Constructor<X> c = canonicalConstructorOfRecord(X.class);
X x = c.newInstance(1,2);
System.out.println(x);
输出
X[i=1,j=2]
,
这似乎有效,虽然它有点蹩脚:
List<?> componentTypes = Stream
.of(X.class.getRecordComponents())
.map(RecordComponent::getType)
.toList();
for (Constructor<?> c : X.class.getDeclaredConstructors())
if (Arrays.asList(c.getParameterTypes()).equals(componentTypes))
System.out.println(c);
打印
Test$1X(int,int)
我仍然愿意接受更好的建议。
,字节码似乎没有任何指示。
字节码中没有任何内容表明这一点,唯一的其他选择是反射 API 中专门为此目的添加的内容,例如一种通过推理工作的 getCanonicalConstructor
方法,与 your solution of checking the argument types 完全一样。不过,并没有添加类似的内容。
在我的实验中,主构造函数总是最后出现,所以如果你只取 getDeclaredConstructors()
的最后一个元素它可能会工作,但你不能依赖它,因为它是一个实现细节。 (也许作为性能优化,您可能决定使用该信息来更改您的实现以向后遍历列表)
Javap 输出如下。为简洁起见,我只保留了 X(String i,String j)
并删除了其他 2 个。我删除了一些方法实现,即使您不熟悉格式,这些实现也应该完全无关。
Classfile /tmp/5610502834030542116/classes/X.class
Last modified Apr 16,2021; size 1555 bytes
SHA-256 checksum fe06254f15d68f71f0a576d1ce19c28c2d4b9479c3b16dadc8c0e69e6ab734c4
Compiled from "Main.java"
final class X extends java.lang.Record
minor version: 0
major version: 60
flags: (0x0030) ACC_FINAL,ACC_SUPER
this_class: #8 // X
super_class: #2 // java/lang/Record
interfaces: 0,fields: 2,methods: 7,attributes: 4
Constant pool:
{
private final int i;
descriptor: I
flags: (0x0012) ACC_PRIVATE,ACC_FINAL
private final int j;
descriptor: I
flags: (0x0012) ACC_PRIVATE,ACC_FINAL
X(java.lang.String,java.lang.String);
descriptor: (Ljava/lang/String;Ljava/lang/String;)V
flags: (0x0000)
Code:
stack=3,locals=3,args_size=3
start local 0 // X this
start local 1 // java.lang.String i
start local 2 // java.lang.String j
0: aload_0
1: aload_1
2: invokestatic #16 // Method java/lang/Integer.parseInt:(Ljava/lang/String;)I
5: aload_2
6: invokestatic #16 // Method java/lang/Integer.parseInt:(Ljava/lang/String;)I
9: invokespecial #22 // Method "<init>":(II)V
12: return
end local 2 // java.lang.String j
end local 1 // java.lang.String i
end local 0 // X this
LineNumberTable:
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 this LX;
0 13 1 i Ljava/lang/String;
0 13 2 j Ljava/lang/String;
X(int,int);
descriptor: (II)V
flags: (0x0000)
Code:
stack=2,args_size=3
start local 0 // X this
start local 1 // int i
start local 2 // int j
0: aload_0
1: invokespecial #1 // Method java/lang/Record."<init>":()V
4: aload_0
5: iload_1
6: putfield #7 // Field i:I
9: aload_0
10: iload_2
11: putfield #13 // Field j:I
14: return
end local 2 // int j
end local 1 // int i
end local 0 // X this
LineNumberTable:
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 this LX;
0 15 1 i I
0 15 2 j I
MethodParameters:
Name Flags
i
j
public final java.lang.String toString();
...toString
public final int hashCode();
...hashCode
public final boolean equals(java.lang.Object);
...equals
public int i();
...getter
public int j();
...getter
}
SourceFile: "Main.java"
Record:
int i;
descriptor: I
int j;
descriptor: I
BootstrapMethods:
0: #54 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
Method arguments:
#8 X
#61 i;j
#63 REF_getField X.i:I
#64 REF_getField X.j:I
InnerClasses:
public static final #70= #66 of #68; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles