一. Lambada 表达式

1. 简介

  Lambda 表达式是一种匿名函数(**对 Java 而言这并不完全正确,但为了便于理解,姑且这么认为**),简单地说,它是没有声明的方法,也即没有访问修饰符、返回值声明和名字。
  你可以将其想做一种速记,在你需要使用某个方法的地方写上它。当某个方法只使用一次,而且定义很简短,使用这种速记替代之尤其有效,这样,你就不必在类中费力写声明与方法了。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。

2. 为什么需要 Lambda 表达式?

本质上来说,编程关注两个维度:数据和数据上的操作。
  面向对象的编程泛型强调让操作围绕数据,对象至上,这样可以实现以类为单位的重用,当为类添加新的数据类型时,原有代码无需修改。
  函数式编程是一种不同的编程模型,它以操作(函数)为中心,强调变量不变性。函数式编程的准则是不依赖外部的数据,也不改变外部数据的值。这一特性满足了多核并行程序设计的需求,因此能简化并行程序开发。在函数式编程语言中,函数是一等公民,它们可以独立存在,你可以将其赋值给一个变量,或将他们当做参数传给其他函数。函数式编程提供了一种强大的功能——闭包,相比于传统的编程方法有很多优势,闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。
在 Java 中,函数无法单独存在,必须依托于对象。Lambda 表达式是Java8对函数式编程的支持,是可以传递执行的代码块,他们为java的函数式编程提供了有力支持,但是他们必须依附于一类特别的对象类型——函数式接口(functional interface)。

3 函数式接口

函数式接口是只包含一个抽象方法声明的接口。java.lang.Runnable 就是一种函数式接口,在 Runnable 接口中只声明了一个方法 void run(),我们使用匿名内部类来实例化函数式接口的对象,有了 Lambda 表达式,这一方式可以得到简化。
每个 Lambda 表达式都能隐式地赋值给函数式接口,例如,我们可以通过 Lambda 表达式创建 Runnable 接口的引用。

  1. Runnable r = () -> System.out.println("hello world");

编译器会把lambada表达式当做一个函数来编译,这样我们可以将它传入Thread的构造函数中,只需要1行代码

  1. new Thread(() -> System.out.println("hello world"));

而使用匿名内部类来创建Thread的话,需要6行代码

  1. new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. System.out.println("hello world")
  5. }
  6. });

@FunctionalInterface 注解

在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:

  1. @FunctionalInterface
  2. public interface Functional {
  3. void method();
  4. }

不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。

  1. @FunctionalInterface
  2. public interface FunctionalDefaultMethods {
  3. void method();
  4. default void defaultMethod() {
  5. }
  6. }

4. Lambda 表达式的结构

Java 中的 Lambda 表达式通常使用 (argument) -> (body) 语法书写,例如:

  1. (arg1, arg2...) -> { body }
  2. (Type arg1, Type arg2...) -> { body }

常见的写法如下

  1. // 1. 不需要参数,返回值为 5
  2. () -> 5
  3. // 2. 接收一个参数(数字类型),返回其2倍的值
  4. x -> 2 * x
  5. // 3. 接受2个参数(数字),并返回他们的差值
  6. (x, y) -> x y
  7. // 4. 接收2个int型整数,返回他们的和
  8. (int x, int y) -> x + y
  9. // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
  10. (String s) -> {System.out.print(s)}
  • 一个 Lambda 表达式可以有零个或多个参数
  • 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)与(a)效果相同
  • 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
  • 空圆括号代表参数集为空。例如:() -> 42
  • 当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a*a
  • Lambda 表达式的主体可包含零条或多条语句 例如:(a, b) ->{a=1;b=2;System.out.print(b-a);}
  • 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致
  • 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空

    5. 常见的函数式接口例子

    5.1 Runnable 任务(常用)

    ```java Runnable runnable = ()->{ System.out.println(Thread.currentThread().getName()); };

Thread thread = new Thread(runnable); thread.start();

  1. - Runnable只有一个run函数,且run函数没有参数
  2. <a name="pLpNG"></a>
  3. ### 5.2 Comparator 比较器(常用)
  4. lambada表达式实现Comparator

List list = new ArrayList<>(); list.add(1); list.add(3); list.add(2); list.sort((a,b) -> {return a>b?-1:1;});

//另一种方式,利用Comparator下的comparingInt方法,结合lambada表达式,更加优雅 list.sort(comparingInt(a -> a));

  1. <a name="D0llI"></a>
  2. ### 5.3 集合的操作(常用)
  3. List的遍历
  4. ```java
  5. List<Integer> list = new ArrayList<>();
  6. list.add(1);
  7. list.add(3);
  8. list.add(2);
  9. list.forEach((value)->System.out.println(value));

Map的遍历

  1. Map<String, Integer> items = new HashMap<>();
  2. items.put("A", 10);
  3. items.put("B", 20);
  4. items.put("C", 30);
  5. items.put("D", 40);
  6. items.put("E", 50);
  7. items.put("F", 60);
  8. items.forEach((k,v)->System.out.println("Item : " + k + " Count : " + v));

5.4 Consumer接口

Consumer接口接收一个参数,不返回参数

  1. public static void consumerFun(int value, Consumer<Integer> c) {
  2. c.accept(value);
  3. }
  4. //调用
  5. consumerFun(1,(value)->{
  6. System.out.println(value);
  7. });

5.5 BinConsumer接口

与Consumer接口一样,只不过接收两个参数,返回0个参数

  1. public static void binConsumerFun(String a, String b, BiConsumer<String, String> binc) {
  2. binc.accept(a, b);
  3. }
  4. //调用
  5. binConsumerFun("hello", "maskwang", (a,b)->{
  6. System.out.println(a+b)
  7. });

5.6 Predication

作用接收一个参数,返回一个boolean值

  1. public static boolean predicateFun(int value, Predicate<Integer> pre) {
  2. return pre.test(value);
  3. }
  4. //调用
  5. System.out.println(predicateFun(3, x->x==3));

5.7 Supplier

作用是接收0个参数,返回一个值

  1. public static int supplierFun(Supplier<Integer> supplier) {
  2. return supplier.get();
  3. }
  4. //调用
  5. System.out.println(supplierFun(()->1));

6. Lambda表达式与匿名内部类的联系和区别

联系:

  • 都可以直接访问 “effectively final” 的局部变量,以及外部类的成员变量(包括实例变量和类变量)。
  • 都可以直接调用从接口继承得到的默认方法。

    “effectively final”:局部内部类和匿名内部类访问的局部变量必须由final修饰,java8开始,可以不加final修饰符,由系统默认添加。java将这个功能称为:Effectively (有效) final

区别:

  • 所需类型不同
    • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
    • Lambda表达式:只能是接口
  • 使用限制不同
    • 如果接口中有且仅有一一个抽象方法,可以使用L ambda表达式,也可以使用匿名内部类
    • 如果接口中多 于一个抽象方法,只能使用匿名内部类,而不能使用L ambda表达式
  • 实现原理不同
    • 匿名内部类:编译之后,产生-一个单独的.class字节码文件
    • Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成

7. 引申:方法引用

方法引用也是JDK8的新特性,通常与Lambda表达式联合使用,可以直接引用已有Java类或对象的方法。一般有四种不同的方法引用:

  • 构造器引用。语法是Class::new,或者更一般的Class< T >::new,可无参,可有参数。方法签名保持一致;
  • 静态方法引用。语法是Class::static_method,要求方法签名保持一致;
  • 特定类的任意对象方法引用。它的语法是Class::method。要求方法签名保持一致;
  • 特定对象的方法引用,它的语法是instance::method。要求方法签名保持一致。与3不同的地方在于,3是在列表元素上分别调用方法,而4是在某个对象上调用方法,将列表元素作为参数传入;
    1. ArrayList::new () -> new ArrayList<>()
    2. String::valueOf x -> String.valueOf(x)
    3. Object::toString x -> x.toString()
    4. x::toString () -> x.toString()

8. 总结

Lambda 表达式赋予了 Java 相较于其他函数式编程语言缺失的特性,结合虚拟扩展方法之类的特性,Lambda 表达式能写出一些极好的代码。希望能在以后写代码的过程中,把这些用上去,使整个代码看起来很简洁.