问题
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
回答1:
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.
回答2:
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();
}
回答3:
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.
回答4:
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) }
}
来源:https://stackoverflow.com/questions/28015278/convert-stream-to-intstream