Search code examples
javajava-17java-record

this-keyword is not allowed to access a field in constructors of records


I have the following record:

public record A(List<String> list) {
    public A {
      this.list = new ArrayList<>(list);
    }
}

But I get this compile error:

Cannot assign a value to final variable

On the other hand this compiles:

public record A(List<String> list) {
    public A {
      list = new ArrayList<>(list);
    }
}

Why is this?


Solution

  • The reason is given in JEP 395: Records (emphasis mine):

    The canonical constructor may be declared explicitly with a list of formal parameters which match the record header, as shown above. It may also be declared more compactly, by eliding the list of formal parameters. In such a compact canonical constructor the parameters are declared implicitly, and the private fields corresponding to record components cannot be assigned in the body but are automatically assigned to the corresponding formal parameter (this.x = x;) at the end of the constructor. The compact form helps developers focus on validating and normalizing parameters without the tedious work of assigning parameters to fields.

    In other words, with the compact canonical constructor, you're updating the constructor parameter, and that constructor parameter is then assigned to the field in code generated by the compiler.

    This means that declaring

    public record A(List<String> list) {
        public A {
            this.list = new ArrayList<>(list);
        }
    }
    

    in essence generates:

    public record A(List<String> list) {
        public A(List<String> list) {
            this.list = new ArrayList<>(list);
            this.list = list;
        }
    }
    

    Which attempts to assign the list field twice. This is not allowed for final fields.

    On the other hand, the following code:

    public record A(List<String> list) {
        public A {
            list = new ArrayList<>(list);
        }
    }
    

    Results in:

    public record A(List<String> list) {
        public A(List<String> list) {
            list = new ArrayList<>(list);
            this.list = list;
        }
    }
    

    Which is perfectly fine as the list field is only assigned once.