美文网首页
Lambda 表达式

Lambda 表达式

作者: 柯基去哪了 | 来源:发表于2019-06-06 23:51 被阅读0次

    一 lambda 表达式初探

    关于定义:Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。

    上面这句话,在不了解代码的时候,读起来有点生涩。在了解完相关特性之后,再回来看这个定义,才能更好的理解。

    /**
     * @author gaopeng@doctorwork.com
     * @description
     * @date 2019-06-05 9:00
     **/
    public interface MathOperation {
    
        int operation(int a, int b);
    }l
    ===================================
    /**
     * @author gaopeng@doctorwork.com
     * @description
     * @date 2019-06-05 9:03
     **/
    public class LambdaTest {
    
        public static void main(String[] args) {
            // 匿名函数与 lambda 表达式的对比,将匿名函数的声明完全简化,仅仅保留了入参列表和具体实现的表达式,
            // 如果函数内方法的实现仅有一行,那么这个 lambda 表达式看起来就十分的简洁
            MathOperation addition = (int a, int b) -> a + b;
            MathOperation normalAddition = new MathOperation() {
                @Override
                public int operation(int a, int b) {
                    return a + b;
                }
            };
            // 不使用参数的类型声明
            MathOperation substract = ((a, b) -> a - b);
            // 在大括号中包含返回值
            MathOperation multiplication = (((a, b) -> { return a * b;}));
            // 最简洁的写法:没有大括号、返回语句
            MathOperation division = (a, b) -> a / b;
    
    
            /**
             * lambda 表达式的变量作用域
             */
    
        }
    }
    

    面对数学操作 MathOperation 接口,要想使用它,我们有两种大的方式分类:

    1. 独立的类,实现这个接口并单独使用
    2. 使用匿名函数

    老的方法,使用匿名函数的话,写法就是 demo 中的样式:

            MathOperation normalAddition = new MathOperation() {
                @Override
                public int operation(int a, int b) {
                    return a + b;
                }
            };
    

    这个场景,是 JDK8 之后 lambda 表达式的适用场景:

            // 匿名函数与 lambda 表达式的对比,将匿名函数的声明完全简化,仅仅保留了入参列表和具体实现的表达式,
            // 如果函数内方法的实现仅有一行,那么这个 lambda 表达式看起来就十分的简洁
            MathOperation addition = (int a, int b) -> a + b;
    

    二 函数式接口

    前面一节的例子中,自定义接口 MathOperation,有且仅有一个方法定义,所有实现了这个接口的类都被强制要求实现该方法。这样的接口范例,在 JDK8 之中有了一个新的官方定义 函数式接口,也叫 SAM 接口-Single Abstract Method Interface,并得到了一个专有的注解

    @FunctionalInterface
    

    如果自定义的接口里面,加上了这个注解,在编译阶段就会检查这个接口是否符合 函数式接口的定义规范,如果不规范,那么这个类就无法完成编译。

    那么问题来了,为什么要定义这样一个注解呢,只要接口使用了这个注解并通过了编译检查,那么这些类型的接口,就都可以方便地使用 lambda 表达式编写,使用 lambda 表达式完成匿名内部类的简写。

            // 自定义比较器
            Comparator<Student> studentComparator = new Comparator<Student>() {
                @Override
                public int compare(Student o1, Student o2) {
                    return o1.getAge() - o2.getAge();
                }
            };
            // 使用lambda表达式完成的自定义比较器
            Comparator<Student> lambdaComparator = (student1, student2) -> student1.getAge() - student2.getAge();
    

    常用的自定义比较器 Comparator 在 JDK8 的版本上,都被增加了这个 @FunctionalInterface 注解。

    函数式接口的别名让我们就可以很清楚地了解到这类接口的要求:只有一个抽象方法的声明。如果使用了注解还声明了多个抽象方法,那么就会报错,无法通过编译检查。

    那么除了抽象方法,函数式接口还是允许拥有一些其他的东西:

    1. 接口默认方法(JDK8 特性)-接口默认方法也不会要求实现接口的类去实现它
    2. 静态方法-静态方法不能是抽象方法
    3. Object 基类中的 public 方法,java 中所有的类都继承自基类

    这一节我们使用了 JDK 自带的 Comparator 接口来做示例,除了这个,还有 JDK 中的 Runnable 接口也被 JDK 标注上了函数式接口注解,这表示我们在使用 runnable 接口的时候,一样可以顺当地利用 lambda 表达式。

    除此之外,我们的自定义接口也可以使用了,在第一节使用到的 MathOperation 接口,虽然没有加上这个注解,但是其接口使用,完全也是符合 SAM 标准,即一个单一的抽象方法声明接口。我可以简单完善一下这个接口:

    @FunctionalInterface
    public interface MathOperation {
    
        int operation(int a, int b);
    }
    
    

    如果去掉函数式接口注解,并在接口里声明另一个方法,那么这个接口就无法顺利地使用 lambda 表达式。使用则会报错

    Multiple non-overriding abstract methods found in xxx

    三 lambda 表达式变量作用域

    1 lambda 表达式中不允许声明一个与局部变量同名的参数或者局部变量

            Student demo = new Student();
            MathOperation mathOperation = (demo, demo2) -> demo - demo2;
    

    在这段示例代码中,表达式里面的 demo 变量会报错,因为这个变量名在上一行代码中已经被其他的对象声明给占用了。

    2 lambda 表达式中可以直接访问外层的局部变量

            int outNumber = 12;
            Thread thread1 = new Thread(() -> System.out.println(outNumber));
            Thread thread2 = new Thread(() -> System.out.println(params));
    

    上面的示例代码,outNumber 是一个方法内的局部变量,params 则是一个类的静态成员。这些类型的参数均可以在 lambda 表达式中引用。

    3 lambda 表达式中被引用的变量值不可以被更改

    Thread thread3 = new Thread(() -> System.out.println(outNumber--));
    

    这样一行代码是无法通过编译检查的,在表达式中我尝试修改方法的局部变量 outNumber。然后 idea 就提示我: variable used in lambda expression should be final or effectively final。即 lambda 表达式中使用的变量只能是 final 类型或者隐式的 final 类型。

    最明显的好处:final类型的参数传入 lambda 表达式的使用,表示这个操作是 线程安全的,我们可以放心的时候而不必担心并发问题。需要注意的是,如果传入的不是基础数据类型,是一个对象引用,那么修改对象引用内的成员是对的。

    Thread thread4 = new Thread(() -> demo.setName("gaop"));
    

    这样的使用方法,需要注意产生并发修改的场景。

    相关文章

      网友评论

          本文标题:Lambda 表达式

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