Java 开销:在基本流与装箱之间转换
我使用的Java 8流API如下所示: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
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()
vsmap()
。我将改写双/双位。。。这太开放了。您必须衡量性能,但我认为用for循环替换analyze()正文应该可以提高性能和可读性。@Aaron-好建议。。。我没想到会那样做。我想我真的是在问我是否理解这些东西的作用;这确实有助于在我的整个上下文中加入我的理解要点(特别是更好地了解primitive函数
类型,以及我可以创建我自己的primitive->primitive函数接口
的事实),我的流程函数具有一些独立于输入类型的通用逻辑(即,我可以将Float更改为Float,但我可能希望将输入参数装箱:Float apply(T)
。在之前的编辑中,您提到了OOTBToDoubleFunction
,这可能更适合我(虽然我可能会制作自己的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的回答,因为