美文网首页JavaJava 杂谈
Hi,你的Lambda表达式掉了

Hi,你的Lambda表达式掉了

作者: 刘笔尖儿 | 来源:发表于2019-08-14 17:38 被阅读1次

    写在之前

    用了一段时间的Lambda 表达式,但是感觉都还是有点不系统,每次都是想到要用再去找。这里做一个总结,希望对大家能有所帮助。

    内容结构

    基本内容如下:

    1. Lambda 是什么
    2. Lambda 表达式实现原理
    3. Lambda 表达式语法格式

    1 Lambda 是什么

    基本概念

    Lambda的基本语法格式;

    //parameters -> expression
    (int num) -> {return num++;}
    

    Lambda表达式作为Java8的重要新特性,第一次出现是2014年了。但是因为技术更新的延迟和项目的历史原因,真正完全使用这个新特性还有一定的时间滞后。

    那么首先来看看Java8为什么要引入Lambda的新特性呢?

    Lambda是函数式编程的 λ演算的产物,百度一下,Lambda这个单词的解释是匿名函数(并不是闭包)。在Java中一切都是对象,那么为什么要引入这个匿名函数呢?这还要从函数式编程开始说起。

    函数式编程

    我们日常在Java中都是面向对象编程,这种叫做命令式编程,也就是一次方法调用是给对象发出一条指令,告诉对象该执行什么操作了。

    而函数式编程的世界里,一切都是数学函数。函数式编程是通过某种语法或者调用函数进行编程。所以函数式编程是告诉你该做什么,而命令式编程告你该怎么做。这样的区别引发的结果就是函数式编程更能体现出逻辑含义,而且代码更加简洁,这也就是使用函数式编程的原因之一。

    在命令式编程到函数式编程的转变过程中,除了语法上的变化,更重要的是思考方式的转变。

    2 Lambda表达式实现原理

    首先声明的是Jvm对Lambda表达式是无感知的,因为Lambda表达式作为一种语法糖,在类编译阶段已经将Lambda的语法糖编译为实际的实现结果,没有匿名函数,没有Lambda表达式,对于Jvm来说只知道这是一堆可以执行的字节码。

    匿名内部类

    在Java中提到Lambda表达式,一定再提到另外一个概念,匿名内部类。

        @Test
        public void runnableTest() {
    
            //普通语法
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("我是普通语法线程");
                }
            };
            runnable.run();
    
            System.out.println("**************************");
    
        }
    

    上面这段代码使用匿名内部类的方式定义了一个执行线程,在编译阶段Jvm会创建一个类文件,所以匿名内部类的定义虽然和一般的类不同,但是本质没有区别。

    下面是通过Lambda表达式定义一个执行线程,可以对比一下和匿名内部类的区别。

            
        @Test
        public void runnableTest() {
          //Lambda语法线程
            Runnable runnable1 = () -> System.out.println("我是Lambda语法线程");
            runnable1.run();
        }        
    
    

    虽然看上去类似,但是Lambda的底层实现原理并不是使用匿名内部类。Scala之前版本是使用匿名内部类来实现Lambda表达式,但是在后续版本更新中,也是通过匿名函数的方式来实现了。

    为什么不是使用匿名内部类

    首先,对于匿名内部类,编译器首先会创建一个类文件,如果匿名内部类很多,那么再类加载过程中就会对这些文件进行验证,导致影响应用的启动性能。加载类文件也是一个耗时过程,涉及到jar文件的解压、磁盘的IO,这些也会影响加载性能。

    而且,如果Lambda表达式使用匿名内部类实现,那么每个Lambda表达式都会编译成一个类文件,这样无论是类文件加载到元空间,还是创建类的对象,都会占用内存。

    同时,如果使用匿名内部类实现,那么Lambda表达式会按照匿名内部类的字节码机制进行编译,这样以后如果对Lambda表达式优化的话,那么是相当于要在匿名内部类的限制下了。

    所以Lambda实际使用invokedynamic字节码指令来生成Lambda字节码。首先生成一个Lambda工厂,当实际调用时返回Lambda表达式转化成的函数式接口实例。

    将Lambda表达式的方法体转换成方法供invokeddynamic指令调用。

    3 Lambda 语法格式

    函数式接口

    Java中定义了很多函数式接口,特点就是接口中只有一个方法。

    1. Runnable
    2. Consumer
    3. Function
    4. Predicate
    5. Supplier

    还有其他的函数式接口,同时也可以自定义函数式接口,Java提供了一个@FunctionalInterface注解,通过这个注解可以帮助验证定义的接口是否是函数式接口,如果不符合规则,编译阶段就会报错。

    @FunctionalInterface
    public interface LambdaInterface {
        void invoke();
        //如果再有这个定义,编译报错
        //void anotherInvoke();
    }
    

    Lambda表达式只支持函数式接口,这一点需要注意,下面通过实现以上函数式接口,说明一下Lambda的基本语法格式。

    语法格式一:无参数,无返回值

        @Test
        public void lambdaFormatTest1() {
    
            //普通语法
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("我是普通语法线程");
                }
            };
            runnable.run();
    
            System.out.println("**************************");
    
            //语法格式一:无参数,无返回值
            Runnable runnable1 = () -> System.out.println("Lambda表达式 语法格式一");
            runnable1.run();
    
        }
    

    语法格式二:一个参数,无返回值

        @Test
        public void lambdaFormatTest2() {
    
            Consumer<String> consumer = new Consumer<String>() {
                @Override
                public void accept(String s) {
                    System.out.println(s);
                }
            };
            consumer.accept("Consumer 原始语法格式");
    
            System.out.println("**************************");
    
            //语法格式二:一个参数,无返回值
            Consumer<String> consumer1 = (String str) -> System.out.println(str);
            consumer1.accept("Lambda表达式 语法格式二");
    
            System.out.println("**************************");
    
        }
    

    语法格式三:通过类型推断,省略参数类型

        @Test
        public void lambdaFormatTest2() {
    
            Consumer<String> consumer = new Consumer<String>() {
                @Override
                public void accept(String s) {
                    System.out.println(s);
                }
            };
            consumer.accept("Consumer 原始语法格式");
    
            System.out.println("**************************");
            //语法格式三:通过类型推断,省略参数类型
            Consumer<String> consumer2 = (str) -> System.out.println(str);
            consumer2.accept("Lambda表达式 语法格式三");
    }
    

    语法格式四:一个参数省略

        @Test
        public void lambdaFormatTest2() {
    
            Consumer<String> consumer = new Consumer<String>() {
                @Override
                public void accept(String s) {
                    System.out.println(s);
                }
            };
            consumer.accept("Consumer 原始语法格式");
    
            System.out.println("**************************");
            //语法格式四:一个参数,省略()
            Consumer<String> consumer3 = str -> System.out.println(str);
            consumer3.accept("Lambda表达式 语法格式四");
    
        }
    

    语法格式五:两个或以上参数,多条执行语句,有返回值

        @Test
        public void lambdaFormatTest3() {
    
            //普通语法
            Comparator<Integer> comparator = new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o1.compareTo(o2);
                }
            };
            System.out.println(comparator.compare(1, 2));
    
            System.out.println("**************************");
    
            //语法格式五:两个或以上参数,多条执行语句,有返回值
            Comparator<Integer> comparator1 = (Integer o1, Integer o2) -> {
                System.out.println("第一个参数:" + o1);
                System.out.println("第二个参数:" + o2);
                return o1.compareTo(o2);
            };
            System.out.println("Lambda表达式 语法格式五: " + comparator1.compare(1, 2));
    }
    

    语法格式六:一条执行语句,有返回值

        @Test
        public void lambdaFormatTest3() {
    
            //普通语法
            Comparator<Integer> comparator = new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o1.compareTo(o2);
                }
            };
            System.out.println(comparator.compare(1, 2));
          
            System.out.println("**************************");
    
            //语法格式六:一条执行语句,有返回值
            Comparator<Integer> comparator2 = (o1, o2) -> o1.compareTo(o2);
            System.out.println("Lambda表达式 语法格式六: " + comparator2.compare(1, 2));
    
        }
    

    以上介绍了Lambda的6中语法格式,实际使用中,Lamba经常和Stream流结合使用,变化可能更丰富些,但基本也在这6中情况中。

    最后

    本篇主要介绍了Lambda表达式是什么,实现原理,基本使用。其实还有很多方面没有说明,例如异常处理、并发处理等。后续还会对Lambda相关的内容继续学习,补充内容。

    而且Lambda和Stream的结合是日常开发中非常高频的使用场景,Stream也是Java8的亮瞎眼新特性之一,接下来会总结一下Stream的相关内容。

    相关文章

      网友评论

        本文标题:Hi,你的Lambda表达式掉了

        本文链接:https://www.haomeiwen.com/subject/skdnjctx.html