英文:
Java record constructor invisible through reflection
问题
I'm playing with Java 15's new records feature, and how it interacts with reflection. I've run into some strange behavior, 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 me what's going on here? What do I need to do to see a record's constructor in Java 15 through reflection?
英文:
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?
答案1
得分: 13
getConstructors()
仅返回public
构造函数。使用getDeclaredConstructors()
以获取所有构造函数。
您的声明record MainRecord(int i, String s) {}
缺少public
修饰符,因此它创建了一个非public
类和一个非public
构造函数。参见JLS15-preview,§8.10.4
隐式声明的规范构造函数具有与记录类R相同的访问修饰符,除非记录类缺少访问修饰符,在这种情况下,规范构造函数具有包访问权限。
这确实与JDK 14 preview不同。JDK 15 preview文档的开头说:
这些更改与Java SE 14中Records的第一个预览版相同,但有以下不同:
...
- 8.10.4 删除了规范构造函数必须是public的要求。任何访问修饰符必须至少提供与记录类一样多的访问权限。如果隐式声明了规范构造函数,则其访问修饰符与记录类相同。
似乎,在JShell中创建的顶级类隐式为public
。
> jdk-15\bin\jshell --enable-preview
| 欢迎使用JShell -- 版本 15
| 要获取简介,请键入:/help intro
jshell> record JShellRecord(int i, String s) {}
| 已创建记录 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>
英文:
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>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论