开销:原始流与装箱之间的转换

Overhead: Convert Between Primitive Streams vs. Boxing

我正在使用 Java 8 流 API,如下所示:

private Function<Long, Float> process;          // Intermediate step (& types)

private long getWeekFrequency(final ScheduleWeek week) {
    return week.doStreamStuff().count();        // Stream<>.count() returns long
}

@Override
public float analyse(final Schedule sample) {
    return (float) sample                       // cast back to float
            .getWeeks()
            .stream()
            .mapToLong(this::getWeekFrequency)  // object to long
            .mapToDouble(process::apply)        // widen float to double
            .sum();
}

@Override
public String explain(final Schedule sample) {
    return sample
            .getWeeks()
            .stream()
            .map(this::getWeekFrequency)        // change stream type?
            .map(String::valueOf)
            .collect(Collectors.joining(", "));
}

问题

具体来说:
在分析师中,我应该使用.map(...).mapToDouble(...)吗?
在解释中,我应该使用 .mapToLong(...).mapToObj(...)?

所以让我们分解一下:

.mapToLong(this::getWeekFrequency)

给你一个原始长。

.mapToDouble(process::apply)

这个原始 long 被装箱到 Long 因为 process 函数需要它。 process returns 一个 Float 映射到原始双精度数(通过 Float.doubleValue())。

对这些求和并将总和转换为原始浮点数(变窄,但你说安全),然后将其返回。


那么我们怎样才能摆脱一些自动装箱呢?我们想要一个 FunctionalInterface 与我们的 process 函数完全匹配,而不使用任何框 类。没有一个我们可以使用现成的,但我们可以很容易地这样定义它:

@FunctionalInterface
public interface LongToFloatFunction
{
    float apply(long l);
}

然后我们将声明更改为:

private LongToFloatFunction process;

并保持其他一切不变,这将防止任何自动装箱。函数返回的原始 float 将自动加宽为原始 double。

好吧,根据你的定义,process 看起来有点像这样:

double process (long value) {
       // do something
} 

因此,如果您这样做:map(...).mapToDouble 您每次都会创建一个 Long 类型的对象,只是在 process 中使用后立即将其拆箱。我会保留代码,因为它是使用可以避免这种情况的原始实现。

第二个使用String#valueOf。在 long 的情况下,将调用 String.valueOf(l),它适用于原语:Long.toString(l).

Object 的情况下将调用相同的方法, 需要注意的是第一次装箱发生。所以,我会把它改成 mapToLong