美文网首页
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