Java 如何处理一个流畅的界面,其中每个步骤都可以是终端操作?

Java 如何处理一个流畅的界面,其中每个步骤都可以是终端操作?,java,generics,functional-programming,Java,Generics,Functional Programming,我正在构建一个大致如下的fluent API(假设存在一个带有gettergetId的类Person,该类返回Long): 正如您所看到的,只有.end-函数充当终端操作员。这扰乱了上述API的整体使用,因为我经常不得不以.end(Function.identity())-调用结束,即使前面的.pipe-调用已经具有正确的类型 有没有办法制作一个fluent API,使其中的一部分同时成为终端操作符和“桥接操作符”?我只是不想用专门的pipe-变体(一种只接受函数并在内部调用.end的管道)将A

我正在构建一个大致如下的fluent API(假设存在一个带有getter
getId
的类
Person
,该类返回
Long
):

正如您所看到的,只有
.end
-函数充当终端操作员。这扰乱了上述API的整体使用,因为我经常不得不以
.end(Function.identity())
-调用结束,即使前面的
.pipe
-调用已经具有正确的类型

有没有办法制作一个fluent API,使其中的一部分同时成为终端操作符和“桥接操作符”?我只是不想用专门的
pipe
-变体(一种只接受
函数
并在内部调用
.end
的管道)将API弄得乱七八糟,因为它迫使用户考虑API中对我来说似乎不必要的非常具体的部分

编辑: 根据要求简化了上下文实现:

class Context<InType, CurrentType, TargetType> {
    private final Function<InType, CurrentType> getter;

    public Context(Function<InType, CurrentType> getter) {
        this.getter = getter;
    }

    public <IntermediateType> Context<InType, IntermediateType, TargetType>
    pipe(Function<CurrentType, IntermediateType> mapper) {

        return new Context<>(getter.andThen(mapper));
    }

    public Function<InType, TargetType> end(Function<CurrentType, TargetType> mapper) {
        return getter.andThen(mapper);
    }
}

//usage
Function<Person, String> mapper = new Context<Person, Long, String>(Person::getId)
    .pipe(Object::toString)
    .pipe(String::toUpperCase)
    .end(Function.identity());

mapper.apply(new Person(...))
类上下文{
私有最终函数getter;
公共上下文(函数getter){
this.getter=getter;
}
公共环境
管道(函数映射器){
返回新上下文(getter.andThen(mapper));
}
公共函数结束(函数映射器){
返回getter.and(映射器);
}
}
//用法
函数映射器=新上下文(Person::getId)
.pipe(对象::toString)
.pipe(字符串::toUpperCase)
.end(Function.identity());
映射器。应用(新用户(…)

如果我理解您要查找的内容,我会重载
end()
,然后去掉最后一个函数组合:

public Function<InType, CurrentType> end() {
    return this.getter;
}

不能用相同的名称和不同的返回类型在Java中定义方法。您的方法可能返回类似于
Wrapped
的内容,而您希望返回
T
。一般来说,我可能会建议对每个方法使用类似于
*andEnd(…)
的方法。因此,
pipeAndEnd(…)
将执行管道,然后以终端操作结束。这样做可能会变得单调乏味,因此如果有很多方法,您可能需要研究一些代码生成


另一方面,您似乎正在实现自己版本的流API。除非您这样做是出于教育目的,否则使用现有的经过良好测试/记录的代码(尤其是标准jdk的代码部分)几乎总是比重新实现您自己的版本要好。

我遇到的主要问题是,任何
管道
步骤都可能是终端操作。正如下面讨论的每个答案和主要帖子所概述的:在java中不可能使用两次同名函数,一次作为终端操作

我对这个问题绞尽脑汁,尝试了多种方法,但每种方法都不起作用。这时我意识到我所做的与Javas
Stream
-API基本相同:你有一个来源(source),做一些奇特的事情(pipe),然后结束(collect)。如果我们对我的问题应用相同的方案,
管道
不需要作为终端操作,我们只需要另一个操作(例如,
结束
)作为终点。由于我对何时可以结束(当前类型必须与另一种类型匹配)有一些扩展需求,因此我只允许一个上下文特定的功能,该功能只有一个可用的健全实现(很难解释),从而实现了
end
。以下是当前实施的示例(
pipe
已重命名为
map
end
):

Mapper Mapper=Datus.forTypes(Person.class,PersonDTO.class)。不可变(PersonDTO::new)
.from(Person::getFirstName).to(ConstructorParameter::bind)
.from(Person::getLastName)
.given(Objects::nonNull,ln->ln.toUpperCase()).orElse(“回退”)
.to(构造函数参数::绑定)
.build();
如您所见,
.to
充当终端操作符,如果当前类型与预期类型不匹配,则
构造函数参数::bind
会抱怨类型不匹配


请参阅
to
部分,了解
构造函数参数的实现以及如何定义它。

我能想到的就是为什么不
end()
(即使作为重载)?您可能需要共享更多信息,或者只需发布
上下文类
。并非所有fluent API都相同。将使用简化的上下文类编辑问题,谢谢。只是
end()。谢谢我已经编辑了我的问题,正如您所看到的,我没有办法(据我所知)表达一个
end()
函数,该函数只在CurrentType==TargetType时工作。正如我所说:
pipe
有时是一个终端,有时是一个“桥”——运营商在原始帖子中添加了一个链接,链接到我遇到问题的班级。我删除了自己先前接受的答案,看看你为什么是对的!关于我:在完整的
Context
类中,我不仅返回getter,还使用另一个
function
类型的函数,它不能使用这种方法。我想我不能以一种不会导致在问题中有冗长解释的巨大代码段的方式减少我真正的
上下文
-类,但无论如何,谢谢你的回答!我正在考虑如何在一个小示例中说明我的问题,因为
context
类的完整上下文太宽了。你要搭便车了,我在想:)(注意:这并不是说我不能发布原始代码,但有太多的解释,因为它是)它在这里太晚了,明天将再次签入。我决定先完成这个问题连接到的库,但不处理这个问题,将链接到t
public Function<InType, CurrentType> end() {
    return this.getter;
}
class OtherContext<I, O> {

    private final Function<I, O> getter;

    public OtherContext(Function<I, O> getter) {
        this.getter = getter;
    }

    public <T> OtherContext<I, T> pipe(Function<O, T> mapper) {

        return new OtherContext<I, T>(getter.andThen(mapper));
    }

    public <T> Function<I, T> end(Function<O, T> mapper) {
        return getter.andThen(mapper);
    }

    public Function<I, O> end() {
        return getter;
    }
}
Mapper<Person, PersonDTO> mapper = Datus.forTypes(Person.class, PersonDTO.class).immutable(PersonDTO::new)
    .from(Person::getFirstName).to(ConstructorParameter::bind)
    .from(Person::getLastName)
        .given(Objects::nonNull, ln -> ln.toUpperCase()).orElse("fallback")
        .to(ConstructorParameter::bind)
    .build();