I have a situation where I want record instances for a specific type to only be creatable using a factory method in a separate class within the same package. The reason for this is because before creating the record I need to perform a significant amount of validation.
The record is intended to be a dumb-data carrier of its validated fields but the validation cannot take place in the record's constructor because we require access to some elaborate validator objects to actually perform the validation.
Since passing the validator objects to the record constructor would mean they would form part of the record state it means we cannot use the record constructor to perform the record's validation.
And so I extracted the validation out into its own factory and coded up something like this (a factory class and a record in the same package):
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 {
}
}
For whatever reason the above does not work with IntelliJ complaining:
Compact constructor access level cannot be more restrictive than the record access level (public)
I can avoid the issue by using a normal class (which allows for a single package-private constructor) but would like to more accurately model the data as a record.
Why does this restriction exist for records? Are there any plans to remove this restriction in the future?
The compiler also creates a constructor for you, called the canonical constructor. This constructor takes the components of your record as arguments and copies their values to the fields of the record class. There are situations where you need to override this default behavior.
Compact Constructors. If you want your record's constructor to do more than initialize its private fields, you can define a custom constructor for the record. However, unlike a class constructor, a record constructor doesn't have a formal parameter list; this is called a compact constructor.
A record class is a shallowly immutable, transparent carrier for a fixed set of values, called the record components. The Java language provides concise syntax for declaring record classes, whereby the record components are declared in the record header.
Record classes are final so they cannot be extended. Records also extend java. lang. Record and since Java doesn't allow multiple inheritance they cannot extend any classes or abstract classes.
I asked the question on the amber mailing list (http://mail.openjdk.java.net/pipermail/amber-dev/2020-December.txt).
The question was posed:
What exactly is the reason that the canonical constructor must have the same access as the record?
And the answer given was (emphasis added mine):
Records are named tuples, they are defined only by their components, in a transparent manner i.e. no encapsulation. From a tuple, you can access to the value of each component and from all component values, you can create a tuple. The idea is that, in a method, if you are able to see a record, you can create it. Thus the canonical constructor has the same visibility as the record itself.
So the restriction exists to comply with the design goal and fact that if someone has an instance of a record they should be able to deconstruct it and then reconstruct it with the canonical constructor. And of course as a corollary this necessitates the canonical constructor having the same access as the record itself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With