Tengo el siguiente registro:
public record A(List<String> list) { public A { this.list = new ArrayList<>(list); } }Pero me sale este error de compilación:
No se puede asignar un valor a la variable final
Por otro lado esto compila:
public record A(List<String> list) { public A { list = new ArrayList<>(list); } }¿Por qué es esto?
La razón se da en JEP 395: Expedientes (énfasis mío):
El constructor canónico puede declararse explícitamente con una lista de parámetros formales que coincidan con el encabezado del registro, como se muestra arriba. También se puede declarar de forma más compacta, eliminando la lista de parámetros formales. En un constructor canónico tan compacto, los parámetros se declaran implícitamente, y los campos privados correspondientes a los componentes del registro no se pueden asignar en el cuerpo, sino que se asignan automáticamente al parámetro formal correspondiente (this.x = x;) al final del constructor . El formato compacto ayuda a los desarrolladores a concentrarse en validar y normalizar parámetros sin el tedioso trabajo de asignar parámetros a los campos.
En otras palabras, con el constructor canónico compacto, está actualizando el parámetro del constructor, y ese parámetro del constructor se asigna al campo en el código generado por el compilador.
Esto significa que declarar
public record A(List<String> list) { public A { this.list = new ArrayList<>(list); } }en esencia genera:
public record A(List<String> list) { public A(List<String> list) { this.list = new ArrayList<>(list); this.list = list; } } Que intenta asignar el campo de list dos veces. Esto no está permitido para los campos finales.
Por otro lado, el siguiente código:
public record A(List<String> list) { public A { list = new ArrayList<>(list); } }Resultados en:
public record A(List<String> list) { public A(List<String> list) { list = new ArrayList<>(list); this.list = list; } } Lo cual está perfectamente bien ya que el campo de list solo se asigna una vez.
Cuando hace referencia a this.list, implica la propiedad del objeto/clase actual, y en los registros, todos los accesorios son finales
es similar a
final String s = "ABC";ahora si lo intentas
s = "PQR";te dará el mismo error porque 's' es final
public record A(List<String> list){ public A{ this.list=new ArrayList<>(list); } }aunque puedes hacer esto
public record A(List<String> list){ public A(List<String> list){ this.list=new ArrayList<>(list); } }registros 'todas las propiedades son definitivas, no puede reasignar el valor al registro, esa es una razón para usar registros
si quieres reasignar valores usa clases
la razón por la que no recibe un error en la segunda parte es porque está creando un constructor compacto. Lo que realmente debe hacer es
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") } } }en otras palabras, cuando inicialice un nuevo registro, simplemente establecerá los valores de los parámetros en sus propiedades y cuando intente reasignar usando esta palabra clave, definitivamente arrojará un error ya que sus propiedades son definitivas.
y para el segundo bloque de código, solo está asignando valor a su 'lista' de parámetros, no está cambiando las propiedades de su clase
Espero que esto responda tu pregunta.
para más detalles entra aquí