I have 2 Java Classes.
class A {
String name;
List<B> numbers;
}
class B {
Integer number;
}
I want to get the distinct of Class A and concat the List of B in it.
e.g. Suppose I have a List with the following objects.
List<A>{
name = "abc"
List<B>{1,2}
name= "xyz"
List<B>{3,4}
name = "abc"
List<B>{3,5}
}
The result should be:
List<A>{
name = "abc"
List<B>{1,2,3,5}
name="xyz"
List<B>{3,4}
}
Any help would be appreciated.
Note: I want to achieve this functionality using Java 8 streams.
Thanks
distinct() returns a stream consisting of distinct elements in a stream. distinct() is the method of Stream interface. This method uses hashCode() and equals() methods to get distinct elements. In case of ordered streams, the selection of distinct elements is stable.
You may use toMap
collector:
Collection<A> result = list.stream()
.collect(Collectors.toMap(a -> a.name, a -> a,
(a, b) -> {a.numbers.addAll(b.numbers); return a;}))
.values();
You may copy the result after that into List
(like new ArrayList<>(result)
), but as we don't preserve any particular order, having List
is not very useful. In the most of scenarios having Collection
as a result is fine.
I don't think there is a way to get around the map. You can however use groupingBy
to hide it, but it is effectively the same code and performance as Paul Boddington suggested.
List<A> merge(List<A> input) {
return input.stream()
.collect(groupingBy(a -> a.name)) // map created here
.entrySet()
.stream()
.map(entry -> new A(
entry.getKey(),
entry.getValue().stream()
.flatMap(list -> list.numbers.stream())
.collect(toList()) // merging behaviour
)).collect(toList());
}
There is no mutation of original list and you can easily change the behaviour of merging the lists - for example if you want to get rid of duplicates just add .distinct()
after flatMap(list -> list.numbers.stream())
(remember about adding equals
to B
) or in a similar way you can sort them by just adding .sorted()
(you have to make B implement Comparable
interface or just use .sorted(Comparator<B>)
).
Here is full code with tests and imports:
import org.junit.Test;
import java.util.List;
import static com.shazam.shazamcrest.MatcherAssert.assertThat;
import static com.shazam.shazamcrest.matcher.Matchers.sameBeanAs;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
public class Main {
class A {
final String name;
final List<B> numbers;
A(String name, List<B> numbers) {
this.name = name;
this.numbers = numbers;
}
}
class B {
final Integer number;
B(Integer number) {
this.number = number;
}
}
List<A> merge(List<A> input) {
return input.stream()
.collect(groupingBy(a -> a.name))
.entrySet()
.stream()
.map(entry -> new A(
entry.getKey(),
entry.getValue().stream()
.flatMap(list -> list.numbers.stream())
.collect(toList())
)).collect(toList());
}
@Test
public void test() {
List<A> input = asList(
new A("abc", asList(new B(1), new B(2))),
new A("xyz", asList(new B(3), new B(4))),
new A("abc", asList(new B(3), new B(5)))
);
List<A> list = merge(input);
assertThat(list, sameBeanAs(asList(
new A("abc", asList(new B(1), new B(2), new B(3), new B(5))),
new A("xyz", asList(new B(3), new B(4)))
)));
}
}
EDIT:
Following your questions in the comments, if you want to add multiple fields into groupingBy
clause, you would need to create a class that represents a key in the map. If you have fields that you don't want to include in the key, then you have to define how to merge two different values - similarly to what you do with numbers. Depending on what the fields are, merging behaviour can be just choosing the first value and discarding the other (what I have done with numbers
in the code below).
class A {
final String name;
final String type;
final List<B> numbers;
A(String name, String type, List<B> numbers) {
this.name = name;
this.type = type;
this.numbers = numbers;
}
}
class B {
final Integer number;
B(Integer number) {
this.number = number;
}
}
class Group {
final String name;
final String type;
Group(String name, String type) {
this.name = name;
this.type = type;
}
// this needs to have equals and hashCode methods as we use it as a key in a map
}
List<A> merge(List<A> input) {
return input.stream()
.collect(groupingBy(a -> new Group(a.name, a.type)))
.entrySet()
.stream()
.map(entry -> new A(
entry.getKey().name, // entry.getKey() is now Group, not String
entry.getKey().type,
entry.getValue().get(0).numbers // no merging, just take first
)).collect(toList());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With