所以,我试图创建一个建筑商的集合,可以应用到规范的集合来构建阶段。我遇到了一个问题,将我的通用构建器添加到集合中,以便以后在构建过程中使用它们。
这与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泛型一起工作。
EDIT:
我将列表更新为以下规范:
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
所以,它看起来是"字符串"等效的,但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
}
因此,对于final Collection构建器,我不能调用该方法,但是对于final Collection构建器,我不能添加到方法中。
解决方案似乎近在咫尺,但我无法确定允许我调用该方法并将其添加到集合中的正确类型规范。这感觉它应该可以实现,而不需要像强制类型转换这样的hack。
编辑4
非常好的解决方案和Tim对问题的解释。我已经更新了源代码与工作,编译代码。为了将来参考,这里是链接:
https://github.com/apara/templateTrouble-AP_
考虑到您为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
作为运行时保护此问题,但这并不能解决编译时问题。
你有一个东西的集合,这些东西(可能)是不同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() )
而不是canBuild
上现有的filter
我不完全确定您在这里想做什么,但我能够让它编译。I changed
Collection<StageBuilder<? extends Specifications,? extends Stage>>
Collection<StageBuilder>
第19行和第41行。(你的行号可能略有不同,我从github上得到它,但可能不小心做了一些自动格式化)。
这是我最终的完整粘贴,以防万一:
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()
);
}
}