java - 不确定如何使用 Java 泛型解决此问题

标签 java generics

因此,我正在尝试创建一组构建器,这些构建器可以应用于一组规范以构建阶段。我在将我的通用构建器添加到集合中以便以后可以在构建过程中使用它们时遇到问题。

这与泛型在 Java 中的实现方式有关。我花了几天时间尝试不同的方法,但到目前为止还没有想出解决这个问题的办法。感觉应该是可能的,但我缺少一些基本的东西。

完整代码在这里:https://github.com/apara/templateTrouble/blob/master/src/Main.java )

以下是代码中使用的基本接口(interface):

public interface Specifications {}

public interface Stage {}

public interface StageBuilder<SPEC extends Specifications, STAGE extends Stage> {
    STAGE build(SPEC specifications);
    boolean canBuild(SPEC specs);
}

在创建了这些基本接口(interface)之后,我创建了一些简单的实现:

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

因此,它看起来像是“字符串方面”的等价物,但 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
}

因此,对于最终集合构建器,我无法调用该方法,但对于最终集合构建器,我无法添加到该方法。

解决方案似乎近在咫尺,但我无法确定允许我调用该方法并添加到集合中的正确类型规范。感觉它应该可以在没有 hackery(例如强制转换)的情况下实现。

编辑 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 接口(interface),以便为每个 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);
}

如果您想为该转换提供更多安全性,那么您需要更改接口(interface),以便 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() ) canBuild

上现有的 filter

关于java - 不确定如何使用 Java 泛型解决此问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31887042/

相关文章:

java - 通用构造器

java - 在静态方法中获取实际泛型类型的类对象

c# - 通过动态属性将两个通用列表相交

java - 如何在Java中检查字符串数组中的相同名称?

java - Mongodb 批量操作中的跟踪操作

java - 对具有泛型参数的 java 接口(interface)方法使用特定的实现类型。如何避免不加控制的 Actor 阵容

swift - 在 Apple 的 Swift 中实现通用接口(interface)

java - 如何为 drawLine 制作动画?

java - GCP - GoogleCredential 与 GoogleCredentials

javascript - 为什么相同的逻辑在 Java 中失败并显示索引错误,但在 JavaScript 中却没有?