JavaJava 杂谈

Hi,你的Lambda表达式掉了

2019-08-14  本文已影响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的相关内容。

上一篇下一篇

猜你喜欢

热点阅读