# Lambda 表达式语法

## 基本语法

`Lambda` 表达式的组成：

1. 参数列表：即方法的参数列表，对应于 `Runnable` 接口的 `run` 方法，该方法没有参数，所以这里为空。
2. 箭头符号：箭头符号将参数列表与 `Lambda` 表达式的主体分隔开。
3. `Lambda` 表达式的主体：即方法体，对应于 `Runnable` 接口的 `run` 方法的方法体。

```java
new Thread(() -> System.out.println("Hello World")).start();
```

`Lambda` 表达式可以分为以下几种形式：

```java
// 01、无参数，无返回值
() -> System.out.println("Hello World")

// 02、有一个参数，无返回值
(String name) -> System.out.println("Hello " + name)

// 03、有一个参数，无返回值，参数类型可以省略
name -> System.out.println("Hello " + name)

// 04、有多个参数，无返回值
(String name, int age) -> System.out.println("Hello " + name + ", age is " + age)

// 05、有多个参数，无返回值，参数类型可以省略
(name, age) -> System.out.println("Hello " + name + ", age is " + age)

// 06、有返回值
() -> {
    System.out.println("Hello World");
    return "Hello World";
}

// 07、有返回值，单条语句，return 与大括号可以省略
() -> "Hello World"

// 08、有返回值，单条语句，return 与大括号可以省略，参数类型可以省略
name -> "Hello " + name

// 09、有返回值，多条语句，return 与大括号不能省略
() -> {
    System.out.println("Hello World");
    return "Hello World";
}
```

## 变量的作用域

`Lambda` 表达式中可以访问外部的变量，但是有以下限制：

1. `Lambda` 表达式中访问的外部变量必须是 `final` 或者 `effectively final` 的。
2. `Lambda` 表达式中访问的外部变量不能被修改，即不能对外部变量进行赋值操作。

```java
class Sample {
    public static void main(String[] args) {
        String value = "World";
        Person person = (message) -> {
            System.out.println(message + " " + value);
            // ❌ value = "Java";
        };
        
        // 重新赋值，输出那里编译器会报错
        value = "Java";
        person.say("Hello");
    }
}
```

## 方法重载

使用 `Lambda` 表达式时，调用一个类中的重载方法，且方法中的参数都为函数式接口时，编译器会报错。因为编译器无法推断 `Lambda` 表达式的类型。

```java
@FunctionalInterface
interface MyInterfaceA {
    void myMethod(String name);
}
@FunctionalInterface
interface MyInterfaceB {
    void myMethod(String name);
}

class Sample {
    public static void method(MyInterfaceA myInterfaceA) {
        myInterfaceA.myMethod("Hello");
    }

    public static void method(MyInterfaceB myInterfaceb) {
        myInterfaceb.myMethod("Hello");
    }

    public static void main(String[] args) {
        // ⭕️ ❌编译报错，无法推断 Lambda 表达式的类型
        method(str -> System.out.println(str + " World!"));
        
        // ✅ 解决方法是显式指定 Lambda 表达式的类型。
        method((MyInterfaceA) str -> System.out.println(str + " World!"));
        method((MyInterfaceB) str -> System.out.println(str + " World!"));
    }
}
```

## 方法引用

Lambda 表达式的方法引用是一种更简洁的 `Lambda` 表达式的写法，可以直接引用已有方法或者构造方法。

方法引用的语法是 `类名::方法名`，其中 `::` 是方法引用运算符，左边是类名或者对象名，右边是方法名。

方法引用的类型：

1. 静态方法引用：`类名::静态方法`
2. 实例方法引用：`对象名::实例方法`
3. 构造方法引用：`类名::new`

```java
public class LambdaMethodReference {
    public static void main(String[] args) {
        // 01、静态方法引用
        Consumer<String> consumer1 = LambdaMethodReference::print;
        consumer1.accept("Hello World");

        // 02、实例方法引用
        LambdaMethodReference lambdaMethodReference = new LambdaMethodReference();
        Consumer<String> consumer2 = lambdaMethodReference::println;
        consumer2.accept("Hello World");

        // 03、构造方法引用
        Supplier<LambdaMethodReference> supplier = LambdaMethodReference::new;
        LambdaMethodReference lambdaMethodReference1 = supplier.get();
    }
}
```

```java
// 定义一个接受三个参数的函数式接口 TriFunction
@FunctionalInterface
interface TriFunction<T, U, V, R> {
    R apply(T t, U u, V v);
}

class Sample {
    public static void main(String[] args) {
        List<Student> students = List.of(
                new Student("张三", 10, "男"),
                new Student("李四", 20, "男"),
                new Student("王五", 30, "女")
        );

        // 静态方法引用
        List<Student> filterPersons = Sample.filter(students, Sample::adult);

        // 实例方法引用
        List<Student> filter = Sample.filter(students, new Sample()::isAdult);

        // 使用带参构造方法引用创建 Student 对象
        TriFunction<String, Integer, String, Student> studentFactory = Student::new;

        List<String> names = List.of("张三", "李四", "王五");
        List<Integer> ages = List.of(20, 21, 22);
        List<String> genders = List.of("男", "男", "女");
        List<Student> studs = names.stream()
                .map(name -> {
                    int index = names.indexOf(name);
                    return studentFactory.apply(name, ages.get(index), genders.get(index));
                })
                .toList();

    }

    static List<Student> filter(List<Student> students, Filter filter) {
        List<Student> result = new ArrayList<>();
        for (Student student : students) {
            if (filter.call(student)) {
                result.add(student);
            }
        }
        return result;
    }

    static Boolean adult(Student student) {
        return student.getAge() >= 18;
    }

    static void print(Student student) {
        System.out.println(student);
    }

    public Boolean isAdult(Student student) {
        return student.getAge() >= 18;
    }
}
```

如果函数的参数超过2个，就需要自己创建一个函数式接口。

## 类型检查

`Lambda` 表达式的类型检查是通过上下文推断来实现的，`Lambda` 表达式的类型是通过上下文推断出来的，而不是通过 `Lambda` 表达式的参数类型来推断的。

```java
// 01、通过上下文推断出 Lambda 表达式的类型是 Runnable
Runnable runnable = () -> System.out.println("Hello World");

// 02、通过上下文推断出 Lambda 表达式的类型是 Callable
Callable<String> callable = () -> "Hello World";
```

## 类型转换

`Lambda` 表达式的类型转换是通过强制类型转换来实现的，`Lambda` 表达式的类型是通过上下文推断出来的，而不是通过 `Lambda` 表达式的参数类型来推断的。

```java
// 01、通过上下文推断出 Lambda 表达式的类型是 Runnable
Runnable runnable = () -> System.out.println("Hello World");

// 02、通过强制类型转换将 Lambda 表达式转换为 Callable 类型
Callable<String> callable = (Callable<String>) () -> "Hello World";
```

## 错误处理

`Lambda` 表达式中的代码可能会抛出异常，但是 `Lambda` 表达式中不能使用 `throws` 关键字来声明异常，因为函数式接口中的抽象方法没有声明异常。

```java
public class LambdaException {
    public static void main(String[] args) {
        // Lambda 表达式中不能使用 throws 关键字
        // Callable<String> callable = () -> { throw new Exception(); };
    }
}
```

`Lambda` 表达式中的异常处理有以下几种方式：

1. 使用 `try-catch` 语句捕获异常。
2. 使用 `Callable` 函数式接口，将 `Lambda` 表达式的返回值改为 `Callable` 类型，然后在 `call` 方法中捕获异常。
3. 使用自定义函数式接口，将 Lambda 表达式的抽象方法声明异常。

```java
public class LambdaException {
    public static void main(String[] args) {
        // 01、使用 try-catch 语句捕获异常
        Callable<String> callable1 = () -> {
            try {
                throw new Exception();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return "Hello World";
        };

        // 02、使用 Callable 函数式接口捕获异常
        Callable<String> callable2 = new Callable<String>() {
            @Override
            public String call() {
                try {
                    throw new Exception();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return "Hello World";
            }
        };

        // 03、使用自定义函数式接口捕获异常
        MyCallable myCallable = () -> {
            throw new Exception();
        };
    }
}
```
