I have a super class:
@Getter
@SuperBuilder(toBuilder = true)
public class Animal {
private Integer size;
}
and several subclasses for it
@Getter
@SuperBuilder(toBuilder = true)
public class Dog extends Animal{
private String color;
}
As you can see, both of the above classes are immutable. Also I have a generic class which computes on any subclass of animal. I am creating a new instance of the animal here with increased size.
public class GrowAnimal<T extends Animal> {
public T execute(T animal) {
// Here I need to explicitly cast to T
T newAnimal = (T) animal.toBuilder()
.size(animal.getSize() 1)
.build();
return newAnimal;
}
}
There are several problems with this approach:
- I cannot make Animal an abstract class
- It can get pretty unsafe if i have more transformation classes like GrowAnimal
- If someone adds a new subclass of animal and forgets to add SuperBuilder to it, The GrowAnimal will just copy the super-class attributes.
Is there a better way to clone T? I would want to retain all the other attributes of the animal (for example, color of the dog) unchanged in the new instance created. Instead of cloning, I could use setters to mutate the incoming object but that is a code smell.
CodePudding user response:
There is no (type-)safe way for your approach. The underlying reason is that you cannot enforce @SuperBuilder(toBuilder = true)
on subclasses with "pure Java" means.
If a subclass only has @SuperBuilder(toBuilder = false)
or even no @SuperBuilder
at all, this code will compile fine. However, calling toBuilder()
on an instance of this subclass will not return a builder of that type, but a builder of the nearest superclass with @SuperBuilder(toBuilder = true)
. Thus, your explicit cast to T
is marked unsafe by the compiler for a good reason.
If it's just code that you control, you could write a unit test that ensures that all subclasses of Animal
also have @SuperBuilder(toBuilder = true)
. A framework that can support you in writing such tests is ArchUnit. With it, you can write things like classes().that().areAssignableTo(Animal.class).should()
and append your conditions. Remember that the @SuperBuilder
annotation itself is removed during compilation, so you have to check for the presence of the lombok-generated code.
If Animal
can be extended by third-party code, you could check during runtime if all subclasses have @SuperBuilder(toBuilder = true)
.
Or you simply live with the fact that your code in GrowAnimal
could throw ClassCastException
s.
CodePudding user response:
You don't have to use generics. The execute
method can receive an Animal
instead of T
and that should solve it.