将工厂和执行类型捕获的方法一起使用时出现编译错误



我在将泛型与工厂和捕获类型的方法一起使用时遇到了问题。

我使用两个实体(一个工厂和一个测试)在虚拟场景中隔离了我的问题:

家长界面。父项和子项之间存在双向关系,因此 P 和 C 类型都需要成为通用规范的一部分。

public class Parent<P extends Parent<P, C>, C extends Child<P, C>> {
private List<C> children = new ArrayList<>();
public void addChild(C child) {
this.children.add(child);
}
public List<C> getChildren() {
return this.children;
}
}

这同样适用于儿童。

public class Child<P extends Parent<P, C>, C extends Child<P, C>> {
private P parent;
public P getParent() {
return this.parent;
}
public void setParent(P parent) {
this.parent = parent;
}
}

一个非常简单的父母工厂。

@FunctionalInterface
public interface ParentFactory {
Parent<?, ?> create();
}

以及展示编译问题的测试:

public class GenericsTest {
@Test
public void testMakeTwin() {
ParentFactory parentFactory = () -> new FooParent();
// No problem here, makeTwin can infere the type of the parameter
this.makeTwin(new FooParent());
// No problem here either
this.makeTwinIgnoringGenerics(parentFactory.create());
// Does not compile! Why?
this.makeTwin(parentFactory.create());
}
/**
* This is just a dummy example of an operation that needs to capture both C and P
*/
private <P extends Parent<P, C>, C extends Child<P, C>> void makeTwin(P parent) {
List<C> children = parent.getChildren();
if (children != null && !children.isEmpty()) {
parent.addChild(children.iterator().next());
}
}
private void makeTwinIgnoringGenerics(Parent parent) {
// A lot of "this is a raw type" warnings in this method
List children = parent.getChildren();
if (children != null && !children.isEmpty()) {
// This is safe because the child comes from the same parent, but that's something the compiler can't know
parent.addChild((Child) children.iterator().next());
}
}
private static class FooParent extends Parent<FooParent, FooChild> {
}
private static class FooChild extends Child<FooParent, FooChild> {
}
}

为什么测试无法将工厂创建的父级强制转换为方法makeTwin所需的参数?

补遗:

具体的问题是:为什么编译器无法捕获(和转换)工厂返回的 P 和 C 具体类型?

有关真实场景的一些额外信息,解释为什么测试是这样的。真正的上下文是实现 DDD 体系结构的应用程序,此处测试中的代码将位于应用程序层的服务中,此处的工厂属于域层。这就是为什么工厂不能参数化,工厂决定要创建的具体C和P类型是什么,而测试(即应用服务)不关心它们,它只需要知道P和C是有效的,即它们是相互关联的。

Parent<?, ?>

Parent<FooParent, FooChild>不同。

让我们以不同的Parent实现为例:

private static class OtherFooFooParent extends Parent<FooParent, FooChild> {}

我们仍然可以创建返回上述Parent的工厂:

ParentFactory parentFactory = () -> new OtherFooFooParent();

但是OtherFooFooParentP extends Parent<P, C>, C extends Child<P, C>不兼容,因为FooChild仍然绑定到FooParent,并且传递给makeTwin方法无效。

这就是为什么编译器抱怨它无法确保ParentFactory将创建与方法参数兼容makeTwinParent

解决方案是定义ParentFactory绑定到与方法相同的泛型makeTwin

@FunctionalInterface
public interface ParentFactory <P extends Parent<P, C>, C extends Child<P, C>> {
P create();
} 

并更改此内容:

ParentFactory<FooParent, FooChild> parentFactory = () -> new FooParent();

相关内容

最新更新