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?
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.
When you refer this.list it implies property of the current object/class, and in records all the props are final
it's similar to
final String s = "ABC";
now if you try
s = "PQR";
it will give you the same error cause 's' is final
public record A(List<String> list){
public A{
this.list=new ArrayList<>(list);
}
}
although you can do this
public record A(List<String> list){
public A(List<String> list){
this.list=new ArrayList<>(list);
}
}
records' all the properties are final, you cannot reassign value to record that's one reason to use records
if you want to reassign values use classes
reason why you are not getting error in second part is because you're creating compact constructor, What you should really do is
public record A(List<String> list){
public A(List<String> list){
this.list=new ArrayList<>(list);
}
public A{
if(!Objects.nonNull(list){
throw new IllegalArgumentException("List must not be empty")
}
}
}
in other words, when you initialize new record, it will simply set that parameter values to it's properties and when you try to reassign using this keyword it will definitely throw error since it's properties are final
and for second code block you are just assigning value to your parameter 'list' you are not changing your class properties
I hope this answers your question.
for more details go here