Lambda 表达式

简介: 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)

本次分享到此结束啦~~

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

相关文章
|
11天前
|
C#
C#一分钟浅谈:Lambda 表达式和匿名方法
本文详细介绍了C#编程中的Lambda表达式与匿名方法,两者均可用于定义无名函数,使代码更简洁易维护。文章通过基础概念讲解和示例对比,展示了各自语法特点,如Lambda表达式的`(parameters) =&gt; expression`形式及匿名方法的`delegate(parameters)`结构。并通过实例演示了两者的应用差异,强调了在使用Lambda时应注意闭包问题及其解决策略,推荐优先使用Lambda表达式以增强代码可读性。
19 8
|
3月前
|
算法 编译器
lambda 表达式
lambda 表达式
|
4月前
|
存储 JavaScript 前端开发
c++lambda函数与表达式
c++lambda函数与表达式
25 1
|
安全 架构师 Java
必须掌握的 Lambda 表达式
必须掌握的 Lambda 表达式
5889 1
必须掌握的 Lambda 表达式
|
12月前
|
算法 编译器
【lambda函数】lambda()函数
【lambda函数】lambda()函数
|
存储 算法 编译器
C++ lambda 表达式
# C++ `lamdba` 表达式 > Lambda表达式是C++11引入的一个新特性,它允许我们在需要函数对象的地方,使用一种更加简洁的方式定义匿名函数。Lambda表达式通常用于`STL`中的算法、回调函数、事件处理程序等场合。 Lambda表达式的基本语法为: ```c++ Copy Code[capture list](parameter list) mutable exception -> return type { function body } ``` ## `lamdba`表达式在什么场合下使用 C++11 lambda表达式可以用来创建匿名函数,也就是没有函数名的
82 0
|
Java
Lambda 表达式Demoo
Lambda 表达式Demoo
109 0
Lambda 表达式Demoo
|
存储 算法 编译器
【C++】lambda 表达式 | 包装器
【C++】lambda 表达式 | 包装器
【C++】lambda 表达式 | 包装器
|
并行计算 Java 编译器
Lambda 表达式
Lambda 表达式
161 0
Lambda 表达式
|
Java 编译器
深入理解 Lambda 表达式
Java 8 的 Lambda 表达式已经不再是“新特性”。 现在很多人工作中会使用 Lambda 表达式。 但是,你是否真正理解 Lambda 表达式的底层原理?
394 0
深入理解 Lambda 表达式