不确定如何使用Java泛型修复此问题

时间:2015-08-07 21:40:10

标签: java generics

所以,我正在尝试创建一个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 _

2 个答案:

答案 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);
}

如果您想在演员表周围加强安全性,那么您需要更改界面,以便canBuildbuild实际上是相同的方法。

就目前而言(在上面的示例中)理论上可以简单地忽略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()
            );
    }
}