Java 开销:在基本流与装箱之间转换

Java 开销:在基本流与装箱之间转换,java,java-8,java-stream,primitive-types,Java,Java 8,Java Stream,Primitive Types,我使用的Java 8流API如下所示: private Function<Long, Float> process; // Intermediate step (& types) private long getWeekFrequency(final ScheduleWeek week) { return week.doStreamStuff().count(); // Stream<>.count() returns l

我使用的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(", "));
}
double process (long value) {
       // do something
} 
私有函数进程;//中间步骤(&类型)
私人长getWeekFrequency(最终计划周){
return week.doStreamStuff().count();//Stream.count()返回long
}
@凌驾
公共浮动分析(最终计划样本){
返回(浮动)样本//返回浮动
.getWeeks()
.stream()
.mapToLong(this::getWeekFrequency)//对象为long
.mapToDouble(process::apply)//将浮点加宽为双精度
.sum();
}
@凌驾
公共字符串解释(最终计划示例){
回样
.getWeeks()
.stream()
.map(this::getWeekFrequency)//是否更改流类型?
.map(字符串::valueOf)
.collect(收集器。连接(“,”);
}
问题
  • 我假设在对象/基本流类型之间更改时会有开销。。。如果我坚持流,这与装箱开销相比如何?

  • 如果我以后换回去呢?

具体地说:
在analyst中,我应该使用
.map(…).mapToDouble(…)


在解释中,我是否应该使用
.mapToLong(…).mapToObj(…)

那么让我们来分解一下:

.mapToLong(this::getWeekFrequency)
给你一个原始的长

.mapToDouble(process::apply)
由于
过程
函数需要,此原语long被装箱为
long
process
返回一个映射到基本双精度(通过
Float.doubleValue()
)的
Float

这些被求和,求和被转换为一个基本浮点(缩小,但你说是安全的),然后返回


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

@FunctionalInterface
public interface LongToFloatFunction
{
    float apply(long l);
}
然后我们将声明更改为:

private LongToFloatFunction process;

并保持其他一切不变,这将防止任何自动装箱。函数返回的原语浮点将自动扩大为原语双精度浮点

从您的定义来看,
过程
看起来有点像这样:

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(", "));
}
double process (long value) {
       // do something
} 
同样地,如果您这样做:
map(…).mapToDouble
您每次都会创建一个
Long
类型的对象,只是在
过程中使用后立即取消装箱。我会让代码保持原样,使用原语实现来避免这种情况

第二个使用
字符串#valueOf
。在
long
的情况下,将调用
String.valueOf(l)
,该函数适用于原语:
long.toString(l)


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

我还有一个问题要补充:它在你的系统中有那么重要吗?您是否经历过/预计会出现任何性能问题?“我是否应该只考虑使用双/双”——你的业务需求是什么?您的模型是什么样子的?如果这是您希望改进的工作代码,那么这个问题可能会吸引更多有趣的答案。不过,它可能需要重新编写,以包含您简化的部分以及所涉及的类的定义。@Thomas-我现在对性能相当满意(这已经足够了,所以我重视可读性)。我想我是想了解我是否在流的中间部分正确使用了
mapToLong()
vs
map()
。我将改写双/双位。。。这太开放了。您必须衡量性能,但我认为用for循环替换analyze()正文应该可以提高性能和可读性。@Aaron-好建议。。。我没想到会那样做。我想我真的是在问我是否理解这些东西的作用;这确实有助于在我的整个上下文中加入我的理解要点(特别是更好地了解primitive
函数
类型,以及我可以创建我自己的primitive->primitive
函数接口
的事实),我的流程函数具有一些独立于输入类型的通用逻辑(即,我可以将Float更改为Float,但我可能希望将输入参数装箱:
Float apply(T)
。在之前的编辑中,您提到了OOTB
ToDoubleFunction
,这可能更适合我(虽然我可能会制作自己的
ToFloatFunction
)。如果输入总是在函数的输入上装箱,这是否意味着
.map(myToFloatFunction)。maptouble(process::apply)
更有效?(因为long还是装箱的,这样可以避免更改流类型)在大多数实际应用中,没有理由使用此
LongtoFloat函数
,因为已经存在的
LongToDoubleFunction
就足够了。对于中间值,很少有理由将其缩减为
float
值集,使用
float
仅用于存储。t事实上根本没有
FloatStream
,作为一个处理管道,使用
float
而不是
double
没有任何好处。巧妙的副作用是,您可以将函数直接传递到
LongStream.mapToDouble
,而无需通过
对其进行修改::apply
。@Holger很好的一点。我有点错过了当我检查
java.util.function
(如果你检查我以前的版本,我建议
ToDoubleFunction
)时,我修改了该界面。你说得对,这可能是最合适的选择。+1:谢谢你的回答@Eugene;它帮助我更详细地了解内部步骤。我同意Michael的回答,因为