前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【小家java】使用lambda表达式传参是否有性能问题?

【小家java】使用lambda表达式传参是否有性能问题?

作者头像
YourBatman
发布2019-09-03 16:45:31
2.4K0
发布2019-09-03 16:45:31
举报
文章被收录于专栏:BAT的乌托邦

随着java8的普及,lambda表达式的书写日益增多。咱们看下面一个例子: 编程有一条原则如下:

避免创建不必要的对象:最好能重用对象,而不要在每次需要的时候就创建一个相同功能的新对象。

代码语言:javascript
复制
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

 // 1、匿名内部类
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});

//2、lambda表达式
Collections.sort(names, (a, b) -> b.compareTo(a));

如果比较器,如果我们需要使用多次,其实可以在外面定义一个比较器对象,然后直接使用就成,不用每次都new一个内部类吧?

那么问题来了:是不是每次排序都创建了一个新的Comparator对象,导致性能降低?那么还主张使用Lambda表达式吗?

回答一:
  1. 可以静态声明一个Lambda表达式内部匿名类对象;
  2. 朝生夕灭的小对象对JVM来说不是大问题;
回答二:
  1. 每次排序并不会创建一个新的 Comparator 对象,一般情况下 Lambda 表达式对应的实例(instance)一旦在内存中产生,那么 JVM 在当前环境下会重复使用这个实例。
  2. 在可以提升程序可读性和开发效率的前提下,主张使用 Lambda 表达式(这是它最现实的意义所在)
分析:lambda表达式原理

根本原因: Lamdba表示根本就不是匿名内部类的语法糖,也就是说Lambda表达式底层实现不是匿名内部类的实现方式,他们其实两个东西。 证明如下: 匿名内部类其实在编译的时候会生成一个类文件,命名以ClassName$数字的方式,所以要是Lamdba表达式底层也是由匿名内部类的方式实现的话,那肯定也会生成一个同样类似的内文件 所以我们简单把你的例子分别写在不同包下面的类,再在来检查编译后的效果 匿名内部类实现 InnerTest

这时候我们编译后,可以看到两个class文件

我们再看看lambda表达式的例子lambda表达式 LambdaTest

然而class文件只有一个:LambdaTest.class 所以,真像永远只有一个:很显然lambda和匿名内部类不是同一个东西

那Lamdba表达式是怎么实现的?

具体的,以后有时间会专门写博文分享,但是lambda的大致思路如下:

  1. lamdba表达式被编译生成当前类的一个私有静态方法
  2. 在原调用Lamdba方法的地方编译成了一个invokedynamic指令(java7 JVM中增加了一个新的指令)调用,同时呢也生成了一个对应的BootstrapMethod
  3. 当lamdba表达式被JVM执行,也就是碰到2中说到的invokedynamic指令,该指令引导调用LambdaMetafactory.metafactory方法,该方法返回一个CallSite实例
  4. 而这个CallSite实例中的target对象,也就是直接引用到一个MethodHandle实例,而这个MethodHandle实例会调用到1中生成的静态方法,在上面的例子就是lambda$main$0这个方法,完成整个lamdba表达式的使用

其实可以看到lamdba表达式在某种意义上的确比匿名内部类好很多,可读性还是大优势~哈哈,我要说大势,是因为lamdba表达式后续可以优化的空间更广,反正我是在java中用惯了,相当喜欢

关于lambda的性能问题(测试)

有许许多多关于 Java 8 中流效率的讨论,其中最著名要属这篇译文了:Java8 Lambda表达式和流操作如何让你的代码变慢5倍 看标题是不是感觉有点不敢用lambda了呢?我觉得还是不能人云亦云,还是自己测试一把吧~~~~

下面Demo用稍微真是一点的场景测试,而不是单纯的数字计算,这样应该更具有说服力些~

代码语言:javascript
复制
public class Person {
    public String name;
    public Integer age;
    // 省略get set方法
}

// Main方法测试
    public static void main(String[] args) {
        int count = 10000;
        List<Person> personList = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            personList.add(new Person("fsx", 18 + i));
        }


        Instant start = Instant.now();
        //getAgesWithLambda(personList);
        getAges(personList);
        System.out.println("耗时:" + Duration.between(start, Instant.now()).toMillis() + "ms");
    }

	// 使用lambda表达式方式~
    private static List<Integer> getAgesWithLambda(List<Person> personList) {
        return personList.stream().map(Person::getAge).collect(Collectors.toList());
    }

	// 使用普通方式~
    private static List<Integer> getAges(List<Person> personList) {
        List<Integer> ages = new ArrayList<>();
        for (Person person : personList) {
            ages.add(person.getAge());
        }
        return ages;
    }

当count=1000时:getAges(1ms) getAgesWithLambda(70ms) 当count=10000时:getAges(2ms) getAgesWithLambda(71ms) 当count=100000时:getAges(10ms) getAgesWithLambda(76ms) 当count=1000000时:getAges(21ms) getAgesWithLambda(101ms) 当count=10000000时:getAges(619ms) getAgesWithLambda(738ms)

从上面测试,我得出如下两个结论,不知道小伙伴同意不?

  1. 从人的角度,代码可读性方面lambda表达式更佳,并且可维护性更好
  2. 性能上当量非常大时,lambda的表现并不比普通方式差

所以我坚持:lambda表达式,非常推荐使用。毕竟我认为代码永远是人看得懂看得舒服才是第一位,其次才是机器~ (虽然量不大的时候lambda比普通方式差,但是既然量都不大怎么可能会有性能问题呢?完全可以忽略嘛~)

说明:测试机器为I5普通pc笔记本,各位不同机器上测试效果(耗时)上可能会有出入~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018年07月27日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 回答一:
  • 回答二:
  • 分析:lambda表达式原理
    • 那Lamdba表达式是怎么实现的?
    • 关于lambda的性能问题(测试)
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档