37

I have a feeling I'm missing something here. I found myself doing the following

private static int getHighestValue(Map<Character, Integer> countMap) {
    return countMap.values().stream().mapToInt(Integer::intValue).max().getAsInt();
}

My problem is with the silly conversion from Stream to IntStream via the mapToInt(Integer::intValue)

Is there a better way of doing the conversion? all this is to avoid using max() from Stream, which requires passing a Comparator but the question is specifically on the convertion of Stream to IntStream

Neuron
  • 5,141
  • 5
  • 38
  • 59
Hilikus
  • 9,954
  • 14
  • 65
  • 118
  • 2
    Have you considered using `max(Comparator.naturalOrder())`? – Jon Skeet Jan 18 '15 at 22:03
  • 1
    And why exactly do you think it is silly? You want to return an int, so .mapToInt() makes sense... – fge Jan 18 '15 at 22:05
  • 2
    @fge because i think i'm wasting a call to autobox the values, which would need O(n) operations. I'm hoping I can get the stream as an IntStream directly – Hilikus Jan 18 '15 at 22:09
  • 4
    Well, no, since no values in Collections or Maps can be primitives; and you are not boxing here but unboxing. Still, it's not as expensive as you think. I have a real use case in mind but it's too long to explain in a single comment. – fge Jan 18 '15 at 22:12
  • 7
    No, you're not doing any boxing -- the values are already boxed, you're unboxing them (which you have to do to do comparison on them). You're doing it right. You're not costing yourself any avoidable computation; if you did it in a plain old loop, you'd still be doing at least as many unbox operations. – Brian Goetz Jan 18 '15 at 23:24

4 Answers4

19

Due to type erasure, the Stream implementation has no knowledge about the type of its elements and can’t provide you with neither, a simplified max operation nor a conversion to IntStream method.

In both cases it requires a function, a Comparator or a ToIntFunction, respectively, to perform the operation using the unknown reference type of the Stream’s elements.

The simplest form for the operation you want to perform is

return countMap.values().stream().max(Comparator.naturalOrder()).get();

given the fact that the natural order comparator is implemented as a singleton. So it’s the only comparator which offers the chance of being recognized by the Stream implementation if there is any optimization regarding Comparable elements. If there’s no such optimization, it will still be the variant with the lowest memory footprint due to its singleton nature.

If you insist on doing a conversion of the Stream to an IntStream there is no way around providing a ToIntFunction and there is no predefined singleton for a Number::intValue kind of function, so using Integer::intValue is already the best choice. You could write i->i instead, which is shorter but just hiding the unboxing operation then.

Holger
  • 285,553
  • 42
  • 434
  • 765
12

I realize you are trying to avoid a comparator, but you could use the built-in for this by referring to Integer.compareTo:

private static int getHighestValue(Map<Character, Integer> countMap) {
    return countMap.values().stream().max(Integer::compareTo).get();
}

Or as @fge suggests, using ::compare:

private static int getHighestValue(Map<Character, Integer> countMap) {
    return countMap.values().stream().max(Integer::compare).get();
}
Todd
  • 30,472
  • 11
  • 81
  • 89
  • 2
    Instead of finishing up with `get()` maybe, `orElse(0)` or `orElseThrow(...)` might be "safer"? – wassgren Jan 18 '15 at 22:08
  • 1
    @wassgren Yes, either of those things is great, *especially* if you may have an empty map. Frankly I just used `get()` because I was focused more on the comparator part. Good call. – Todd Jan 18 '15 at 22:10
  • 1
    I upvoted this although it doesn't answer the actual question since I will use this instead. However, I just want to know what's the best way to convert a Stream to IntStream since I see myself needing this in other cases. BTW, it could be that `stream().mapToInt(Integer::intValue)` IS the best way already – Hilikus Jan 18 '15 at 22:13
12

Another way you could do the conversion is with a lambda: mapToInt(i -> i). Whether you should use a lambda or a method reference is discussed in detail here, but the summary is that you should use whichever you find more readable.

Alex - GlassEditor.com
  • 14,957
  • 5
  • 49
  • 49
2

If the question is "Can I avoid passing converter while converting from Stream<T> to IntStream?" one possible answer might be "There is no way in Java to make such conversion type-safe and make it part of the Stream interface at the same time".

Indeed method which converts Stream<T> to IntStream without a converter might be looked like this:

public interface Stream<T> {
    // other methods

    default IntStream mapToInt() {
        Stream<Integer> intStream = (Stream<Integer>)this;
        return intStream.mapToInt(Integer::intValue);
    }
}

So it suppose to be called on Stream<Integer> and will fail on other types of streams. But because streams are lazy evaluated and because of the type erasure (remember that Stream<T> is generic) code will fail at the place where stream is consumed which might be far from the mapToInt() call. And it will fail in a way that is extremely difficult to locate source of the problem.

Suppose you have code:

public class IntStreamTest {

    public static void main(String[] args) {
        IntStream intStream = produceIntStream();
        consumeIntStream(intStream);
    }

    private static IntStream produceIntStream() {
        Stream<String> stream = Arrays.asList("1", "2", "3").stream();
        return mapToInt(stream);
    }

    public static <T> IntStream mapToInt(Stream<T> stream) {
        Stream<Integer> intStream = (Stream<Integer>)stream;
        return intStream.mapToInt(Integer::intValue);
    }

    private static void consumeIntStream(IntStream intStream) {
        intStream.filter(i -> i >= 2)
                .forEach(System.out::println);
    }
}

It will fail on consumeIntStream() call with:

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
    at java.util.stream.ReferencePipeline$4$1.accept(ReferencePipeline.java:210)
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
    at java.util.stream.ForEachOps$ForEachOp$OfInt.evaluateSequential(ForEachOps.java:189)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.IntPipeline.forEach(IntPipeline.java:404)
    at streams.IntStreamTest.consumeIntStream(IntStreamTest.java:25)
    at streams.IntStreamTest.main(IntStreamTest.java:10)

Having this stacktrace do you able to quickly identify that the problem is in produceIntStream() because mapToInt() was called on the stream of the wrong type?

Of course one can write converting method which is type safe because it accepts concrete Stream<Integer>:

public static IntStream mapToInt(Stream<Integer> stream) {
    return stream.mapToInt(Integer::intValue);
}

// usage
IntStream intStream = mapToInt(Arrays.asList(1, 2, 3).stream())

but it's not very convenient because it breaks fluent interface nature of the streams.

BTW:

Kotlin's extension functions allow to call some code as it is a part of the class' interface. So you are able to call this type-safe method as a Stream<java.lang.Integer>'s method:

// "adds" mapToInt() to Stream<java.lang.Integer>
fun Stream<java.lang.Integer>.mapToInt(): IntStream {
    return this.mapToInt { it.toInt() }
}

@Test
fun test() {
    Arrays.asList<java.lang.Integer>(java.lang.Integer(1), java.lang.Integer(2))
            .stream()
            .mapToInt()
            .forEach { println(it) }
}
Ilya Serbis
  • 21,149
  • 6
  • 87
  • 74