Java基础快速入门: 面向对象高级之Lambda表达式深度解析

本文纲要

  1. Lambda表达式初体验与函数式编程思想
  2. Lambda表达式的标准格式和使用前提
  3. Lambda表达式练习:带参数无返回值
  4. Lambda表达式练习:无参数有返回值
  5. Lambda表达式练习:带参数带返回值
  6. Lambda表达式的省略模式
  7. 匿名内部类与Lambda表达式的区别

项目代码结构

text 复制代码
src/
└── com/
    └── wb/
        ├── test1/
        │   └── TestSwimming.java 
        ├── test2/
        │   └── TestLambda.java
        ├── test3/
        │   └── StringHandlerDemo.java
        ├── test4/
        │   └── RandomNumHandlerDemo.java 
        ├── test5/
        │   └── CalculatorDemo.java
        ├── test6/
        │   └── Test6.java
        └── test7/
            └── Test.java

Lambda表达式初体验与函数式编程思想

1 ) 从匿名内部类到Lambda

以前我们调用一个接口方法时,通常需要传入一个匿名内部类对象。例如,有一个游泳接口 Swimming,里面有一个 swim() 方法,我们这样调用:

java 复制代码
goSwimming(new Swimming() {
    @Override 
    public void swim() {
        System.out.println("铁汁, 我们去游泳吧");
    }
});

采用 Lambda表达式 后,可以简化为:

java 复制代码
goSwimming(() -> System.out.println("铁汁, 我们去游泳吧"));

运行结果完全一致。这说明 Lambda表达式可以理解为对匿名内部类的简化(本质上两者有区别,后文会说明)。

2 ) 函数式编程思想

通过对比可以发现,匿名内部类的写法里,我们不仅要关注"做什么"(打印一句话),还要关注"怎么做"(创建匿名内部类对象、重写方法)。而Lambda表达式让我们把注意力完全集中在要做什么上面,至于怎么创建对象、怎么重写方法这些语法细节,都由编译器在背后处理了。

这种更关注做什么,而不是怎么做的编程思想,就是函数式编程思想(Functional Programming)。Lambda表达式正是这种思想在Java中的体现。

Lambda表达式的标准格式和使用前提

1 ) 标准格式

Lambda表达式由三部分组成:(形式参数) -> { 代码块 }

  • 形式参数:如果有多个参数,用逗号分隔;如果没有参数,留空即可
  • 箭头 ->:由英文横线和大于号组成,表示将参数传递给后面的代码块
  • 代码块:具体要执行的内容,相当于方法体

2 ) 使用前提

Lambda表达式并不是任何地方都能用,必须满足两个条件:

  1. 操作的必须是一个接口。
  2. 接口中有且仅有一个抽象方法。

这种接口也被称为函数式接口。

3 ) 示例:无参数无返回值

接口定义(ShowHandler):

java 复制代码
interface ShowHandler {
    void show();
}

测试类:

java 复制代码
public class TestLambda {
    public static void main(String[] args) {
        // 匿名内部类方式
        useShowHandler(new ShowHandler() {
            @Override
            public void show() {
                System.out.println("我是匿名内部类中的show方法");
            }
        });
 
        // Lambda方式 
        useShowHandler(() -> System.out.println("我是Lambda中的show方法"));
    }
 
    public static void useShowHandler(ShowHandler showHandler) {
        showHandler.show();
    }
}

分析:接口中只有一个 show() 方法,无参数,无返回值。

Lambda表达式 () -> System.out.println(...) 中,括号内无参数,箭头后直接写要执行的事情。

Lambda表达式练习:带参数无返回值

当接口方法带有参数时,Lambda表达式也需要在括号内声明参数。

接口定义(StringHandler):

java 复制代码
interface StringHandler {
    void printMessage(String msg);
}

使用示例:

java 复制代码
public class StringHandlerDemo {
    public static void main(String[] args) {
        // 匿名内部类
        useStringHandler(new StringHandler() {
            @Override 
            public void printMessage(String msg) {
                System.out.println("我是匿名内部类" + msg);
            }
        });
 
        // Lambda
        useStringHandler(msg -> System.out.println("我是Lambda表达式" + msg));
    }
 
    public static void useStringHandler(StringHandler stringHandler) {
        stringHandler.printMessage("itheima");
    }
}

要点:方法 printMessage 有一个参数,所以Lambda的括号内需要写上参数名(参数类型可以省略,后面会讲)。如果只有一个参数,括号也可以省略,如 msg -> ...

Lambda表达式练习:无参数有返回值

如果接口方法有返回值,Lambda表达式必须使用 return 语句返回结果。

接口定义(RandomNumHandler):

java 复制代码
interface RandomNumHandler {
    int getNumber();
}

使用示例:

java 复制代码
import java.util.Random;
 
public class RandomNumHandlerDemo {
    public static void main(String[] args) {
        // 匿名内部类 
        useRandomNumHandler(new RandomNumHandler() {
            @Override
            public int getNumber() {
                Random r = new Random();
                int num = r.nextInt(10) + 1;
                return num;
            }
        });
 
        // Lambda
        useRandomNumHandler(() -> {
            Random r = new Random();
            int num = r.nextInt(10) + 1;
            return num;
            // 注意: 如果lambda所操作的接口中的方法有返回值, 一定要通过return语句将结果返回
            // 否则会出现编译错误
        });
    }
 
    public static void useRandomNumHandler(RandomNumHandler randomNumHandler) {
        int result = randomNumHandler.getNumber();
        System.out.println(result);
    }
}

注意:无参数但需要返回值时,括号不能省略;方法体中的代码块如果有多条语句,大括号也不能省略。

Lambda表达式练习:带参数带返回值

这是最完整的形态:既有参数,又有返回值。

接口定义(Calculator):

java 复制代码
interface Calculator {
    int calc(int a, int b);
}

使用示例:

java 复制代码
public class CalculatorDemo {
    public static void main(String[] args) {
        // 匿名内部类
        useCalculator(new Calculator() {
            @Override 
            public int calc(int a, int b) {
                return a + b;
            }
        });
 
        // Lambda
        useCalculator((a, b) -> a + b);
    }
 
    public static void useCalculator(Calculator calculator) {
        int result = calculator.calc(10, 20);
        System.out.println(result);
    }
}

说明:参数 a 和 b 的类型可以省略,方法体只有一条返回语句时,可以省略大括号和 return,直接写表达式。

Lambda表达式的省略模式

Lambda表达式提供了一些简化写法,让代码更简洁。

1 ) 省略规则

  1. 参数类型可以省略,但如果有多个参数,不能只省略一个(要么都省略,要么都保留)。
  2. 如果参数只有一个,小括号可以省略。
  3. 如果代码块只有一条语句,可以省略大括号、分号和 return(这三个必须一起省略)。

2 ) 示例:针对省略规则的综合练习

原始接口:

java 复制代码
interface Inter {
    double method(double a, double b);
}

完整Lambda:

java 复制代码
useInter((double a, double b) -> {
    return a + b;
});

应用省略规则后:

java 复制代码
useInter((a, b) -> a + b);

完整调用代码:

java 复制代码
public class Test6 {
    public static void main(String[] args) {
        useInter((a, b) -> a + b);
    }
 
    public static void useInter(Inter i) {
        double result = i.method(12.3, 22.3);
        System.out.println(result);
    }
}

3 ) 对其他练习的省略优化

test2(无参无返回值):

java 复制代码
  useShowHandler(() -> System.out.println("我是Lambda中的show方法"));

因为无参,括号不能省;只有一条语句,可以省略大括号和分号。

test3(一个参数无返回值):

java 复制代码
useStringHandler(msg -> System.out.println("我是Lambda表达式" + msg));

参数类型省略,且只有一个参数,小括号省略;一条语句,省略大括号和分号。

test4(对应上述无参有返回值,方法体多条语句):

无法省略大括号,因为方法体有两条语句,必须保留 {} 和 return。

test5(有参有返回值,方法体一条语句):

java 复制代码
useCalculator((a, b) -> a + b);

省略了参数类型、大括号和 return。

匿名内部类与Lambda表达式的区别

区别点 匿名内部类 Lambda表达式
所属类型 可以是接口、抽象类,甚至具体类 只能操作接口
使用限制 接口中抽象方法数量不限 接口中必须有且仅有一个抽象方法
实现原理 编译后生成独立的 .class 文件(如 Test$1.class) 编译时不生成单独的 .class 文件,运行时动态生成字节码

1 ) 验证编译结果

接口:

java 复制代码
interface Inter {
    void show();
}

测试类:

java 复制代码
public class Test {
    public static void main(String[] args) {
        // 匿名内部类 
        useInter(new Inter() {
            @Override 
            public void show() {
                System.out.println("匿名内部类的show方法");
            }
        });
    }
 
    public static void useInter(Inter i) {
        i.show();
    }
}

编译后,在 out 目录下可以看到:

  • Inter.class (接口字节码)
  • Test.class (测试类字节码)
  • Test$1.class (匿名内部类生成的字节码)

如果使用Lambda表达式,编译后不会生成 $1.class,对应的字节码会在运行时动态生成,不保留在硬盘上。

结语

Lambda表达式是 Java 8 引入的重要特性,它让代码更简洁,同时将编程思维从"怎么做"引导到"做什么"。

掌握Lambda的格式、使用前提、省略规则以及与匿名内部类的区别,是Java进阶的必经之路。希望本文能帮助你快速入门Lambda表达式。