将流转换为 IntStream

Convert Stream to IntStream

我觉得我在这里遗漏了一些东西。我发现自己在做以下事情

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

我的问题是通过 mapToInt(Integer::intValue)

StreamIntStream 的愚蠢转换

有没有更好的转换方法?所有这一切都是为了避免使用 Stream 中的 max(),这需要传递 Comparator,但问题具体在于 StreamIntStream[=19 的转换=]

我知道你试图避免使用比较器,但你可以通过参考 Integer.compareTo:

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

或者如@fge 建议的那样,使用 ::compare:

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

另一种可以进行转换的方法是使用 lambda:mapToInt(i -> i)。 详细讨论了您应该使用 lambda 还是方法引用 here,但总结是您应该使用您认为更易读的那个。

由于类型擦除,Stream 实现不知道其元素的类型,因此无法为您提供简化的 max 操作或到 [=13 的转换=]方法。

在这两种情况下,它都需要一个函数,分别是 ComparatorToIntFunction,以使用 Stream 元素的未知引用类型执行操作。

您要执行的操作的最简单形式是

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

考虑到 自然顺序 比较器是作为单例实现的。因此,它是唯一有机会被 Stream 实现识别的比较器 如果 有关于 Comparable 元素的任何优化。如果没有这样的优化,由于它的单例特性,它仍然是内存占用最少的变体。

如果您坚持将 Stream 转换为 IntStream,则无法提供 ToIntFunction,并且 [=22= 没有预定义的单例] 之类的功能,所以使用 Integer::intValue 已经是最好的选择了。您可以改写 i->i,它更短但只是隐藏了拆箱操作。

如果问题是 "Can I avoid passing converter while converting from Stream<T> to IntStream?",一个可能的答案可能是 "There is no way in Java to make such conversion type-safe and make it part of the Stream interface at the same time"。

在没有转换器的情况下将 Stream<T> 转换为 IntStream 的确实方法可能如下所示:

public interface Stream<T> {
    // other methods

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

所以它应该在 Stream<Integer> 上被调用并且在其他类型的流上会失败。但是因为流是延迟求值的并且因为类型擦除(记住 Stream<T> 是通用的)代码将在流被消耗的地方失败,这可能远离 mapToInt() 调用。而且它会以一种极难找到问题根源的方式失败。

假设你有代码:

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);
    }
}

它将在 consumeIntStream() 调用时失败:

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
    at java.util.stream.ReferencePipeline.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)

有了这个堆栈跟踪,您是否能够快速确定问题出在 produceIntStream() 中,因为在错误类型的流上调用了 mapToInt()

当然可以编写类型安全的转换方法,因为它接受具体的 Stream<Integer>:

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

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

但这不是很方便,因为它破坏了流的流畅接口特性。

顺便说一句:

Kotlin 的扩展函数允许调用一些代码,因为它是 class' 接口的一部分。所以你可以调用这个类型安全的方法作为 Stream<java.lang.Integer> 的方法:

// "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) }
}