首页 > 编程笔记 > Java笔记 阅读:9

Java lambda表达式详解(附带实例)

lambda 就是数学中的“λ”的读音,lambda 表达式是基于 λ 演算而得名的,因为 lambda 抽象(lambda abstraction)表示一个匿名的函数,于是开发语言也将 lambda 表达式用来表示匿名函数,也就是没有函数名字的函数。

C#Python,甚至是 C++ 都有 lambda 表达式语法。为了提高开发者的开发效率,并照顾“跨语言”开发者的开发习惯,Java 语言也加入了 lambda 表达式。

lambda 表达式可以用非常少的代码实现抽象方法。lambda 表达式不能被独立执行,因此必须实现函数式接口,并且会返回一个函数式接口的对象。

lambda 表达式的语法非常特殊,语法格式如下:
() -> 结果表达式
参数 -> 结果表达式
(参数1, 参数2, … ,参数n) -> 结果表达式  

lambda 表达式也可以实现复杂方法,将操作符右侧的结果表达式换成代码块即可,语法格式如下:
() -> { 代码块 }
参数 -> { 代码块 }
(参数1, 参数2, … ,参数n) -> { 代码块 }

lambda 表达式的语法非常抽象,并且有着非常强大的自动化功能,如自动识别泛型、自动数据类型转换等,这会让初学者很难掌握。如果将 lambda 别泛型、自动数据类型转换等,这会让初学者很难掌握。

如果将 lambda 表达式的功能归纳总结,则可以将 lambda 表达式语法用如下方式理解:
()        ->     { 代码块 }
这个方法  按照  这样的代码来实现
简单总结:操作符左侧的是方法参数,操作符右侧的是方法体。

注意,“->”符号是由英文状态下的“-”和“>”组成的,符号之间没有空格。

Java lambda表达式实现函数式接口

1) 函数式接口

函数式接口指的是仅包含一个抽象方法的接口,接口中的方法简单明了地说明了接口的用途,如线程接口 Runnable、动作事件监听接口 ActionListener 等。

开发者可以创建自定义的函数式接口,例如:
interface MyInterface{
     void method();
}
如果接口中包含一个以上的抽象方法,则不符合函数式接口的规范,这样的接口不能用 lambda 表达式创建匿名对象。

2) lambda表达式实现无参抽象方法

很多函数式接口的抽放方法是无参数的,如线程接口 Runnable 只有一个 run() 方法,这样的无参抽象方法在 lambda 表达式中使用“( )”表示。

【实例】使用 lambda 表达式实现打招呼接口。创建函数式接口和测试类,接口抽象方法为无参方法并返回一个字符串。使用 lambda 表达式实现接口,让方法可以输出当前日期。
interface SayHiInterface { //打招呼接口
    String say(); //打招呼的方法
}

public class NoParameterDemo { //测试类
    public static void main(String[] args) {
        //lambda 表达式实现打招呼接口,返回抽象方法结果
        SayHiInterface pi = () -> "你好啊,这是 lambda 表达式";
        System.out.println(pi.say());
    }
}
运行结果如下:

你好啊,这是lambda表达式

本例直接在 lambda 表达式中创建 SayHiInterface 接口对象,并指定了一个字符串作为接口方法的返回值。最后在输出语句中,pi 对象就是 lambda 表达式创建出的对象,当 pi 调用接口方法时就输出了 lambda 表达式指定的字符串。

3) lambda表达式实现有参抽象方法

抽象方法中有一个或多个参数的函数式接口也是很常见的,lambda 表达式中可以用“(a1,a2,a3)”的方法表示有参抽象方法,圆括号里的标识符对应抽象方法的参数。如果抽象方法中只有一个参数,lambda 表达式则可以省略圆括号。

【实例】使用 lambda 表达式做加法计算。创建函数式接口和测试类,接口抽象方法有两个参数并返回一个 int 型结果。使用 lambda 表达式实现接口,让方法可以计算两个整数的和,具体代码如下:
interface AdditionInterface { //加法接口
    int add(int a, int b); //加法的抽象方法
}

public class ParameterDemo { //测试类
    public static void main(String[] args) {
        //lambda 表达式实现加法接口,返回参数相加的值
        AdditionInterface np = (x, y) -> x + y;
        int result = np.add(15, 26); //调用接口方法
        System.out.println("相加结果:" + result); //输出相加结果
    }
}
运行结果如下:

相加结果:41

在这个实例中,函数式接口的抽象方法有两个参数,lambda 表达式的圆括号内也写了两个参数对应的抽象方法。这里需要注意以下一点,lambda 表达式中的参数不需要与抽象方法的参数名称相同,但顺序必须相同。

4) lambda表达式使用代码块

当函数式接口的抽象方法需要实现复杂逻辑而不是返回一个简单的表达式时,就需要在 lambda 表达式中使用代码块。lambda 表达式会自动判断返回值类型是否符合抽象方法的定义。

【实例】使用lambda表达式为考试成绩分类。创建函数式接口和测试类,接口抽象方法有一个整型参数表示成绩,输入成绩后,返回成绩的字符串评语。在 lambda 表达式中实现成绩判断。
interface CheckGrade {
    String check(int grade); //查询成绩结果
}

public class GradeDemo {
    public static void main(String[] args) {
        CheckGrade g = (n) -> { //lambda 表达式实现代码块
            if (n >= 90 && n <= 100) {
                return "成绩为优"; //如果成绩为 90~100
            } else if (n >= 80 && n < 90) {
                return "成绩为良"; //如果成绩为 80~89
            } else if (n >= 60 && n < 80) {
                return "成绩为中"; //如果成绩为 60~79
            } else if (n >= 0 && n < 60) {
                return "成绩为差"; //如果成绩小于 60
            } else {
                return "成绩无效"; //其他数字不是有效成绩
            }
        };
        System.out.println(g.check(89)); //输出查询结果
    }
}
运行结果如下:

成绩为良

Java lambda表达式调用外部变量

lambda 表达式除了可以调用定义好的参数,还可以调用表达式以外的变量。但是,这些外部的变量有些可以被更改,有些则不能。

例如,lambda 表达式无法更改局部变量的值,但是却可以更改外部类的成员变量(也可以叫作类属性)的值。

1) lambda 表达式无法更改局部变量

局部变量在 lambda 表达式中默认被定义为 final,也就是说,lambda 表达式只能调用局部变量,却不能改变其值。

【实例】使用 lambda 表达式修改局部变量。创建函数式接口和测试类,在测试类的 main() 方法中创建局部变量和接口对象,接口对象使用 lambda 表达式予以实现,并在 lambda 表达式中尝试更改局部变量值。
interface VariableInterface1 { //测试接口
    void method(); //测试方法
}

public class VariableDemo1 { //测试类
    public static void main(String[] args) {
        int value = 100; //创建局部变量
        VariableInterface1 v = () -> { //实现测试接口
            int num = value - 90; //使用局部变量进行赋值
            value = 12; //更改局部变量,此处会报错,无法通过编译
        };
    }
}
在 Eclipse 中编写完这段代码后,会看到更改局部变量的相关代码被标注编译错误,错误提示如下图所示,表示局部变量在 lambda 表达式中是以 final 形式存在的。


图 1 在lambda表达式中更改局部变量会弹出编译错误

2) lambda表达式可以更改类成员变量

类成员变量在 lambda 表达式中不是被 final 修饰的,因此 lambda 表达式可以改变其值。

【实例】使用 lambda 表达式修改类成员变量。创建函数式接口和测试类,在测试类中创建成员属性 value 和成员方法 action()。在 action() 方法中使用 lambda 表达式创建接口对象,并在 lambda 表达式中修改 value 的值。运行程序,查看 value 值是否发生变化。
interface VariableInterface2 { //测试接口
    void method(); //测试方法
}

public class VariableDemo2 { //测试类
    int value = 100; //创建类成员变量
    public void action() { //创建类成员方法
        VariableInterface2 v = () -> { //实现测试接口
            value = -12; //更改成员变量,没提示任何错误
        };
        System.out.println("运行接口方法前 value=" + value); //运行接口方法前先输出成员变量值
        v.method(); //运行接口方法
        System.out.println("运行接口方法后 value=" + value); //运行接口方法后再输出成员变量值
    }
    public static void main(String[] args) {
        VariableDemo2 demo = new VariableDemo2(); //创建测试类对象
        demo.action(); //执行测试类方法
    }
}
运行结果如下:

运行接口方法前value=100
运行接口方法后value=-12

从这个结果中可以看出以下几点:

4) lambda表达式与异常处理

很多接口的抽象方法为了保证程序的安全性,会在定义时就抛出异常。但是 lambda 表达式中并没有抛出异常的语法,这是因为 lambda 表达式会默认抛出抽象方法原有的异常,当此方法被调用时则需要进行异常处理。

【实例】使用 lambda 表达式实现防沉迷接口。创建自定义异常 UnderAgeException,当发现用户是未成年人时进入此异常处理。创建函数式接口,在抽象方法中抛出 UnderAgeException 异常,使用 lambda 表达式实现此接口,并让接口对象执行抽象方法。
import java.util.Scanner;

interface AntiaddictInterface { //防沉迷接口
    boolean check(int age) throws UnderAgeException; //抽象检查方法,抛出用户未成年异常
}

class UnderAgeException extends Exception { //自定义未成年异常
    public UnderAgeException(String message) { //有参构造方法
        super(message); //调用原有父类构造方法
    }
}

public class ThrowExceptionDemo { //测试类
    public static void main(String[] args) {
        //lambda 表达式创建 AntiaddictInterface 对象,默认抛出原有异常
        AntiaddictInterface ai = (a) -> { //如果年龄小于 18 岁
            if (a < 18) {
                throw new UnderAgeException("未满 18 周岁,开启防沉迷模式!"); //抛出异常
            } else {
                return true; //否则验证通过
            }
        };

        Scanner sc = new Scanner(System.in); //创建控制台扫描器
        System.out.println("请输入年龄"); //控制台提示
        int age = sc.nextInt(); //获取用户输入的年龄

        try {
            if (ai.check(age)) { //因为接口方法抛出异常,所以此处必须捕捉异常
                System.out.println("欢迎进入 XX 世界");
            }
        } catch (UnderAgeException e) {
            System.err.println(e); //在控制台上输出异常警告
        }
        sc.close(); //关闭扫描器
    }
}
从这个实例中可以看出,即使 lambda 表达式没有定义异常,原抽象方法抛出的异常仍然是存在的,当接口对象执行此方法时会被强制要求进行异常处理。

这段代码中使用了 Scanner 类来获取用户输入的年龄,当用户输入的年龄小于 18 岁时,捕获到 UnderAgeException 异常,运行结果为:
请输入年龄
16
UnderAgeException: 未满 18 周岁,开启防沉迷模式!

如果用户输入的年龄大于 18 岁,则不会触发异常处理,直接执行其他业务逻辑,运行效果为:
请输入年龄
20
欢迎进入 XX 世界

相关文章