A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 biu波儿了罢 于 2019-4-4 09:53 编辑

                   java8特性lambda基本原理及性能分析
      Java8发布,Lambda表达式作为一项重要的特性随之而来。或许现在你已经在使用Lambda表达式来书写简洁灵活的代码。
      Lambda 表达式是一种匿名函数(对 Java 而言这并不完全正确,但现在姑且这么认为),简单地说,它是没有声明的方法,也即没有访问修饰符、返回值声明和名字。
      你可以将其想做一种速记,在你需要使用某个方法的地方写上它。当某个方法只使用一次,而且定义很简短,使用这种速记替代之尤其有效,这样,你就不必在类中费力写声明与方法了。

函数式接口
      函数式接口(functional interface)。简单来说,函数式接口是只包含一个方法的接口。比如Java标准库中的java.lang.Runnable和java.util.Comparator都是典型的函数式接口。
      java 8提供 @FunctionalInterface作为注解,这个注解是非必须的,只要接口符合函数式接口的标准(即只包含一个方法的接口),
      虚拟机会自动判断,但 最好在接口上使用注解@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。
Java中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现,

包含三个部分
      一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数
      一个箭头符号:->
      方法体,可以是表达式和代码块,方法体函数式接口里面方法的实现,如果是代码块,则必须用{}来包裹起来,且需要一个return 返回值,但有个例外,若函数式接口里面方法返回值是void,则无需{}
      (parameters) -> expression 或者 (parameters) -> { statements; }
      我们看个线程的demo

Lambda语法
[Java] 纯文本查看 复制代码
包含三个部分
一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数
一个箭头符号:->
方法体,可以是表达式和代码块,方法体函数式接口里面方法的实现,如果是代码块,则必须用{}来包裹起来,且需要一个return 返回值,但有个例外,若函数式接口里面方法返回值是void,则无需{}
(parameters) -> expression 或者 (parameters) -> { statements; }
我们看个线程的demo


Lambda原理
      以上是通过不同的方法来实现线程的,那么我们来看下JVM编译执行过程的原理是不是一样的?
      通过 javap -c TestLambda查看字节码
      通过上面的对比发现红色部分不太一样Lambda是InvokeDynamic 内部类是 invokespecial,那么是什么意思呢?
      我们先温习下jvm指令
           invokeinterface:调用接口方法;
           invokespecial:专门用来调用父类方法、私有方法和初始化方法;
           invokestatic:调用静态方法;
           invokevirtual:调用对象的一般方法。
       这四个指令所对应的类、调用的方法在编译时几乎是固定的:invokestatic所对应的类为静态方法所在的类,方法为静态方法本身;invokespecial所对应的类为当前对象,方法是固定的;invokeinterface和invokevirtual所对应的类也为当前对象,方法可以因为继承和实现进行选择,但也仅限于整个继承体系中选择。
      在java7 JVM中增加了一个新的指令invokedynamic,用于支持动态语言,即允许方法调用可以在运行时指定类和方法,不必在编译的时候确定。
      字节码中每条invokedynamic指令出现的位置称为一个动态调用点,invokedynamic指令后面会跟一个指向常量池的调用点限定符,这个限定符会被解析为一个动态调用点。
       我们发现Lambda采用的是invokedynamic指令,所以Lambda还是有别于普通方式的调用的。
       我们在来看下List中Lambda的使用。
代码
[Java] 纯文本查看 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class TestLambdaList {
    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setId(i);
            user.setUserName("un" + i);
            user.setPassword("password" + i);
            userList.add(user);
        }
        // 通过Lambda取出User里面id的值
        List<Integer> idList_LB = userList.stream().map(user -> user.getId()).collect(Collectors.toList());
        System.out.println(idList_LB);
        // 普通方法
        List<Integer> idList_PT = new ArrayList<>();
        for (User user : userList) {
            idList_PT.add(user.getId());
        }
        System.out.println(idList_PT);
    }
}
class User {
    Integer id;
    String  userName;
    String  password;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}
      以上是通过两种不同的方式实现取(出User里面id的值),很明显使用Lambda要简洁很多。

Lambda性能如何?
      有许许多多关于 Java 8 中流效率的讨论,但根据 Alex Zhitnitsky 的测试结果显示:坚持使用传统的 Java 编程风格——iterator 和 for-each 循环——比 Java 8 的实现性能更佳。他们测出的结果是如下图
他们的结论:Java 8 中提供的任何一种新方式都会产生约 5 倍的性能差异。有时使用简单迭代器循环比混合 lambda 表达式和流更有效,即便这样需要多写几行代码,且需要跳过甜蜜的语法糖(syntactic suger)。
使用迭代器或 for-each 循环是遍历 ArrayList 最有效的方式,性能比采用索引值的传统 for 循环方式好两倍。
在 Java 8 的方法中,并行流的性能最佳。但是请小心,在某些情况下它也可能会导致程序运行得更慢。

我们自己来亲自测试下到底怎么样
将上面的代码添加个时间看下耗时情况
[Java] 纯文本查看 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class TestLambdaList {
    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            User user = new User();
            user.setId(i);
            user.setUserName("un" + i);
            user.setPassword("password" + i);
            userList.add(user);
        }
        // 通过Lambda取出User里面id的值
        long t1=System.currentTimeMillis();
        List<Integer> idList_LB = userList.stream().map(user -> user.getId()).collect(Collectors.toList());
        //System.out.println(idList_LB);
        System.out.println(System.currentTimeMillis()-t1);
        // 普通方法
        long t2=System.currentTimeMillis();
        List<Integer> idList_PT = new ArrayList<>();
        for (User user : userList) {
            idList_PT.add(user.getId());
        }
        System.out.println(System.currentTimeMillis()-t2);
       // System.out.println(idList_PT);
    }
}
class User {
    Integer id;
    String  userName;
    String  password;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}
[Java] 纯文本查看 复制代码
测试1:在一个Main方法里面执行完
i=100     多次测试的结果是:Lambda耗时在120左右。但是foreach每次的结果都是0;
i=100000   多次测试的结果是:Lambda耗时在120左右。但是foreach每次的结果都是10秒以内;
i=1000000  多次测试的结果是:Lambda耗时在120左右。但是foreach每次的结果都是10秒以内;  
i=10000000  多次测试的结果是:Lambda耗时在120左右。但是foreach每次的结果都是130秒以内; 

测试2:分别在一个Main方法里面执行完各自执行,(将其中的一个注释掉)
i=100     多次测试的结果是:Lambda耗时在100左右。但是foreach每次的结果都是0;
i=100000   多次测试的结果是:Lambda耗时在100-120左右。但是foreach每次的结果都是12-16秒以内;
i=1000000 多次测试的结果是:Lambda耗时在120~180左右。但是foreach每次的结果都是30-50秒以内;  
i=10000000  多次测试的结果是:Lambda耗时在120~180左右。但是foreach每次的结果都是40~60秒以内;

由此可见:使用简单迭代器循环比混合 lambda 表达式和流更有效.
总结:
      开始使用 Java 8 的第一件事情是在实践中使用 lambda 表达式和流。但是请记住:它确实非常好,好到可能会让你上瘾!但是,我们也看到了,使用传统迭代器和 for-each 循环的 Java 编程风格比 Java 8 中的新方式性能高很多。
      当然,这也不是绝对的。但这确实是一个相当常见的例子,它显示可能会有大约 5 倍的性能差距。如果这影响到系统的核心功能或成为系统一个新的瓶颈,那就相当可怕了。



0 个回复

您需要登录后才可以回帖 登录 | 加入黑马