Reputation: 237
I have a java record that takes in a list
public record Zoo(List<Animal> animals ) {
public Zoo(Collection<Animal> animals) {
this(new ArrayList<>(animals));
}
...
}
However, the animals are not in sorted order, and I want to create record where animals are sorted. Is this possible in Java record?
In plain java class, I could have
public class Zoo {
...
public Zoo(List<Animal> animals) {
this.animals = animals.sort(someComparator);
}
}
Upvotes: 14
Views: 27212
Reputation: 46181
You can do the same as with your "plain Java class".
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public record Zoo(List<Animal> animals) {
/*
* The canonical constructor copies the 'animals' list so that both
* constructors have consistent behavior (i.e. they both always result
* in the list being copied). Plus, since you want to sort the list, it's
* a good idea to create a copy anyway to avoid side-effects.
*
* FIXME: Avoid double-copy when using non-canonical constructor.
*
* When the non-canonical constructor is called with a collection
* other than a list, the collection will be copied twice. This
* appears to be unavoidable, but if anyone can think of a solution
* then please let me know.
*/
// explicit canonical constructor (parameters can be omitted)
public Zoo {
animals = new ArrayList<>(animals);
animals.sort(/* TODO: Pass comparator */);
// records should typically be immutable
animals = Collections.unmodifiableList(animals);
}
// a non-canonical constructor (must delegate to canonical constructor)
public Zoo(Collection<Animal> animals) {
// avoid double-copy if 'animals' is already a List
this(animals instanceof List<Animal> list ? list : new ArrayList<>(animals));
}
}
The first constructor is an explicit declaration of the (compact1) canonical constructor. From the documentation of java.lang.Record:
A record class has the following mandated members: a canonical constructor, which must provide at least as much access as the record class and whose descriptor is the same as the record descriptor; a private final field corresponding to each component, whose name and type are the same as that of the component; a public accessor method corresponding to each component, whose name and return type are the same as that of the component. If not explicitly declared in the body of the record, implicit implementations for these members are provided.
[...]
The primary reasons to provide an explicit declaration for the canonical constructor or accessor methods are to validate constructor arguments, perform defensive copies on mutable components, or normalize groups of components (such as reducing a rational number to lowest terms.)
Note all other constructors must eventually delegate to the canonical constructor.
1. See §8.10.4.2. Compact Canonical Constructors of the Java Language Specification.
Upvotes: 19
Reputation: 144
The answer proposing to use this(..)
has the limitation that this()
must be the first statement.
To overcome that limitation, you can use the static contructor, like:
public record Zoo(List<Animal> animals) {
public static Zoo fromAnimals(Collection<Animal> animals) {
List<Animal> listAnimals = new ArrayList<Animal>(animals);
listAnimals.sort(Animal::compareTo);
// ... any other pre-processing, possibly using additional parameters
return new Zoo(listAnimals);
}
...
}
Upvotes: 2
Reputation: 559
Remember until your constructor has finished to run, your input is not final, so you can overwrite your record class constructor as follows:
public record Zoo(List<animal> animals ) {
public Zoo {
Collections.sort(animals, your_comparator);
}
}
Upvotes: 0
Reputation: 16399
To avoid the issues with List vs. Collection, why not just define the Zoo as having a Collection<Animal>
instead of a List<Animal>
?
public record Zoo(Collection<Animal> animals) {
public Zoo(Collection<Animal> animals) {
Objects.requireNonNull(animals, "animals is null");
List<Animal> list = new ArrayList<>(animals);
Collections.sort(list);
this.animals = list;
}
}
If there is client code that really needs animals()
to return a List
rather than a Collection
, you could add a custom accessor:
public List<Animal> asList() {
return (List<Animal>) animals;
}
This cast is safe since the canonical constructor will never assign any type to animals
that is not an ArrayList
. I stuck with ArrayList
since that was in your original example, but records are immutable by default, so having a member that is mutable is somewhat questionable. If mutability is not needed, you can replace the last line of the constructor with
this.animals = List.copyOf(list);
Or even replace the last three lines of the constructor with
this.animals = animals.stream().sorted().toList();
Upvotes: 5
Reputation: 1521
You could try to use the Stream API:
public record Zoo(List<Animal> animals ) {
public Zoo(Collection<Animal> animals) {
this(animals.stream().sorted(someComparator).collect(Collectors.toList()));
}
...
}
Upvotes: 0