Record's elements unavailable inside methods called on constructors

Victor Williams Stafusa da Silva victorwssilva at gmail.com
Sun Jun 12 20:30:42 UTC 2022


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