Reputation: 1561
Java records are used to implement shallowly immutable data carrier types. If the constructor accepts mutable types then we should implement explicit defensive copying to enforce immutability. e.g.
record Data(Set<String> set) {
public Data(Set<Thing> set) {
this.set = Set.copyOf(set);
}
}
This is mildly annoying - we have to
Ideally what we want to express is the following:
record SomeRecord(ImmutableSet<Thing> set) {
}
or
record SomeRecord(Set<Thing> set) {
public SomeRecord {
if(set.isMutable()) throw new IllegalArgumentException(...);
}
}
Here we use a fictitious ImmutableSet
type and Set::isMutable
method, in either case the record is created using the canonical constructor - nice. Unfortunately it doesn't exist!
As far as I can tell the built-in collection types (introduced in Java 10) are hidden, i.e. there is no way to determine if a collection is immutable or not (short of trying to modify it).
We could use Guava but that seems overkill when 99% of the functionality is already in the core libraries. Alternatively there are Maven plug-ins that can test classes annotated as immutable, but again that's really a band-aid than a solution.
Is there any pure-Java mechanism to enforce a immutable collection?
Upvotes: 23
Views: 6909
Reputation: 72854
You can do it already, the arguments of the constructor are mutable:
record SomeRecord(Set<Thing> set) {
public SomeRecord {
set = Set.copyOf(set);
}
}
A related discussion mentions the argument are not final in order to allow such defensive copying. It is still the responsibility of the developer to ensure the rule on equals()
is held when doing such copying.
Upvotes: 40