深入探索Java 8 Lambda表达式
作者:网络转载 发布时间:[ 2016/6/15 10:34:56 ] 推荐标签:测试开发技术 Java
性能分析
Lambda表达式主要的优势表现在性能方面,虽然使用它很轻松的将很多行代码缩减成一句,但是其内部实现却不这么简单。下面对内部实现的每一步进行性能分析。
第一步是连接,对应的是我们上面提到的Lambda工厂。这一步相当于匿名内部类的类加载过程。来自Oracle的Sergey Kuksenko发布过相关的性能报告,并且他也在2013 JVM语言大会该话题做过分享。报告表明,Lambda工厂的预热准备需要消耗时间,并且这个过程比较慢。伴随着更多的调用点连接,代码被频繁调用后(比如被JIT编译优化)性能会提升。另一方面如果连接处于不频繁调用的情况,那么Lambda工厂方式也会比匿名内部类加载要快,高可达100倍。
第二步是捕获变量。正如我们前面提到的,如果是不进行捕获变量,这一步会自动进行优化,避免在基于Lambda工厂实现下额外创建对象。对于匿名内部类而言,这一步对应的是创建外部类的实例,为了优化内部类这一步的问题,我们需要手动的修改代码,如创建一个对象,并将它设置给一个静态的属性。如下述代码:
// Hoisted Functionpublic static final Function parseInt = new Function()
{
public Integer apply(String arg)
{
return Integer.parseInt(arg);
}}; // Usage:int result = parseInt.apply(“123”);
第三部是真实方法的调用。在这一步中匿名内部类和Lambda表达式执行的操作相同,因此没有性能上的差别。不进行捕获的Lambda表达式要比进行static优化过的匿名内部类较优。进行变量捕获的Lambda表达式和匿名内部类表达式性能大致相同。
在这一节中,我们明显可以看到Lambda表达式的实现表现良好,匿名内部类通常需要我们手动的进行优化来避免额外对象生成,而对于不进行变量捕获的Lambda表达式,JVM已经为我们做好了优化。
实践中的性能分析
理解了Lambda的性能模型很是重要,但是实际应用中的总体性能如何呢?我们在使用Java 8 编写了一些软件项目,一般都取得了很好的效果。非变量捕获的Lambda表达式给我们带来了很大的帮助。这里有一个很特殊的例子描述了关于优化方向的一些有趣的问题。
这个例子的场景是代码需要运行在一个要求GC暂定时间越少越好的系统上。因而我们需要避免创建大量的对象。在这个工程中,我们使用了大量的Lambda表达式来实现回调处理。然而在这些使用Lambda实现的回调中很多并没有捕获局部变量,而是需要引用当前类的变量或者调用当前类的方法。然而目前仍需要对象分配。下面是我们提到的例子的代码:
public MessageProcessor() {} public int processMessages()
{
return queue.read(obj ->
{
if (obj instanceof NewClient)
{
this.processNewClient((NewClient) obj);
} ...
});
}
有一个简单的办法解决这个问题,我们将Lambda表达式的代码提前到构造方法中,并将其赋值给一个成员属性。在调用点我们直接引用这个属性即可。下面是修改后的代码:
Java
private final Consumer handler; public MessageProcessor()
{
handler = obj ->
{if (obj instanceof NewClient)
{
this.processNewClient((NewClient) obj);
} ...
};
}
public int processMessages()
{
return queue.read(handler);
}
然而上面的修改后代码给却给整个工程带来了一个严重的问题:性能分析表明,这种修改产生很大的对象申请,其产生的内存申请在总应用的60%以上。
类似这种无关上下文的优化可能带来其他问题。
纯粹为了优化的目的,使用了非惯用的代码写法,可读性会稍差一???。
内存分配方面的问题,示例中为MessageProcessor增加了一个成员属性,使得MessageProcessor对象需要申请更大的内存空间。Lambda表达式的创建和捕获位于构造方式中,使得MessageProcessor的构造方法调用缓慢一些。
我们遇到这种情况,需要进行内存分析,结合合理的业务用例来进行优化。有些情况下,我们使用成员属性确保为经常调用的Lambda表达式只申请一个对象,这样的缓存策略大有裨益。任何性能调优的科学的方法都可以进行尝试。
上述的方法也是其他程序员对Lambda表达式进行优化应该使用的。书写整洁,简单,函数式的代码永远是第一步。任何优化,如上面的提前代码作为成员属性,都必须结合真实的具体问题进行处理。变量捕获并申请对象的Lambda表达式并非不好,像我们我们写出new Foo()代码并非一无是处一样。
除此之外,我们想要写出优的Lambda表达式,常规书写很重要。如果一个Lambda表达式用来表示一个简单的方法,并且没有必要对上下文进行捕获,大多数情况下,一切以简单可读即可。
总结
在这片文章中,我们研究了Lambda表达式不是简单的匿名内部类的语法糖,为什么匿名内部类不是Lambda表达式的内部实现机制以及Lambda表达式的具体实现机制。对于大多数情况来说,Lambda表达式要比匿名内部类性能更优。然而现状并非完美,基于测量驱动优化,我们仍然有很大的提升空间。
Lambda表达式的这种实现形式并非Java 8 所有。Scala曾经通过生成匿名内部类的形式支持Lambda表达式。在Scala 2.12版本,Lambda的实现形式替换为Java 8中的Lambda 工厂机制。后续其他可以在JVM上运行的语言也可能支持Lambda的这种机制。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。
相关推荐
Java性能测试有哪些不为众人所知的原则?Java设计模式??装饰者模式谈谈Java中遍历Map的几种方法Java Web入门必知你需要理解的Java反射机制知识总结编写更好的Java单元测试的7个技巧编程常用的几种时间戳转换(java .net 数据库)适合Java开发者学习的Python入门教程Java webdriver如何获取浏览器新窗口中的元素?Java重写与重载(区别与用途)Java变量的分类与初始化JavaScript有这几种测试分类Java有哪四个核心技术?给 Java开发者的10个大数据工具和框架Java中几个常用设计模式汇总java生态圈常用技术框架、开源中间件,系统架构及经典案例等
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11热门文章
常见的移动App Bug??崩溃的测试用例设计如何用Jmeter做压力测试QC使用说明APP压力测试入门教程移动app测试中的主要问题jenkins+testng+ant+webdriver持续集成测试使用JMeter进行HTTP负载测试Selenium 2.0 WebDriver 使用指南