Jan Pomikálek Jan Pomikálek - 7 months ago 32
Java Question

Java Stream: find an element with a min/max value of an attribute

I have a stream of objects and I would like to find the one with a maximal value of some attribute that's expensive to calculate.

As a specific simple example, say that we have a list of strings and we want to find the coolest one, given a

coolnessIndex
function.

The following should work:

String coolestString = stringList
.stream()
.max((s1, s2) -> Integer.compare(coolnessIndex(s1), coolnessIndex(s2)))
.orElse(null);


Now, there are two problems with this. First, assuming the
coolnessIndex
is expensive to calculate, this probably won't be very efficient. I suppose the
max
method will need to use the comparator repeatedly, which in turn will call the
coolnessIndex
repeatedly and at the end it will be called more than once for each string.

Second, having to provide the comparator leads to some redundancy in the code. I would much prefer syntax like this:

String coolestString = stringList
.stream()
.maxByAttribute(s -> coolnessIndex(s))
.orElse(null);


However, I haven't been able to find a matching method in the
Stream
API. This surprises me, since finding min/max by an attribute seems like a common pattern. I wonder if there's a better way than using the comparator (other than a for loop).

Answer

Thanks everyone for suggestions. At last I found the solution I like the most at Efficiency of the way comparator works -- the answer from bayou.io:

Have a general purpose cache method:

public static <K,V> Function<K,V> cache(Function<K,V> f, Map<K,V> cache)
{
    return k -> cache.computeIfAbsent(k, f);
}

public static <K,V> Function<K,V> cache(Function<K,V> f)
{
    return cache(f, new IdentityHashMap<>());
}

This could then be used as follows:

String coolestString = stringList
        .stream()
        .max(Comparator.comparing(cache(CoolUtil::coolnessIndex)))
        .orElse(null);