Java 8 thread concatenates and returns multiple values

I am porting a piece of code from .NET to Java and stumbled upon a script in which I want to use a stream for matching and pruning.

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 pass the contents of the contents of a content list and return a class with the result

 content { propA = "A1, A2, A3", propB = "B1, B2, B3", propC = "C1, C2, C3" } 

I am new to Java, so you can find code similar to C # than to java

+7
java dictionary java-8 java-stream collect
source share
4 answers

You can use the correct lambda for BinaryOperator in the reduction function.

 Content c = contentList .stream() .reduce((t, u) -> new Content( t.getA() + ',' + u.getA(), t.getB() + ',' + u.getB(), t.getC() + ',' + u.getC()) ).get(); 
+3
source share
 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: Extending the built-in Federico collector, here is a specific class dedicated to combining 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
source share

The most common way to solve such problems is to combine the result of several collectors into one.

Using the jOOL library, you can get 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 Seq from the input list and combines the 3 given collectors to create Tuple3 , which is simply a holder for 3 values. Then these 3 values ​​are displayed in Content using the new Content(a, b, c) constructor. The collectors themselves simply map each Content to its value a , b or c and combine the results together, separating them with ", " .


Without third-party help, we could create our own combiner collector (this is based on StreamEx pairing , which does the same for 2 collectors). It takes 3 collectors as arguments and performs a finisher operation based on the result of 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 )); 
+4
source share

If you do not want to repeat three times in the list or do not want to create too many intermediate Content objects, then you will need to assemble a 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 three empty StringBuilder objects. It then defines a battery that adds each property of the Content element to the corresponding StringBuilder . Then it defines the merge function, which is used only when the stream is processed in parallel, which combines the two previously accumulated partial results. Finally, it also defines a finisher function that converts 3 StringBuilder objects to a new Content instance with each property corresponding to the accumulated lines of the previous steps.

Please check Stream.collect() and Collector.of() javadocs for future reference.

+3
source share

All Articles