备注于 2019-08-10:

  以上是我对Lambda原理比较模糊时的测试,现在觉得唯一的用处在于对比第二篇得出在循环中使用Lambda会慢很多。

  实际运用的话,我建议看下一篇:Java的几种创建实例方法的性能对比(二)

近来打算自己封装一个比较方便读写的Office Excel 工具类,前面已经写了一些,比较粗糙本就计划重构一下,刚好公司的电商APP后台原有的导出Excel实现出现了可怕的性能问题,600行的数据生成Excel工作簿居然需要50秒以上,客户端连接都被熔断了还没导出来,挺巧,那就一起解决吧。

在上一个版本里呢,我认为比较巧妙的地方在于用函数式编程的方式代替反射,很早以前了解了反射的一些底层后我就知道反射的性能很差,但一直没实际测试过各种调用场景的性能差距。

至于底层字节码、CPU指令这些我就不深究了,我还没到那个级别,那这次就来个简单的测试吧。

目标:创建Man对象。

方式:

① 直接引用 new Man();

② 使用反射

③ 使用内部类

④ 使用Lombda表达式

⑤ 使用Method Reference

在学习Java8新特性的时候,我所了解到的是Lombda表达式是内部类的一种简化书写方式,也就是语法糖,但两者间在运行时居然有比较明显的性能差距,让我不得不怀疑它底层到底是啥东西,时间精力有限先记着,有必要的时候再去啃openJDK吧。

还有就是Lombda和Method Reference从表现来看,底层应该是同一个东西,但IDEA既然分开两种内部类的写法推荐,那就分开对待吧。

测试时每种方式循环调用 1 亿次,每种方式连续计算两次时间,然后对比第二次运行的结果,直接run没有采用debug运行。

贴代码:

 package com.supalle.test;

 import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.function.Supplier; /**
* @描述:语法PK
* @作者:Supalle
* @时间:2019/7/26
*/
public class SyntaxPKTest { /* 循环次数 */
private final static int SIZE = 100000000; /* 有类如下 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
private static class Man {
private String name;
private int age;
} /**
* 使用 new Man();
*
* @return 运行耗时
*/
public static long runWithNewConstructor() {
long start = System.currentTimeMillis(); for (int i = 0; i < SIZE; i++) {
new SyntaxPKTest.Man();
} return System.currentTimeMillis() - start;
} /**
* 使用反射
*
* @return 运行耗时
*/
public static long runWithReflex() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<SyntaxPKTest.Man> constructor = SyntaxPKTest.Man.class.getConstructor();
long start = System.currentTimeMillis(); for (int i = 0; i < SIZE; i++) {
constructor.newInstance();
} return System.currentTimeMillis() - start;
} /**
* 使用内部类调用 new Man();
*
* @return 运行耗时
*/
public static long runWithSubClass() {
long start = System.currentTimeMillis(); for (int i = 0; i < SIZE; i++) {
new Supplier<SyntaxPKTest.Man>() {
@Override
public SyntaxPKTest.Man get() {
return new SyntaxPKTest.Man();
}
}.get(); } return System.currentTimeMillis() - start;
} /**
* 使用Lambda调用 new Man();
*
* @return 运行耗时
*/
public static long runWithLambda() {
long start = System.currentTimeMillis(); for (int i = 0; i < SIZE; i++) {
((Supplier<SyntaxPKTest.Man>) () -> new SyntaxPKTest.Man()).get();
} return System.currentTimeMillis() - start;
} /**
* 使用 MethodReference
*
* @return 运行耗时
*/
public static long runWithMethodReference() {
long start = System.currentTimeMillis(); for (int i = 0; i < SIZE; i++) {
((Supplier<SyntaxPKTest.Man>) SyntaxPKTest.Man::new).get();
} return System.currentTimeMillis() - start;
} public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { // 测试前调用一下,加载Man字节码,尽量公平
SyntaxPKTest.Man man1 = new SyntaxPKTest.Man();
SyntaxPKTest.Man man2 = new SyntaxPKTest.Man("张三", 20); System.out.println("测试环境:CPU核心数 - " + Runtime.getRuntime().availableProcessors()); System.out.println(); // 这里的话对比再次调用的时间
System.out.println("首次使用 new Man() 耗时:" + runWithNewConstructor());
System.err.println("再次使用 new Man() 耗时:" + runWithNewConstructor());
System.out.println("首次使用反射 耗时:" + runWithReflex());
System.err.println("再次使用反射 耗时:" + runWithReflex());
System.out.println("首次使用内部类调用 new Man() 耗时:" + runWithSubClass());
System.err.println("再次使用内部类调用 new Man() 耗时:" + runWithSubClass());
System.out.println("首次使用Lambda调用 new Man() 耗时:" + runWithLambda());
System.err.println("再次使用Lambda调用 new Man() 耗时:" + runWithLambda());
System.out.println("首次使用 MethodReference 耗时:" + runWithMethodReference());
System.err.println("再次使用 MethodReference 耗时:" + runWithMethodReference()); } }

运行结果:

一:

测试环境:CPU核心数 - 8

首次使用 new Man()            耗时:5
再次使用 new Man() 耗时:3
首次使用反射 耗时:312
再次使用反射 耗时:276
首次使用内部类调用 new Man() 耗时:6
再次使用内部类调用 new Man() 耗时:3
首次使用Lambda调用 new Man() 耗时:142
再次使用Lambda调用 new Man() 耗时:100
首次使用 MethodReference 耗时:86
再次使用 MethodReference 耗时:85

二:

测试环境:CPU核心数 - 8

首次使用 new Man()            耗时:5
再次使用 new Man() 耗时:2
首次使用反射 耗时:326
再次使用反射 耗时:275
首次使用内部类调用 new Man() 耗时:6
再次使用内部类调用 new Man() 耗时:3
首次使用Lambda调用 new Man() 耗时:122
再次使用Lambda调用 new Man() 耗时:86
首次使用 MethodReference 耗时:102
再次使用 MethodReference 耗时:83

三:

测试环境:CPU核心数 - 8

首次使用 new Man()            耗时:5
再次使用 new Man() 耗时:3
首次使用反射 耗时:322
再次使用反射 耗时:288
首次使用内部类调用 new Man() 耗时:7
再次使用内部类调用 new Man() 耗时:2
首次使用Lambda调用 new Man() 耗时:128
再次使用Lambda调用 new Man() 耗时:92
首次使用 MethodReference 耗时:97
再次使用 MethodReference 耗时:81

如果Lambda和MethodReference调换一下位置如下:

 1      System.out.println("首次使用 new Man()            耗时:" + runWithNewConstructor());
System.err.println("再次使用 new Man() 耗时:" + runWithNewConstructor());
System.out.println("首次使用反射 耗时:" + runWithReflex());
System.err.println("再次使用反射 耗时:" + runWithReflex());
System.out.println("首次使用内部类调用 new Man() 耗时:" + runWithSubClass());
System.err.println("再次使用内部类调用 new Man() 耗时:" + runWithSubClass());
7 System.out.println("首次使用 MethodReference 耗时:" + runWithMethodReference());
8 System.err.println("再次使用 MethodReference 耗时:" + runWithMethodReference());
System.out.println("首次使用Lambda调用 new Man() 耗时:" + runWithLambda());
System.err.println("再次使用Lambda调用 new Man() 耗时:" + runWithLambda());

一:

测试环境:CPU核心数 - 8

首次使用 new Man()            耗时:6
再次使用 new Man() 耗时:2
首次使用反射 耗时:351
再次使用反射 耗时:270
首次使用内部类调用 new Man() 耗时:6
再次使用内部类调用 new Man() 耗时:3
首次使用 MethodReference 耗时:128
再次使用 MethodReference 耗时:97
首次使用Lambda调用 new Man() 耗时:82
再次使用Lambda调用 new Man() 耗时:74

二:

测试环境:CPU核心数 - 8

首次使用 new Man()            耗时:5
再次使用 new Man() 耗时:3
首次使用反射 耗时:318
再次使用反射 耗时:297
首次使用内部类调用 new Man() 耗时:6
再次使用内部类调用 new Man() 耗时:2
首次使用 MethodReference 耗时:117
再次使用 MethodReference 耗时:100
首次使用Lambda调用 new Man() 耗时:91
再次使用Lambda调用 new Man() 耗时:79

三:

测试环境:CPU核心数 - 8

首次使用 new Man()            耗时:6
再次使用 new Man() 耗时:3
首次使用反射 耗时:319
再次使用反射 耗时:277
首次使用内部类调用 new Man() 耗时:8
再次使用内部类调用 new Man() 耗时:3
首次使用 MethodReference 耗时:139
再次使用 MethodReference 耗时:85
首次使用Lambda调用 new Man() 耗时:103
再次使用Lambda调用 new Man() 耗时:84

总结:

  ① 如果不需要足够的灵活性,直接使用 new 来构造一个对象,效率最高的。

   ② 反射确确实实是垫底,当然它也给我们提供了足够全面的、灵活的类操纵能力。

    ③ 使用内部类的方式,效率上和直接new 非常贴近,虽然看起来代码多一些,但是足够灵活。

④ Lambda和Method Reference效率其实很贴近,又是一起在Java8推出的,底层实现应该是一样的,在效率上比起反射好很多。

  上个版本中,我使用的Method Reference,下个版本还会继续使用Method Reference,因为接口方式和内部类一致,如果碰到某些对性能要求非常极致的使用场景,可以在使用时以内部类的方式替代Method Reference而不需要改变工具类的代码。

  

  备注于 2019-08-10:

  以上是我对Lambda原理比较模糊时的测试,现在觉得唯一的用处在于对比第二篇得出在循环中使用Lambda会慢很多。

  实际运用的话,我建议看下一篇:Java的几种创建实例方法的性能对比(二)

Java的几种创建实例方法的性能对比的更多相关文章

  1. Java的几种创建实例方法的性能对比(二)

    上一篇里对几种书写方式进行了简单的测试,得出了一些初步的结论.这次简单了解Lambda原理后,对测试做了一些调整,发现得到不一样的结果,而这个调整,明显更契合实际开发的场景. 暂时还没有亲自去验证,主 ...

  2. Java中两种实现多线程方式的对比分析

    本文转载自:http://www.linuxidc.com/Linux/2013-12/93690.htm#0-tsina-1-14812-397232819ff9a47a7b7e80a40613cf ...

  3. Go_18: Golang 中三种读取文件发放性能对比

    Golang 中读取文件大概有三种方法,分别为: 1. 通过原生态 io 包中的 read 方法进行读取 2. 通过 io/ioutil 包提供的 read 方法进行读取 3. 通过 bufio 包提 ...

  4. Golang 中三种读取文件发放性能对比

    Golang 中读取文件大概有三种方法,分别为: 1. 通过原生态 io 包中的 read 方法进行读取 2. 通过 io/ioutil 包提供的 read 方法进行读取 3. 通过 bufio 包提 ...

  5. 求斐波那契数列第n位的几种实现方式及性能对比(c#语言)

    在每一种编程语言里,斐波那契数列的计算方式都是一个经典的话题.它可能有很多种计算方式,例如:递归.迭代.数学公式.哪种算法最容易理解,哪种算法是性能最好的呢? 这里给大家分享一下我对它的研究和总结:下 ...

  6. java线程——三种创建线程的方式

    前言 线程,英文Thread.在java中,创建线程的方式有三种: 1.Thread 2.Runnable 3.Callable 在详细介绍下这几种方式之前,我们先来看下Thread类和Runnabl ...

  7. Java数组3种创建方式

    public static void main(String[] args){ /** * 1. 固定大小的空数组, 动态创建 */ String[] strArr1 = new String[3]; ...

  8. Delegate、Thread、Task、ThreadPool几种方式创建异步任务性能对比

    开始预测的结果是 Task>Delegate>ThreadPool>>Thread. (一)测试代码 static async Task<int> AsyncTas ...

  9. 几种流行Webservice框架性能对比

    1      摘要 开发webservice应用程序中离不开框架的支持,当open-open网站列举的就有30多种,这对于开发者如何选择带来一定的疑惑.性能Webservice的关键要素,不同的框架性 ...

随机推荐

  1. Oracle数据库密码重置、导入导出库命令

    重置办法如下:打开CMD命令提示符,然后输入下面命令进行重置: 输入sqlplus /nolog,回车 SQL> conn /as sysdba 已连接: SQL>alter user s ...

  2. 文件夹管理工具(MVC+zTree+layer)

    文件夹管理工具(MVC+zTree+layer)(附源码)   写在前 之前写了一篇关于 文件夹与文件的操作的文章  操作文件方法简单总结(File,Directory,StreamReader,St ...

  3. django自带的cache

    cache语法 from django.core.cache import cache #存入内存 cache.set("aaa",123) #从内存中获取 cache.get(& ...

  4. comboBox控件动态绑定数据

    /// <summary>        /// load加载数据        /// </summary>        /// <param name=" ...

  5. 移动IM开发指南2:心跳指令详解

    <移动IM开发指南>系列文章将会介绍一个IM APP的方方面面,包括技术选型.登陆优化等.此外,本文作者会结合他在网易云信多年iOS IM SDK开发的经验,深度分析实际开发中的各种常见问 ...

  6. spring 5.x 系列第4篇 —— spring AOP (代码配置方式)

    文章目录 一.说明 1.1 项目结构说明 1.2 依赖说明 二.spring aop 2.1 创建待切入接口及其实现类 2.2 创建自定义切面类 2.3 配置切面 2.4 测试切面 2.5 切面执行顺 ...

  7. Ember报错

    错误是ember-data的版本不对 解决办法是: npm install --save ember-data@2.14.2 //bing.com中去查资料,应有尽有

  8. APPCAN 通信(appcan.ajax)

    引用JS: function callWebService(serviceName, params, methodType, offline, callBack, callBackError){ // ...

  9. vscode左边侧边栏字体的大小

    相信很多小伙伴们都会在用vscode的时候,当屏幕大小发生变化的时候,你可能会觉得左边的字体太小了,我也遇到了这样的问题,百度也没有找到解决办法,自己摸索了几天,发现可以通过ctrl+shift+ + ...

  10. vim配置轻量级IDE

    安装VIM 安装YouCompleteMe的时候, 需要VIM的版本比较高, 一般一些Linux长期支持版的Vim包就比较老, 所以可能需要编译安装. 卸载之前系统中安装的版本: CentOS/Fed ...