备注于 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. Qt Style Sheet实践(二):组合框QComboBox的定制(24K纯开源)——非常漂亮

    组合框是一个重要且应用广泛的组件,一般由两个子组件组成:文本下拉单部分和按钮部分.在许多既需要用户选择.又需要用户手动输入的应用场景下,组合框能够很好的满足我们的需求.如我们经常使用的聊天软件QQ登录 ...

  2. spring源码解析之IOC容器(三)——依赖注入

    上一篇主要是跟踪了IOC容器对bean标签进行解析之后存入Map中的过程,这些bean只是以BeanDefinition为载体单纯的存储起来了,并没有转换成一个个的对象,今天继续进行跟踪,看一看IOC ...

  3. centos 5.5版本中添加ext4格式

    1.我在使用centos 5.5版本做练习的时候发现默认是不支持ext4文件格式. 在添加硬盘后,用fdisk -l 查看到信息如下: 分区完后,使用命令:mkfs -t ext4 /dev/sdb会 ...

  4. spring boot 2.x 系列 —— spring boot 整合 dubbo

    文章目录 一. 项目结构说明 二.关键依赖 三.公共模块(boot-dubbo-common) 四. 服务提供者(boot-dubbo-provider) 4.1 提供方配置 4.2 使用注解@Ser ...

  5. vagrant+xdebug

    https://segmentfault.com/a/1190000007789295

  6. Analysis of requirement specification of parking management system

    Analysis of requirement specification of parking management system PURPOSE OF THE SYSTEM The parking ...

  7. MySQL性能分析之Explain

    目录 Explain基础 Explain进阶 Explain基础 关于explain命令相信大家并不陌生,具体用法和字段含义可以参考官网explain-output ,这里需要强调rows是核心指标, ...

  8. Codeforces 348B:Apple Tree(DFS+LCM+思维)

    http://codeforces.com/contest/348/problem/B 题意:给一棵树,每个叶子结点有w[i]个苹果,每个子树的苹果数量为该子树所有叶子结点苹果数量之和,要使得每个结点 ...

  9. vue中ajax应用

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. Elasticsearch(二)集群设置

    Elasticsearch支持多播和单播自动发现节点,但多播已经不被大多数操作系统支持,并且安全性不高,这里主要用单播发现节点,配置如下 discovery.zen.ping.multicast.en ...