Tengo una situación en la que quiero que las instancias de registro para un tipo específico solo se puedan crear usando un método de fábrica en una clase separada dentro del mismo paquete. La razón de esto es que antes de crear el registro necesito realizar una cantidad significativa de validación.
El registro pretende ser un portador de datos tonto de sus campos validados, pero la validación no puede tener lugar en el constructor del registro porque necesitamos acceso a algunos objetos validadores elaborados para realizar la validación.
Dado que pasar los objetos del validador al constructor de registros significaría que formarían parte del estado del registro, significa que no podemos usar el constructor de registros para realizar la validación del registro.
Entonces extraje la validación a su propia fábrica y codifiqué algo como esto (una clase de fábrica y un registro en el mismo paquete):
package some.package; // imports..... @Component class SomeRecordFactory { private final SomeValidator someValidator; private final SomeOtherValidator someOtherValidator; // Rest of the fields // .... // constructor // .... public SomeRecord create(...) { someValidator.validate(....); someOtherValidator.validate(....); // .... other validation return new SomeRecord(...); } } package some.package; public record SomeRecord(...) { /* package-private */ SomeRecord { } }Por alguna razón, lo anterior no funciona con IntelliJ quejándose:
Compact constructor access level cannot be more restrictive than the record access level (public)Puedo evitar el problema usando una clase normal (que permite un solo constructor privado de paquete) pero me gustaría modelar los datos con mayor precisión como un registro.
¿Por qué existe esta restricción para los registros? ¿Hay planes para eliminar esta restricción en el futuro?
P: ¿Por qué existe esta restricción para los registros?
No hay una justificación explícita para esa decisión en JEP 359 o en JLS, pero creo que está implícita en este extracto de JEP:
"Debido a que los registros hacen el reclamo semántico de ser portadores transparentes de sus datos..."
Un "portador transparente" significa (para mí 1 ) que los registros están diseñados para tener un límite mínimo de abstracción. Restringir el acceso de un constructor implica (para mí) un límite de abstracción adicional.
Además, sospecho que los constructores de registros con modificadores de acceso más restrictivos podrían impedir o complicar los casos de uso previstos para registros en futuras versiones de Java.
De todos modos, mi opinión es que si quieres cosas sofisticadas como esa, deberías declarar una clase en lugar de un registro.
1 - Transparente es lo opuesto a opaco, y los tipos de datos abstractos suelen ser opacos por diseño. Obviamente, esta es solo mi opinión sobre lo que querían decir los autores de JEP.
P: ¿Hay planes para eliminar esta restricción en el futuro?
No estoy enterada de nada. No hay errores de Java abiertos (públicos) o RFE sobre esto.
De hecho, todos los errores de JDK relacionados con este tema fueron para garantizar que las especificaciones de Java 15+ aclararan la restricción. No hay ninguna sugerencia de que la restricción haya ocurrido por accidente o por descuido.
Hice la pregunta en la lista de correo ámbar ( http://mail.openjdk.java.net/pipermail/amber-dev/2020-December.txt ).
Se planteó la pregunta:
¿Cuál es exactamente la razón por la que el constructor canónico debe tener el mismo acceso que el registro?
Y la respuesta dada fue (énfasis mío):
Los registros son tuplas nombradas, se definen solo por sus componentes, de manera transparente, es decir, sin encapsulamiento. Desde una tupla se puede acceder al valor de cada componente y desde todos los valores de los componentes se puede crear una tupla. La idea es que, en un método, si puede ver un registro, puede crearlo. Por lo tanto, el constructor canónico tiene la misma visibilidad que el propio registro.
Entonces, la restricción existe para cumplir con el objetivo del diseño y el hecho de que si alguien tiene una instancia de un registro, debería poder deconstruirlo y luego reconstruirlo con el constructor canónico. Y, por supuesto, como corolario, esto requiere que el constructor canónico tenga el mismo acceso que el propio registro.