所以,我正在尝试创建一个Builders集合,它可以应用于一系列规范来构建阶段。我在将通用构建器添加到集合时遇到问题,以便以后可以在构建过程中使用它们。
它与如何在Java中实现泛型有关。我花了几天尝试不同的方法,但到目前为止还没有找到解决这个问题的方法。感觉应该是可能的,但我遗漏了一些基本的东西。
完整代码:https://github.com/apara/templateTrouble/blob/master/src/Main.java)
以下是代码中使用的基本接口:
public interface Specifications {}
public interface Stage {}
public interface StageBuilder<SPEC extends Specifications, STAGE extends Stage> {
STAGE build(SPEC specifications);
boolean canBuild(SPEC specs);
}
创建这些基本接口后,我创建了一些简单的实现:
public class FilterSpecifications implements Specifications {}
public class GroupSpecifications implements Specifications {}
public class FilterStage implements Stage {}
public class GroupStage implements Stage {}
最后,我实现了一些简单的构建器:
public abstract class AbstractStageBuilder<SPEC extends Specifications, STAGE extends Stage> implements StageBuilder<SPEC, STAGE> {
private Class<? extends SPEC>
specClass;
public AbstractStageBuilder(final Class<? extends SPEC> specClass) {
this.specClass = specClass;
}
@Override
public boolean canBuild(final SPEC specs) {
return
specClass.isAssignableFrom(specs.getClass());
}
}
public class FilterStageBuilder extends AbstractStageBuilder<FilterSpecifications, FilterStage> {
public FilterStageBuilder() {
super(FilterSpecifications.class);
}
@Override
public FilterStage build(final FilterSpecifications specifications) {
return
new FilterStage();
}
}
public class GroupStageBuilder extends AbstractStageBuilder<GroupSpecifications, GroupStage> {
public GroupStageBuilder() {
super(GroupSpecifications.class);
}
@Override
public GroupStage build(final GroupSpecifications specifications) {
return
new GroupStage();
}
}
问题似乎是我不知道如何构建一组构建器以传递给构建方法:
public class Main {
public static void main(String[] args) {
//Create the builder list
//
final Collection<StageBuilder<Specifications,?>>
builders =
new LinkedList<>();
//*** THESE TWO LINES DO NOT COMPILE, cannot add specific builders to collection ***
//
//builders.add(new FilterStageBuilder());
//builders.add(new GroupStageBuilder());
final Collection<Stage>
result =
build(
builders,
Arrays.asList(
new FilterSpecifications(),
new GroupSpecifications()
)
);
System.out.println("Created stages: " + result.size());
}
static Collection<Stage> build(final Collection<StageBuilder<Specifications,?>> builders, final Collection <Specifications> specifications) {
return
specifications
.stream()
.map(
spec ->
builders
.stream()
.filter(
builder ->
builder
.canBuild(spec)
)
.findFirst()
.orElseThrow(
() ->
new RuntimeException(
"Builder not found for " + spec
)
)
.build(
spec
)
)
.collect(
Collectors.toList()
);
}
}
感谢您的帮助。
PS:我不一定要投,我希望理想情况下这只适用于Java泛型。编辑1:
我将列表更新为以下规范:
final Collection<StageBuilder<? extends Specifications,? extends Stage>>
builders =
new LinkedList<>();
现在我可以在将列表发送到构建函数时添加两个构建器:
static Collection<Stage> build(final Collection<StageBuilder<? extends Specifications,? extends Stage>> builders, final Collection <? extends Specifications> specifications) {...}
我在尝试调用builder.canBuild(spec)方法时遇到以下错误:
Main.java:52: error: method canBuild in interface StageBuilder<SPEC,STAGE> cannot be applied to given types;
.canBuild(spec) //--- << unable to invoke here
^
required: CAP#1
found: CAP#2
reason: argument mismatch; Specifications cannot be converted to CAP#1
where SPEC,STAGE are type-variables:
SPEC extends Specifications declared in interface StageBuilder
STAGE extends Stage declared in interface StageBuilder
where CAP#1,CAP#2 are fresh type-variables:
CAP#1 extends Specifications from capture of ? extends Specifications
CAP#2 extends Specifications from capture of ? extends Specifications
所以,看起来它是&#34;字符串明智&#34;相同但CAP#1和CAP#2虽然看起来相同但显然不是。所以,我需要以某种方式使它们相同?
编辑2:
所以,我完成了大部分工作,但仍然无法将构建器添加到集合中:
Builder集合简单定义为:
final Collection<StageBuilder<Specification, Stage>>
builders =
new LinkedList<>();
实际构建函数定义为(没有问题):
static
<STAGE extends Stage, SPEC extends Specification>
Collection<? extends Stage> build(final Collection<? extends StageBuilder<? super SPEC, ? super STAGE>> builders, final Collection <SPEC> specifications) {
return
specifications
.stream()
.map(
specification ->
builders
.stream()
.filter(
builder ->
builder
.canBuild(specification)
)
.findFirst()
.orElseThrow(
RuntimeException::new
)
.build(
specification
)
)
.collect(
Collectors.toList()
);
}
这是我被困的地方。我创建了一个辅助函数(正如我在一些帖子中看到的那样),虽然可以调用它,但它无法在函数内部编译:
static
<SPEC extends Specification, STAGE extends Stage, BUILDER extends StageBuilder<?,?>>
void add(final Collection<? extends StageBuilder<? super SPEC, ? super STAGE>> builders, final BUILDER builder){ //StageBuilder<? super SPEC, ? super STAGE> builder) {
builders
.add(builder); // <-- DOES NOT COMPILE
}
仍有一个错误:
Main.java:81: error: method add in interface Collection<E> cannot be applied to given types;
.add(builder); // <-- DOES NOT COMPILE
^
required: CAP#1
found: BUILDER
reason: argument mismatch; BUILDER cannot be converted to CAP#1
where SPEC,STAGE,BUILDER,E are type-variables:
SPEC extends Specification declared in method <SPEC,STAGE,BUILDER>add(Collection<? extends StageBuilder<? super SPEC,? super STAGE>>,BUILDER)
STAGE extends Stage declared in method <SPEC,STAGE,BUILDER>add(Collection<? extends StageBuilder<? super SPEC,? super STAGE>>,BUILDER)
BUILDER extends StageBuilder<?,?> declared in method <SPEC,STAGE,BUILDER>add(Collection<? extends StageBuilder<? super SPEC,? super STAGE>>,BUILDER)
E extends Object declared in interface Collection
where CAP#1 is a fresh type-variable:
CAP#1 extends StageBuilder<? super SPEC,? super STAGE> from capture of ? extends StageBuilder<? super SPEC,? super STAGE>
1 error
以下是当前代码的完整参考链接:
https://github.com/apara/templateTrouble/blob/7356c049ee5c2ea69f371d3b84d44dbe7a104aec/src/Main.java
编辑3
这是问题的本质,稍微改变add方法的签名就会在无法添加或无法调用方法之间切换问题。
所以,我们有这样的设置:
//Attempt 1
//
add(builders, filterStageBuilder); // <-- DOES NOT COMPILE
add(builders, groupStageBuilder); // <-- DOES NOT COMPILE
static
<SPEC extends Specification, STAGE extends Stage, BUILDER extends StageBuilder<? extends SPEC, ? extends STAGE>>
void add(final Collection<? super BUILDER> builders, final BUILDER builder) {
builders
.add(builder); // <-- COMPILES FINE
}
或者这个设置:
//Attempt 2
//
add2(builders, filterStageBuilder); // <-- COMPILES FINE
add2(builders, groupStageBuilder); // <-- COMPILES FINE
static
<SPEC extends Specification, STAGE extends Stage, BUILDER extends StageBuilder<? extends SPEC, ? extends STAGE>>
void add2(final Collection<? extends BUILDER> builders, final BUILDER builder) {
builders
.add(builder); // <-- DOES NOT COMPILE
}
因此,对于最终的Collection构建器,我无法调用该方法,但是对于最终的Collection构建器,我无法添加到该方法。
解决方案似乎近在咫尺,但我不能指出允许我调用方法并添加到集合的正确类型规范。这种感觉应该是可以实现的,没有像演员这样的hackery。
编辑4
蒂姆非常好的解决方案和对问题的解释。我用工作的编译代码更新了源代码。以下是链接:
https://github.com/apara/templateTrouble
-AP _
答案 0 :(得分:3)
根据您为StageBuilder
问题的核心本质上是StageBuilder
为其specification
采用了一个类型参数,这使得很难一般地处理构建器,因为它们(实际上)在不同的输入上运行域。
您告诉编译器您希望它确保您永远不会将FilterSpecifications
传递给GroupStageBuilder
。也就是说,你不希望这个编译:
Specifications spec = new FilterSpecifications();
GroupStageBuilder builder = new GroupStageBuilder();
builder.build(spec);
你想要这样做是有意义的,但你试图用你的建造者集合做的事情基本上是一样的:
Collection<Specifications> specs = Collections.singleton(new FilterSpecifications());
Collection<GroupStageBuilder> builders = Collections.singleton(new GroupStageBuilder());
specs.forEach( s -> builders.forEach( b-> b.build(s) ) );
您正在使用canBuild
作为此问题的运行时保护,但这并不能解决您遇到的编译时问题。< / p>
您有一些(可能)混合了不同Specifications
类型的东西,并且您希望将它们传递给仅为这些类型的子集定义的构建器集合。
你可以选择几种解决方案,但它们都会非常相似。
更直接的选择是更改StageBuilder
界面,以便为每个Specifications
类型定义,因为这就是您的Main.build
方法实际上所期望的 - 它希望编译器允许它将任何Specifications
对象传递给任何StageBuilder
,并在运行时进行安全检查。
public interface StageBuilder<STAGE extends Stage> {
STAGE build(Specifications specifications);
boolean canBuild(Specifications specs);
}
public abstract class AbstractStageBuilder<SPEC extends Specifications, STAGE extends Stage> implements StageBuilder<STAGE> {
private Class<? extends SPEC>
specClass;
public AbstractStageBuilder(final Class<? extends SPEC> specClass) {
this.specClass = specClass;
}
@Override
public boolean canBuild(final Specifications specs) {
return specClass.isAssignableFrom(specs.getClass());
}
@Override
public STAGE build(Specifications specifications) {
SPEC spec = specClass.cast(specifications);
doBuild(spec);
}
protected abstract STAGE doBuild(SPEC specifications);
}
如果您想在演员表周围加强安全性,那么您需要更改界面,以便canBuild
和build
实际上是相同的方法。
就目前而言(在上面的示例中)理论上可以简单地忽略canBuild
然后将错误的类型传递给build
并获得ClassCastException
。
解决方案是将其更改为:
public interface StageBuilder<STAGE extends Stage> {
Optional<Supplier<STAGE>> supplier(Specifications specifications);
}
public abstract class AbstractStageBuilder<SPEC extends Specifications, STAGE extends Stage> implements StageBuilder<STAGE> {
private Class<? extends SPEC>
specClass;
public AbstractStageBuilder(final Class<? extends SPEC> specClass) {
this.specClass = specClass;
}
@Override
public Optional<Supplier<Stage>> supplier(final Specifications specs) {
if( specClass.isAssignableFrom(specs.getClass()) ) {
return Optional.of( () -> this.build(specClass.cast(specs)) );
} else {
return Optional.empty();
}
}
protected abstract STAGE build(SPEC specifications);
}
然后更改Main.build
以使用map( builder -> builder.supplier(spec) ).filter(o -> o.isPresent() )
代替filter
上的现有canBuild
答案 1 :(得分:0)
我不完全确定你在这里尝试做什么,但我能够让它编译。我改变了
Collection<StageBuilder<? extends Specifications,? extends Stage>>
到
Collection<StageBuilder>
在第19行和第41行。(你的行号可能略有不同,我从github得到它,但可能不小心做了一些autoformating。)
以下是我最终结果的完整粘贴,以防万一:
import api.Specifications;
import api.Stage;
import api.StageBuilder;
import builder.FilterStageBuilder;
import builder.GroupStageBuilder;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.stream.Collectors;
import specification.FilterSpecifications;
import specification.GroupSpecifications;
public class Main {
public static void main(String[] args) {
//Create the builder list
//
final Collection<StageBuilder>
builders =
new LinkedList<>();
builders.add(new FilterStageBuilder());
builders.add(new GroupStageBuilder());
final Collection<Stage>
result =
build(
builders,
Arrays.asList(
new FilterSpecifications(),
new GroupSpecifications()
)
);
System.out.println("Created stages: " + result.size());
}
static Collection<Stage> build(final Collection<StageBuilder> builders, final Collection <? extends Specifications> specifications) {
return
specifications
.stream()
.map(
spec ->
builders
.stream()
.filter(
builder ->
builder
.canBuild(spec)
)
.findFirst()
.orElseThrow(
() ->
new RuntimeException(
"Builder not found for " + spec
)
)
.build(
spec
)
)
.collect(
Collectors.toList()
);
}
}