查看代码:
Collection<MyDto> col = ...
MyBuilder builder = new MyBuilder();
for (MyDto dto: col) {
switch (dto.getType()) {
case FIELD1:
builder.field1(dto.getValue());
break:
case FIELD2:
builder.field2(dto.getValue());
break:
}
}
Some result = builder.build();
是否可以使用流来执行此操作,例如:
Some result = col.stream().collect(...)
请注意,所有流值都被收集到单行标记中,而不是收集,流或映射中。
答案 0 :(得分:2)
最重要的是,无论如何,您都需要将MyDto.getType()
的可能返回值映射到MyBuilder
的属性设置方法。您的代码通过switch
语句做到了,就好了。您可以将简化内容编写为基于流的管道,但是您仍然需要以某种方式合并映射。
一种非常直接的方法是构造一个文字Map
,可以将其设置为静态,最终且不可修改。例如,如果您从这样的结构类开始...
class Some {
}
class MyBuilder {
void field1(String s) { }
void field2(String s) { }
void field3(String s) { }
Some build() {
return null;
}
}
class ValueType {}
class MyDto {
int type;
ValueType value;
int getType() {
return type;
}
ValueType getValue() {
return value;
}
}
...那么您可以像这样设置您所描述的减少量:
public class Reduction {
// Map from DTO types to builder methods
private final static Map<Integer, BiConsumer<MyBuilder, ValueType>> builderMethods;
static {
// one-time map initialization
Map<Integer, BiConsumer<MyBuilder, ValueType>> temp = new HashMap<>();
temp.put(FIELD1, MyBuilder::field1);
temp.put(FIELD2, MyBuilder::field2);
temp.put(FIELD3, MyBuilder::field3);
builderMethods = Collections.unmodifiableMap(temp);
}
public Some reduce(Collection<MyDto> col) {
return col.stream()
// this reduction produces the populated builder
.reduce(new MyBuilder(),
(b, d) -> { builderMethods.get(d.getType()).accept(b, d); return b; })
// obtain the built object
.build();
}
}
该特定实现每次都使用一个新的构建器,但是如果您想从某些预先填充的属性开始,可以对其进行修改以使用通过参数传递到Reduction.reduce()
中的构建器,和/或保留有关用于构建返回对象的属性的记录。
最后,请注意,尽管您可以将细节隐藏在一个地方或另一个地方,但是我发现没有比最初使用基于switch
的代码更简单的整个过程的范围。>
答案 1 :(得分:1)
我没有编译这个,只是给你一个主意:
Map<Boolean, List<MyDto>> map = col.stream().collect(Collectors.partitioningBy(t -> t.getType() == FIELD2));
map.get(false).forEach(x -> builder.field1(x.getValue()))
map.get(true).forEach(x -> builder.field2(x.getValue()))
答案 2 :(得分:1)
假设两个MyBuilder
实例可以合并/合并,那么您可以使用Collector
进行此操作。
public class MyCollector implements Collector<MyDto, MyBuilder, Result> {
@Override
public Supplier<MyBuilder> supplier() {
return MyBuilder::new;
}
@Override
public BiConsumer<MyBuilder, MyDto> accumulator() {
return (builder, dto) -> {
// Add "dto" to "builder" based on type
};
}
@Override
public BinaryOperator<MyBuilder> combiner() {
return (left, right) -> left.merge(right);
}
@Override
public Function<MyBuilder, Result> finisher() {
return MyBuilder::build;
}
@Override
public Set<Characteristics> characteristics() {
return Set.of();
}
}
那么你可以做:
Collection<MyDto> col = ...;
Result r = col.stream().collect(new MyCollector());
如果您不想自定义实现Collector
,则可以使用Collector.of(...)
。
另一种可能更易于维护的方法是让构建器完成所有工作。这样,所有映射逻辑都在一个地方。
public class ResultBuilder {
public static Collector<MyDto, ?, Result> resultCollector() {
return Collector.of(ResultBuilder::new, ResultBuilder::add,
ResultBuilder::merge, ResultBuilder::build);
}
public ResultBuilder add(MyDto dto) {
// Do what is needed based on the type of "dto"
return this;
}
public ResultBuilder merge(ResultBuilder other) {
// Merge "other" into "this"
return this;
}
public Result build() {
// Build result and return it
}
}
然后,您可以使用带有或不带有流的构建器。带流与以前非常相似:
Collection<MyDto> col = ...;
Result r = col.stream().collect(ResultBuilder.resultCollector());
答案 3 :(得分:1)
现在,一个令人沮丧的无聊答案:
像这样使用流有效地进行映射,使您的代码在未来的可读性和可维护性降低。不建议为此目的使用此Java 8功能。
正如一些回答者所倡导的那样,绝对可以做到 ,但这并不一定意味着它应该做到 。
更简洁地说,您最初的前提是,您可以使用switch
所用的某种枚举或结构来捕获所有字段,这在您每次引入或删除字段时都会中断,这可能很耗时追查。 稍微可以灵活地通过反射使场散开,但是您会遇到比您想象的更严格的反射设置;如果您想将1映射为1,则效果很好,但是如果要进行一些数据转换,则必须非常注意如何调整映射器。
所有要说的...
答案 4 :(得分:1)
您的主要问题是每个MyBuilder
方法到每个MyDto
类型的映射都是任意的,即Java无法自动知道为每种类型调用哪种方法: Java是哪个。
因此,如果构建器的每个方法都映射到一个不同的dto.getType()
值,那么告诉Java的最简单方法就是将switch
移到MyBuilder
内部的通用方法中,您可以通知相应的字段,例如:
public MyBuilder fieldFromDto(MyDto dto) {
switch (dto.getType()) {
case FIELD1: return field1(dto.getValue);
case FIELD2: return field2(dto.getValue);
//...
那么您就可以这样做:
MyBuilder builder = new MyBuilder();
col.stream().forEach(builder::fieldFromDto);
Some result = builder.build();
另一种可能性是将开关切换为lambda映射(Type
和Value
是MyDto
字段的类型)
class MyBuilder {
public final Map<Type, Function<Value, MyBuilder>> mappings = new Map<>();
public MyBuilder() {
mappings.put(FIELD1, this::field1);
mappings.put(FIELD2, this::field2);
//...
}
然后在forEach
中使用这些lambda:
MyBuilder builder = new MyBuilder();
col.stream().forEach(dto -> builder.mappings.get(dto.getType()).apply(dto.getValue()));
Some result = builder.build();
除此之外,您可以像其他建议的答案一样使用反射,但是随后需要确保FIELD1
,FIELD2
等是实际的MyBuilder
方法名称,从而丢失了一些灵活性。
最后,我不建议您执行上述任何操作。流虽然很棒,但是有时它们比普通的for
循环没有任何优势,并且会使您的代码更丑陋且难以维护。