Record's elements unavailable inside methods called on constructors
Remi Forax
forax at univ-mlv.fr
Sun Jun 12 20:55:46 UTC 2022
Hi Victor,
i think you are misunderstanding how a canonical compact constructor works.
The Java language specification 8.10.4.2 [1] says:
"After the last statement, if any, in the body of the compact constructor has completed normally (§14.1), all component fields of the record class are implicitly initialized to the values of the corresponding formal parameters. The component fields are initialized in the order that the corresponding record components are declared in the record header. "
The component fields are initialized *after* the call to checkNulls(), that why you are seeing the "x" not yet initialized inside checkNulls().
regards,
Rémi
[1] https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.10.4.2
----- Original Message -----
> From: "Victor Williams Stafusa da Silva" <victorwssilva at gmail.com>
> To: "discuss" <discuss at openjdk.java.net>
> Sent: Sunday, June 12, 2022 10:30:42 PM
> Subject: Record's elements unavailable inside methods called on constructors
> Sorry if this is the wrong mail list or has already been answered before,
> but I couldn't find any relevant information. I was playing around with
> record classes:
>
> public class MyRecordTest {
>
> public record Foo1(String x) {
> public Foo1 {
> if (x == null) throw new IllegalArgumentException("null
> Foo1.x");
> }
> }
>
> public record Foo2(String x) {
> public Foo2 {
> checkNulls();
> }
>
> private void checkNulls() {
> if (x == null) throw new IllegalArgumentException("null
> Foo2.x");
> }
> }
>
> public record Foo3(String x) {
> public Foo3 {
> x = x + x;
> }
> }
>
> public static void main(String... args) {
> System.out.println(new Foo3("a").x());
> try {
> new Foo1("a");
> System.out.println("Foo 1 is ok.");
> } catch (Exception x) {
> x.printStackTrace(System.out);
> }
> try {
> new Foo2("a");
> System.out.println("Foo 2 is ok.");
> } catch (Exception x) {
> x.printStackTrace(System.out);
> }
> }
> }
>
> Running the above code gives the following output:
>
> aa
> Foo 1 is ok.
> java.lang.IllegalArgumentException: null Foo2.x
> at MyRecordTest$Foo2.checkNulls(MyRecordTest.java:15)
> at MyRecordTest$Foo2.<init>(MyRecordTest.java:11)
> at MyRecordTest.main(MyRecordTest.java:34)
>
> It seems that instance methods called inside the record's constructor can't
> see its fields. This is at least an undesirable gotcha, and is a
> side-effect from the way that the record values are implemented, since they
> are still mutable inside the record's constructor and written only after
> the given constructor implementation finishes.
>
> Ok, so, what is my point? My point is that this behavior is either not
> documented or poorly documented. The java.lang.Record class javadocs should
> warn about calling instance methods inside the record constructor or
> leaking out the "this" reference (including by indirect means through
> instance methods called in the constructor).
>
> Since it promises to be immutable, and the field attribution is handled by
> the compiler, many people (including me until a few minutes ago) assumed
> that the record instance values were already ready to use when the
> programmer's custom constructor's code starts to run and that it would be
> impossible to observe a field of the record's value mutating. However, it
> does mutate once: it starts out with nulls and zeros and are mutated by the
> assignments injected in the end of the constructor by the compiler, and not
> in the beginning of the constructor as many people would assume. And,
> although the javadocs does state that the values can be changed in the
> constructor, it still does not make it clear what are the side-effects or
> the not-so-obvious consequences of that.
More information about the discuss
mailing list