The bytecode doesn't seem to have any indication of such.
Without anything in the bytecode to indicate this, the only other alternative would be something in the reflection API which was specifically added for this purpose, e.g. a getCanonicalConstructor
method which works via inference, exactly like your solution of checking the argument types does. There wasn't anything like that added, though.
In my experiments, the primary constructor always occurs last so it would probably work if you just took the last element of getDeclaredConstructors()
, but you can't rely on that since it's an implementation detail. (Maybe as a performance optimization you might decide to use that information to change your implementation to iterate through the list backwards though)
Javap output is below. For the purpose of brevity I just kept the X(String i, String j)
and removed the other 2. I've removed some of the method implementations which should be plainly irrelevant even if you're not familiar with the format.
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, locals=3, 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