问题
I'm porting a piece of code from .NET to Java and stumbled upon a scenario where I want to use stream to map & reduce.
class Content
{
private String propA, propB, propC;
Content(String a, String b, String c)
{
propA = a; propB = b; propC = c;
}
public String getA() { return propA; }
public String getB() { return propB; }
public String getC() { return propC; }
}
List<Content> contentList = new ArrayList();
contentList.add(new Content("A1", "B1", "C1"));
contentList.add(new Content("A2", "B2", "C2"));
contentList.add(new Content("A3", "B3", "C3"));
I want to write a function that can stream through the contents of contentlist and return a class with result
content { propA = "A1, A2, A3", propB = "B1, B2, B3", propC = "C1, C2, C3" }
I'm fairly new to Java so you might find some code that resembles more like C# than java
回答1:
You can use proper lambda for BinaryOperator in reduce function.
Content c = contentList
.stream()
.reduce((t, u) -> new Content(
t.getA() + ',' + u.getA(),
t.getB() + ',' + u.getB(),
t.getC() + ',' + u.getC())
).get();
回答2:
The most generic way to deal with such tasks would be to combine the result of multiple collectors into a single one.
Using the jOOL library, you could have the following:
Content content =
Seq.seq(contentList)
.collect(
Collectors.mapping(Content::getA, Collectors.joining(", ")),
Collectors.mapping(Content::getB, Collectors.joining(", ")),
Collectors.mapping(Content::getC, Collectors.joining(", "))
).map(Content::new);
This creates a Seq
from the input list and combines the 3 given collectors to create a Tuple3
, which is simply a holder for 3 values. Those 3 values are then mapped into a Content
using the constructor new Content(a, b, c)
. The collector themselves are simply mapping each Content
into its a
, b
or c
value and joining the results together separated with a ", "
.
Without third-party help, we could create our own combiner collector like this (this is based of StreamEx pairing collector, which does the same thing for 2 collectors). It takes 3 collectors as arguments and performs a finisher operation on the result of the 3 collected values.
public interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
public static <T, A1, A2, A3, R1, R2, R3, R> Collector<T, ?, R> combining(Collector<? super T, A1, R1> c1, Collector<? super T, A2, R2> c2, Collector<? super T, A3, R3> c3, TriFunction<? super R1, ? super R2, ? super R3, ? extends R> finisher) {
final class Box<A, B, C> {
A a; B b; C c;
Box(A a, B b, C c) {
this.a = a;
this.b = b;
this.c = c;
}
}
EnumSet<Characteristics> c = EnumSet.noneOf(Characteristics.class);
c.addAll(c1.characteristics());
c.retainAll(c2.characteristics());
c.retainAll(c3.characteristics());
c.remove(Characteristics.IDENTITY_FINISH);
return Collector.of(
() -> new Box<>(c1.supplier().get(), c2.supplier().get(), c3.supplier().get()),
(acc, v) -> {
c1.accumulator().accept(acc.a, v);
c2.accumulator().accept(acc.b, v);
c3.accumulator().accept(acc.c, v);
},
(acc1, acc2) -> {
acc1.a = c1.combiner().apply(acc1.a, acc2.a);
acc1.b = c2.combiner().apply(acc1.b, acc2.b);
acc1.c = c3.combiner().apply(acc1.c, acc2.c);
return acc1;
},
acc -> finisher.apply(c1.finisher().apply(acc.a), c2.finisher().apply(acc.b), c3.finisher().apply(acc.c)),
c.toArray(new Characteristics[c.size()])
);
}
and finally use it with
Content content = contentList.stream().collect(combining(
Collectors.mapping(Content::getA, Collectors.joining(", ")),
Collectors.mapping(Content::getB, Collectors.joining(", ")),
Collectors.mapping(Content::getC, Collectors.joining(", ")),
Content::new
));
回答3:
static Content merge(List<Content> list) {
return new Content(
list.stream().map(Content::getA).collect(Collectors.joining(", ")),
list.stream().map(Content::getB).collect(Collectors.joining(", ")),
list.stream().map(Content::getC).collect(Collectors.joining(", ")));
}
EDIT: Expanding on Federico's inline collector, here is a concrete class dedicated to merging Content objects:
class Merge {
public static Collector<Content, ?, Content> collector() {
return Collector.of(Merge::new, Merge::accept, Merge::combiner, Merge::finisher);
}
private StringJoiner a = new StringJoiner(", ");
private StringJoiner b = new StringJoiner(", ");
private StringJoiner c = new StringJoiner(", ");
private void accept(Content content) {
a.add(content.getA());
b.add(content.getB());
c.add(content.getC());
}
private Merge combiner(Merge second) {
a.merge(second.a);
b.merge(second.b);
c.merge(second.c);
return this;
}
private Content finisher() {
return new Content(a.toString(), b.toString(), c.toString());
}
}
Used as:
Content merged = contentList.stream().collect(Merge.collector());
回答4:
If you don't want to iterate 3 times over the list, or don't want to create too many Content
intermediate objects, then you'd need to collect the stream with your own implementation:
public static Content collectToContent(Stream<Content> stream) {
return stream.collect(
Collector.of(
() -> new StringBuilder[] {
new StringBuilder(),
new StringBuilder(),
new StringBuilder() },
(StringBuilder[] arr, Content elem) -> {
arr[0].append(arr[0].length() == 0 ?
elem.getA() :
", " + elem.getA());
arr[1].append(arr[1].length() == 0 ?
elem.getB() :
", " + elem.getB());
arr[2].append(arr[2].length() == 0 ?
elem.getC() :
", " + elem.getC());
},
(arr1, arr2) -> {
arr1[0].append(arr1[0].length() == 0 ?
arr2[0].toString() :
arr2[0].length() == 0 ?
"" :
", " + arr2[0].toString());
arr1[1].append(arr1[1].length() == 0 ?
arr2[1].toString() :
arr2[1].length() == 0 ?
"" :
", " + arr2[1].toString());
arr1[2].append(arr1[2].length() == 0 ?
arr2[2].toString() :
arr2[2].length() == 0 ?
"" :
", " + arr2[2].toString());
return arr1;
},
arr -> new Content(
arr[0].toString(),
arr[1].toString(),
arr[2].toString())));
}
This collector first creates an array of 3 empty StringBuilder
objects. Then defines an accumulator that appends each Content
element's property to the corresponding StringBuilder
. Then it defines a merge function that is only used when the stream is processed in parallel, which merges two previously accumulated partial results. Finally, it also defines a finisher function that transforms the 3 StringBuilder
objects into a new instance of Content
, with each property corresponding to the accumulated strings of the previous steps.
Please check Stream.collect() and Collector.of() javadocs for further reference.
来源:https://stackoverflow.com/questions/36162262/java-8-stream-join-and-return-multiple-values