流转为整数流

38
我有一种感觉,我在这里漏掉了什么。我发现自己在做以下事情。
private static int getHighestValue(Map<Character, Integer> countMap) {
    return countMap.values().stream().mapToInt(Integer::intValue).max().getAsInt();
}

我的问题出在从Stream转换为IntStream时使用了愚蠢的mapToInt(Integer::intValue)方法。有没有更好的转换方式?这是为了避免使用Stream中需要传递Comparatormax()方法,但问题特别关注从Stream转换为IntStream的转换。

2
你考虑过使用 max(Comparator.<Integer>naturalOrder()) 吗? - Jon Skeet
1
你为什么认为这很傻呢?你想要返回一个整数,所以使用.mapToInt()是有道理的... - fge
2
@fge 因为我认为这样做会浪费一个自动装箱值的调用,这需要 O(n) 操作。我希望我可以直接将流作为 IntStream。 - Hilikus
4
不,因为Collections或Maps中没有基本类型的值;在这里你不是装箱而是拆箱。但是,它并不像你想象的那么昂贵。我有一个真正的用例,但是在一条评论中解释太长了。 - fge
7
不,你没有进行任何的装箱操作——这些值已经被装箱了,你正在进行拆箱操作(这是比较它们时必须要做的)。你做得很对。你不会浪费任何可避免的计算量;如果你在普通的循环中完成它,你仍然至少需要进行同样数量的拆箱操作。 - Brian Goetz
4个回答

19
由于类型擦除,Stream 实现不知道它的元素类型,并且既不能为您提供简化版的 max 操作,也不能提供转换为 IntStream 方法。在这两种情况下,都需要使用函数,即 ComparatorToIntFunction 来使用 Stream 元素的未知引用类型执行操作。 要执行所需的操作的最简单形式为:
return countMap.values().stream().max(Comparator.naturalOrder()).get();

鉴于自然顺序比较器是实现为单例的事实。因此,它是唯一提供被Stream实现识别的机会的比较器,如果存在与Comparable元素相关的任何优化。如果没有这样的优化,由于其单例性质,它仍将是内存占用最小的变体。

如果您坚持将Stream转换为IntStream,那么没有绕过提供ToIntFunction的方式,也没有预定义的用于Number :: intValue类型函数的单例,因此使用Integer :: intValue已经是最佳选择。您可以使用i->i代替,这样更短,但只是隐藏了解包装操作。


12

我知道你想避免使用比较器,但你可以使用内置的方法来完成,只需要引用 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();
}

2
与其最终使用 get(),也许使用 orElse(0)orElseThrow(...) 会更“安全”? - wassgren
1
@wassgren 是的,无论哪个都很好,尤其是如果你可能有一个空映射。坦白地说,我只是使用了 get(),因为我更关注比较器部分。做得好。 - Todd
1
我点赞了这个回答,尽管它并没有回答实际问题,因为我会使用它。然而,我只是想知道将Stream转换为IntStream的最佳方法是什么,因为我发现在其他情况下也需要这样做。顺便说一句,stream().mapToInt(Integer::intValue) 可能已经是最好的方法了。 - Hilikus

12

另一种进行转换的方法是使用lambda表达式:mapToInt(i -> i)。 关于是否应该使用lambda表达式或方法引用的讨论可以在这里详细阐述,但总结起来就是你应该选择你觉得更易读的那一个。


1
Function.identity() - DexterHaxxor
1
@MichalŠtein Function.identity() 在这里 __不起作用__,因为与 lambda 表达式 i -> i 相反,它不会将 Integer 拆箱为 int - Jens Piegsa

2
如果问题是“在将Stream<T>转换为IntStream时,我能否避免传递转换器?”可能的答案之一是:“在Java中没有办法使这种转换具有类型安全性并同时成为Stream接口的一部分。”
实际上,将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$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)

有了这个堆栈跟踪,您能否快速确定问题在于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的扩展函数允许将某些代码调用作为类接口的一部分。因此,您可以将这个类型安全方法作为Stream 的方法来调用:
// "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) }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接