网站公告

本次更新:没啥更新的

“钱塘江上潮信起,今日方知我是我”

Skip to content

Lambda表达式 ​

字数: 0 字 时长: 0 分钟

1. 概述 ​

Lambda 表达式是 JDK 8 引入的一种语法,可以简化函数式接口匿名内部类的写法。

它是函数式编程思想的重要体现,让我们不再过多关注“创建了什么对象”,而是更关注:

传入什么参数,以及对这些参数执行什么操作。

例如,原本需要通过匿名内部类实现接口:

java
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
}).start();

使用 Lambda 表达式后:

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

不是所有匿名内部类都可以使用 Lambda,只有函数式接口才能使用 Lambda 表达式。

函数式接口指只有一个抽象方法的接口。


2. 核心原则 ​

Lambda 表达式最重要的原则:

可推导,可省略。

如果某些信息可以通过上下文推导出来,就可以省略不写。

例如:

  • 参数类型可以推导,可以省略
  • 方法名可以通过函数式接口的抽象方法推导,因此不需要写
  • 只有一个参数时,小括号可以省略
  • 方法体只有一条语句时,大括号可以省略
  • 方法体只有一条 return 语句时,return 和分号可以一起省略

Lambda 最终主要关注两部分:

text
参数
  ↓
执行逻辑

3. 基本格式 ​

Lambda 表达式的基本格式:

java
(参数列表) -> {
    代码
}

其中:

text
(参数列表)  ->  {方法体}
    ↑               ↑
   参数            执行逻辑

3.1 练习一:Runnable ​

原来的匿名内部类写法:

java
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("我是老写法");
    }
}).start();

Runnable 是一个函数式接口,其中只有一个抽象方法:

java
void run();

因此真正需要关注的只有方法的参数和方法体。

run() 没有参数,因此可以理解为:

java
() -> {
    System.out.println("我是新写法");
}

最终简化为:

java
new Thread(() -> {
    System.out.println("我是新写法");
}).start();

如果方法体只有一句代码,还可以继续简化:

java
new Thread(() -> System.out.println("我是新写法")).start();

3.2 练习二:IntBinaryOperator ​

现有方法如下,其中 IntBinaryOperator 是一个函数式接口:

java
public static int calculateNum(IntBinaryOperator operator) {
    int a = 10;
    int b = 20;
    return operator.applyAsInt(a, b);
}

使用匿名内部类:

java
public static void main(String[] args) {
    calculateNum(new IntBinaryOperator() {
        @Override
        public int applyAsInt(int left, int right) {
            return left + right;
        }
    });
}

对于 Lambda 来说,我们主要关注:

java
(int left, int right) -> {
    return left + right;
}

因此可以改造成:

java
calculateNum((int left, int right) -> {
    return left + right;
});

继续按照省略规则,可以写成:

java
calculateNum((left, right) -> left + right);

3.3 练习三:IntPredicate ​

现有方法如下,其中 IntPredicate 是一个函数式接口:

java
public static void printNum(IntPredicate predicate) {
    int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    for (int i : arr) {
        if (predicate.test(i)) {
            System.out.println(i);
        }
    }
}

使用匿名内部类:

java
public static void main(String[] args) {
    printNum(new IntPredicate() {
        @Override
        public boolean test(int value) {
            return value % 2 == 0;
        }
    });
}

改造成 Lambda:

java
printNum((int value) -> {
    return value % 2 == 0;
});

继续简化:

java
printNum(value -> value % 2 == 0);

3.4 练习四:Function ​

定义一个类型转换方法:

java
public static <R> R typeConver(Function<String, R> function) {
    String str = "1235";
    return function.apply(str);
}

使用匿名内部类,将 String 转换为 Integer:

java
public static void main(String[] args) {
    Integer integer = typeConver(new Function<String, Integer>() {
        @Override
        public Integer apply(String s) {
            return Integer.valueOf(s);
        }
    });

    System.out.println(integer);
}

使用 Lambda:

java
Integer integer = typeConver((String s) -> {
    return Integer.valueOf(s);
});

继续简化:

java
Integer integer = typeConver(s -> Integer.valueOf(s));

也可以将字符串转换成另一个字符串。

匿名内部类:

java
String str = typeConver(new Function<String, String>() {
    @Override
    public String apply(String s) {
        return s + "哈哈";
    }
});

System.out.println(str);

Lambda:

java
String str = typeConver((String s) -> {
    return s + "哈哈";
});

继续简化:

java
String str = typeConver(s -> s + "哈哈");

3.5 练习五:IntConsumer ​

定义数组遍历方法,其中 IntConsumer 是一个函数式接口:

java
public static void foreachArr(IntConsumer consumer) {
    int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    for (int i : arr) {
        consumer.accept(i);
    }
}

使用匿名内部类:

java
public static void main(String[] args) {
    foreachArr(new IntConsumer() {
        @Override
        public void accept(int value) {
            System.out.println(value);
        }
    });
}

改造成 Lambda:

java
foreachArr((int value) -> {
    System.out.println(value);
});

继续简化:

java
foreachArr(value -> System.out.println(value));

4. 省略规则 ​

4.1 参数类型可以省略 ​

原写法:

java
foreachArr((int value) -> {
    System.out.println(value);
});

参数类型可以根据 IntConsumer 推导,因此可以省略:

java
foreachArr((value) -> {
    System.out.println(value);
});

4.2 单个参数的小括号可以省略 ​

当 Lambda 只有一个参数时:

java
foreachArr((value) -> {
    System.out.println(value);
});

可以省略小括号:

java
foreachArr(value -> {
    System.out.println(value);
});

没有参数或者有多个参数时,小括号不能省略。


4.3 方法体只有一句代码时可以省略大括号 ​

原写法:

java
foreachArr(value -> {
    System.out.println(value);
});

可以简化为:

java
foreachArr(value -> System.out.println(value));

4.4 单条 return 语句可以继续省略 ​

例如:

java
calculateNum((left, right) -> {
    return left + right;
});

如果方法体只有一条 return 语句,可以同时省略大括号、return 和分号:

java
calculateNum((left, right) -> left + right);

注意:

java
(left, right) -> {
    return left + right;
}

不能只删除 return:

java
// 错误
(left, right) -> {
    left + right;
}

而是需要一起简化:

java
(left, right) -> left + right

5. 小结 ​

Lambda 表达式的核心并不复杂:

text
匿名内部类
    ↓
找到唯一的抽象方法
    ↓
保留参数 + 方法体
    ↓
使用 -> 连接
    ↓
根据“可推导可省略”继续简化

最核心的一句话:

Lambda 主要关注参数和方法体,可推导出来的内容就可以省略。

Noctiflorous Garden · MIT License