克隆泛型的类型安全方法

Type safe way for cloning generics

我有一个超级class:

@Getter
@SuperBuilder(toBuilder = true)
public class Animal {
   private Integer size;
}

和几个子class它

@Getter
@SuperBuilder(toBuilder = true)
public class Dog extends Animal{
   private String color;
}

如您所见,以上两个 class 都是不可变的。我还有一个通用的 class 可以计算动物的任何子class。我在这里创建了一个新的动物实例,增加了尺寸。

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;
    }
}

这种方法存在几个问题:

  1. 我不能让 Animal 成为抽象的 class
  2. 如果我有更多的变形 class 像 GrowAnimal
  3. 它会变得非常不安全
  4. 如果有人添加了一个新的 subclass animal 并忘记向其添加 SuperBuilder,GrowAnimal 将只复制 super-class 属性。

有没有更好的克隆T的方法?我想在创建的新实例中保留动物的所有其他属性(例如,狗的颜色)不变。我可以使用 setter 来改变传入的对象,而不是克隆,但这是一种代码味道。

您不必使用泛型。 execute 方法可以接收 Animal 而不是 T 并且应该可以解决它。

您的方法没有(类型)安全的方法。根本原因是您不能对具有“纯Java”含义的子类强制执行@SuperBuilder(toBuilder = true)

如果一个子类只有 @SuperBuilder(toBuilder = false) 甚至根本没有 @SuperBuilder,这段代码可以正常编译。但是,在该子类的实例上调用 toBuilder() 不会 return 该类型的构建器,而是 @SuperBuilder(toBuilder = true) 最近的超类的构建器。因此,您显式转换为 T 被编译器标记为不安全是有充分理由的。

如果只是您控制的代码,您可以编写单元测试以确保 Animal 的所有子类也具有 @SuperBuilder(toBuilder = true)。可以支持您编写此类测试的框架是 ArchUnit。使用它,您可以编写 classes().that().areAssignableTo(Animal.class).should() 之类的内容并附加您的条件。请记住,@SuperBuilder 注释本身在编译期间被删除,因此您必须检查 lombok-generated 代码是否存在。

如果 Animal 可以通过 third-party 代码扩展,您可以在运行时检查是否所有子类都有 @SuperBuilder(toBuilder = true).

或者您只是忍受这样一个事实,即您在 GrowAnimal 中的代码可能会抛出 ClassCastExceptions。