Lambda 表达式

Posted 陈皮的JavaLib

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lambda 表达式相关的知识,希望对你有一定的参考价值。

我是陈皮,一个在互联网 Coding 的 ITer,个人微信公众号「陈皮的JavaLib」关注第一时间阅读最新文章。

文章目录

Lambda 简介

Lambda 表达式是 Java 8 引入的一个重要新语法,是一种紧凑的传递代码的方式,即允许把代码作为一个方法的实参。从而可以写出更简洁,更灵活的代码,语言表达能力得到了提升。

案例分析

Java 编程中推荐面向接口编程,例如 test 方法需要一个 Human 接口对象。正常编程是编写一个 Human 接口的实现类,实例化一个实现类对象传递到方法中。

package com.chenpi;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/18
 */
public interface Human 
  void speak();

package com.chenpi;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/18
 */
public class Man implements Human 

  @Override
  public void speak() 
    System.out.println("I am man!");
  

package com.chenpi;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/18
 */
public class ChenPi 

  public static void main(String[] args) 
    test(new Man());
  

  public static void test(Human human) 
    human.speak();
  

如果我们不想编写一个 Human 的实现类,那么可以使用匿名内部类的形式,如下所示。

package com.chenpi;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/18
 */
public class ChenPi 

  public static void main(String[] args) 
    test(new Human() 
      @Override
      public void speak() 
        System.out.println("I am man!");
      
    );
  

  public static void test(Human human) 
    human.speak();
  

其实传递给 test 方法中的代码中,有用的代码就只有输出语句那一行,所以借助 Lambda 表达式,我们可以只传递有用的代码,不需要匿名内部类和实现类。

package com.chenpi;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/18
 */
public class ChenPi 

  public static void main(String[] args) 
    test(() -> System.out.println("I am man!"));
  

  public static void test(Human human) 
    human.speak();
  

Lambda 语法

语法:() -> 
() :Lambda的形参列表,即接口中抽象方法的形参列表
-> :Lambda的操作符,参数列表和Lambda体的分隔符
 :Lambda体,即实现了接口中的抽象方法的方法体
    
注:
1:参数列表的参数类型可以省略,Java可以根据上下文推断出来
2:如果参数列表只有一个,()可以省略
3:如果Lambda体只有一行语句,可以省略,并且语句末尾不能加;分号
4:如果Lambda体有返回值,而且只有一行语句,return关键字都可以省略,并且语句末尾不能加;分号

Lambda 表达式其实是对某些接口的简单实现。但不是所有接口都可以使用 Lambda 表达式来实现,接口只能有一个抽象方法。但不要求接口只能有一个方法,因为 Java 8 中支持接口中可以有 default 关键字修饰的有默认实现的默认方法,这个默认的方法是可以不需要子类实现的,可使用@FunctionalInterface注解来强制接口只能有一个抽象方法,只有一个抽象方法的接口称为函数式接口。

package com.chenpi;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/18
 */
public class ChenPi 

  public static void main(String[] args) 
    test((String name, int age) -> 
      System.out.println("大家好我是" + name + ",今年" + age + "岁!");
    );
  

  public static void test(Human human) 
    String name = "陈皮";
    int age = 18;
    human.speak(name, age);
  


@FunctionalInterface
interface Human 

  void speak(String name, int age);

可以省略参数列表的参数类型,如下所示。

package com.chenpi;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/18
 */
public class ChenPi 

  public static void main(String[] args) 
    test((name, age) -> 
      System.out.println("大家好我是" + name + ",今年" + age + "岁!");
    );
  

  public static void test(Human human) 
    String name = "陈皮";
    int age = 18;
    human.speak(name, age);
  


@FunctionalInterface
interface Human 

  void speak(String name, int age);

Lambda 体只有一行语句,可以省略大括号 ,并且末尾不能加 ; 分号,如下所示。

package com.chenpi;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/18
 */
public class ChenPi 

  public static void main(String[] args) 
    test((name, age) -> System.out.println("大家好我是" + name + ",今年" + age + "岁!"));
  

  public static void test(Human human) 
    String name = "陈皮";
    int age = 18;
    human.speak(name, age);
  


@FunctionalInterface
interface Human 

  void speak(String name, int age);

如果参数列表只有一个参数,可以省略 () 括号,如下所示。

package com.chenpi;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/18
 */
public class ChenPi 

  public static void main(String[] args) 
    test(name -> System.out.println("大家好我是" + name));
  

  public static void test(Human human) 
    String name = "陈皮";
    human.speak(name);
  


@FunctionalInterface
interface Human 

  void speak(String name);

如果 Lambda 体有返回值,而且只有一行语句, 和 return 关键字都可以省略,并且语句末尾不能加 ; 分号,如下所示。

package com.chenpi;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/18
 */
public class ChenPi 

  public static void main(String[] args) 
    test(age -> age + 10);
  

  public static void test(Human human) 
    int age = 18;
    int speakAge = human.speak(age);
    System.out.println(speakAge);
  


@FunctionalInterface
interface Human 

  int speak(int age);

与匿名内部类一样,Lambda 表达式也可以访问定义在 Lambda 体外面的变量。但对于局部变量,它也只能访问final类型的变量,但与匿名内部类不同的是,它不要求变量声明为 final,只要不被重新赋值即可。

package com.chenpi;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/18
 */
public class ChenPi 

  private static int AGE = 20;

  public static void main(String[] args) throws InterruptedException 

    int i = 10;

    // 不能再重新赋值,不然会导致下面对i的使用报错
    // i = 12;

    // 不是局部变量可以修改
    AGE = 21;

    test((name, age) -> 
      // 访问外部的变量
      System.out.println(AGE);
      // 访问局部变量
      System.out.println(i);
      System.out.println("大家好我是" + name + ",今年" + age + "岁!");
      return age + i;
    );

  

  public static void test(Human human) 
    int age = 18;
    String name = "陈皮";
    int speakAge = human.speak(name, age);
    System.out.println(speakAge);
  


@FunctionalInterface
interface Human 

  int speak(String name, int age);


// 输出结果如下
21
10
大家好我是陈皮,今年18!
28

原理和匿名内部类一样,外部局部变量的值作为参数传递给 Lambda 表达式,为 Lambda 表达式建立一个副本,Lambda 访问的是副本,而不是外部局部变量。如果外部变量允许修改,程序员可能会误以为 Lambda 表达式读到修改后的值,引起混淆。

为什么不直接访问外部的局部变量呢?因为外部局部变量定义在栈中,当 Lambda 表达式被执行的时候,外部局部变量可能早已被释放了。如果希望能够修改值,可以将变量定义为实例变量,或者将变量定义为数组。

package com.chenpi;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/18
 */
public class ChenPi 

  public static void main(String[] args) throws InterruptedException 

    int[] arr = 1, 2;
    // 数组可以修改
    arr[0] = 5;

    test(() -> 
      // 访问局部变量
      return arr[0];
    );

  

  public static void test(Human human) 
    int speakAge = human.speak();
    System.out.println(speakAge);
  


@FunctionalInterface
interface Human 

  int speak();


// 输出结果如下
5

Lambda 表达式和匿名内部类很相似,那它是不是语法糖,内部实现其实就是内部类呢?答案是否定的,Java 会为每个匿名内部类生成一个类,但 Lambda 表达式不会。Lambda 表达式通常比较短,如果为每个表达式生成一个类会生成大量的类,性能会受到影响。

预定义函数式接口

Lambda 表达式是对函数式接口的简单实现。函数式接口只能有一个抽象方法。但还可以有 default 关键字修饰的有默认实现的默认方法。默认方法是可以不需要子类实现的,@FunctionalInterface注解可以强制接口只能有一个抽象方法。

Java 8 定义了大量的预定义函数式接口,用于常见类型的代码传递,这些函数定义在包java.util.function下。以下简单列举几个。

// 判断输入是否满足条件
Predicate<T> # boolean test(T t)
    
// 转换,输入类型T,输出类型R
Function<T, R> # R apply(T t)

// 工厂方法
Supplier<T> # T get()
    
// 消费者
Consumer<T> # void accept(T t)
    
// 转换,输入类型T和U,输出类型R
BiFunction<T, U, R> # R apply(T t, U u)
    
// BiFunction特例,输入输出类型都一样
BinaryOperator<T> # R apply(T t, U u)

本次分享到此结束啦~~

如果觉得文章对你有帮助,点赞、收藏、关注、评论,您的支持就是我创作最大的动力!

以上是关于Lambda 表达式的主要内容,如果未能解决你的问题,请参考以下文章

关于Java 中的 Lambda 表达式你了解多少?

Lambda表达式中的表达式lambda和语句lambda区别

lambda表达式来自哪里?

表达式 lambda 和语句 lambda 的区别

Lambda简介

Lambda 表达式的演示样例-来源(MSDN)