2

Given the following example class:

class Foo{
    String name;
    int value;
    public Foo(String name, int value) {
        this.name = name;
        this.value = value;
    }
    // getters, setters, toString & equals
}

and a list of foos:

List<Foo> fooList = List.of(new Foo("A",1), new Foo("A",2),
                                new Foo("B",1),
                                new Foo("C",1),
                                new Foo("D",1), new Foo("D",2), new Foo("D",3)
                            );

I want to get a list of foos distinct by name and if there are more than one Foos I want to keep the one with the highst value.

I've found this java-8-distinct-by-property question to get elemnts of the list distinct by name, which uses this method to get distinct elements:

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(keyExtractor.apply(t));
}

I use it like:

fooList.stream().filter(distinctByKey(Foo::getName)).collect(Collectors.toList());

It works to get distinct elements but keeps the first element in the list if there are two or more with the same name and I'am not able to add a condition to keep the one with the highst value.

Another option is to use grouping by:

Map<String,List<Foo>> map = fooList.stream().collect(Collectors.groupingBy(f -> f.getName()));

Here I don't know how to collect the Foos with the highst value from the map to a list.

With the first approach I get:

Foo{name=A, value=1}
Foo{name=B, value=1}
Foo{name=C, value=1}
Foo{name=D, value=1}

With the second:

A=[Foo{name=A, value=1}, Foo{name=A, value=2}]
B=[Foo{name=B, value=1}]
C=[Foo{name=C, value=1}]
D=[Foo{name=D, value=1}, Foo{name=D, value=2}, Foo{name=D, value=3}]

How can I get a list of:

Foo{name=A, value=2}
Foo{name=B, value=1}
Foo{name=C, value=1}
Foo{name=D, value=3}
wannaBeDev
  • 516
  • 3
  • 14
  • `List.of` is available from which Java version? I have Java 1.8 and it's not available... – deHaar Dec 06 '19 at 10:24
  • 1
    @deHaar [Java 9](https://docs.oracle.com/javase/9/docs/api/java/util/List.html#of--) – Aaron Dec 06 '19 at 10:26
  • Dont use groupingBy. Use toMap() with a merge function that returns the element with the highest value: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#toMap-java.util.function.Function-java.util.function.Function-java.util.function.BinaryOperator- – JB Nizet Dec 06 '19 at 10:27
  • 1
    [Stream - Collect by property and max](https://stackoverflow.com/q/48446138/2711488) – Holger Dec 06 '19 at 10:31
  • @Holger Thank you. That is a similar question as mine which i didn't found earlier and solves my issue. – wannaBeDev Dec 06 '19 at 10:38

2 Answers2

7

You could use the groupBy(classifier, downstream) method.

The classification function maps elements to some key type K. The downstream collector operates on elements of type T and produces a result of type D. The resulting collector produces a Map.

snippet

fooList.stream().collect(
        Collectors.groupingBy(Foo::getName,
                Collectors.collectingAndThen(
                        Collectors.maxBy(Comparator.comparing(Foo::getValue)),
                        Optional::get
                )
        )
).values().forEach(System.out::println);

output

Foo{name='A', value=2}
Foo{name='B', value=1}
Foo{name='C', value=1}
Foo{name='D', value=3}
SubOptimal
  • 22,518
  • 3
  • 53
  • 69
1

First sort by the value desc and then use distinctByKey method, this way first Foo instance of a name will have highest value. As long as you will not use pararel streams, you should be good.

Rumid
  • 1,627
  • 2
  • 21
  • 39