I'm playing with Java 15's new records feature, and how it interacts with reflection. I've run into some strange behaviour, where I can sometimes access a record's constructor via reflection, and sometimes not. For example, given the following Java file:
Recording.java
:
public class Recording {
public static void main(String[] args) {
System.out.println("Constructors: " + MainRecord.class.getConstructors().length);
System.out.println("Methods: " + MainRecord.class.getDeclaredMethods().length);
}
record MainRecord(int i, String s) {}
}
This behaves as follows:
❯ javac --enable-preview --release 15 Recording.java
Note: Recording.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
❯ java --enable-preview Recording
Constructors: 0
Methods: 5
In other words, the call to getConstructors()
does not find any constructors (while the call to `getDeclaredMethods() does find methods). I don't understand why not, because the constructor does exist:
❯ javap Recording\$MainRecord
Compiled from "Recording.java"
final class Recording$MainRecord extends java.lang.Record {
Recording$MainRecord(int, java.lang.String);
public final java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public int i();
public java.lang.String s();
}
(Putting the record in a separate Java file gives the same results.)
However, if I do the same from JShell:
❯ jshell --enable-preview
| Welcome to JShell -- Version 15
| For an introduction type: /help intro
jshell> record JShellRecord(int i, String s) {}
| created record JShellRecord
jshell> JShellRecord.class.getConstructors().length
$2 ==> 1
So, now it does find the constructor.
Here's the Java version I'm using:
❯ java -version
openjdk version "15" 2020-09-15
OpenJDK Runtime Environment AdoptOpenJDK (build 15+36)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 15+36, mixed mode, sharing)
Compiling and running the same program from Java 14 does work:
❯ java -version
openjdk version "14.0.2" 2020-07-14
OpenJDK Runtime Environment AdoptOpenJDK (build 14.0.2+12)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 14.0.2+12, mixed mode, sharing)
❯ javac --enable-preview --release 14 Recording.java
Note: Recording.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
❯ java --enable-preview Recording
Constructors: 1
Methods: 5
I know that in Java 15, compared to Java 14, a number of restrictions have been put in place regarding reflection on records, but if I read the JEP correctly, those only apply to modification. Finding (and perhaps invoking) a constructor does not seem to apply.
Can anyone tell my what's going on here? What do I need to do to see a record's constructor in Java 15 through reflection?
As you can see, we are able to instantiate object using java.lang.reflect.Constructor at run time using reflection. In case, you have constructor parameters with primitive types, you can use .class suffix with that. If the parameter type is of int, then you can use int.class while getting constructor of that type.
It also displays whether the constructor is synthetic (compiler-generated) or of variable arity. There are two reflective methods for creating instances of classes: java.lang.reflect.Constructor.newInstance () and Class.newInstance ().
For hidden class access, Java's annotation grammar requires the class to be annotated apart from its package. More generally you can use @Jailbreak for any type of reflection:
Using records, a public constructor – with an argument for each field – is generated for us. In the case of our Person record, the equivalent constructor is: This constructor can be used in the same way as a class to instantiate objects from the record:
getConstructors()
returns public
constructors only. Use getDeclaredConstructors()
to get all constructors.
Your declaration record MainRecord(int i, String s) {}
lacks the public
modifier, so it creates a non-public
class and also a non-public
constructor. See JLS15-preview, §8.10.4
The implicitly declared canonical constructor has the same access modifier as the record class R, unless the record class lacks an access modifier, in which case the canonical constructor has package access
This does indeed differ from the JDK 14 preview. The beginning of the JDK 15 preview document says:
The changes are the same as those in the first preview of Records in Java SE 14, except for the following:
…
- 8.10.4 Removed requirement that canonical constructor must be public. Any access modifier must provide at least as much access as the record class. If a canonical constructor is implicitly declared, then its access modifier is the same as the record class.
It seems, top level classes created in JShell are implicitly public
.
> jdk-15\bin\jshell --enable-preview
| Welcome to JShell -- Version 15
| For an introduction type: /help intro
jshell> record JShellRecord(int i, String s) {}
| created record JShellRecord
jshell> JShellRecord.class.getConstructors()[0]
$2 ==> public JShellRecord(int,java.lang.String)
jshell> java.lang.reflect.Modifier.isPublic(JShellRecord.class.getModifiers())
$3 ==> true
jshell>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With