Home > database >  Type safe way for cloning generics
Type safe way for cloning generics

Time:02-27

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:

  1. I cannot make Animal an abstract class
  2. It can get pretty unsafe if i have more transformation classes like GrowAnimal
  3. 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 ClassCastExceptions.

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.

  • Related