Java记录(record)构造函数在通过反射(reflection)时不可见。

huangapple go评论83阅读模式
英文:

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>

huangapple
  • 本文由 发表于 2020年9月21日 22:07:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/63993967.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定