• Java-Lambda表达式基本理解及使用


    背景

    ​ Lambda 表达式是Java 8引入的一个重要特性,它旨在简化函数对象的创建过程,使得Java语言更加简洁、功能更加强大,尤其是在处理集合、流和并发编程中表现突出。Lambda允许将功能作为方法参数或将其赋值给变量,这在函数式编程范式中尤为重要,它促进了行为参数化,即代码可以根据传入的行为(函数)动态变化。

    Lambda 表达式(Lambda expression)可以看作是一个匿名函数

    Lambda 表达式语法

    Lambda表达式的语法结构一般为:

    (parameters) -> expression
    或者
    (parameters) -> {
        statements;
    }
    
    • parameters一个或多个参数,参数之间用逗号分隔,无参数可留空
    • ->:箭头符号,表示“指向”,连接参数列表和主体
    • expression单个表达式当Lambda体只有一条语句时可直接使用
    • { statements; }当Lambda体包含多条语句时,需要用花括号包裹起来,并明确指定返回值(如果有的话)
    // 1. 不需要参数,返回值为 2
    () -> 2
    // 2. 接收一个参数(数字类型),返回其2倍的值
    x -> 2 * x
    // 3. 接受2个参数(数字),并返回他们的和
    (x, y) -> x + y
    // 4. 接收2个int型整数,返回他们的乘积
    (int x, int y) -> x * y
    // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
    (String s) -> System.out.print(s)
    

    函数式接口

    函数式接口只有一个抽象方法的接口,它是Lambda表达式的基础Java 8引入了@FunctionalInterface注解来标记这类接口,但即使没有这个注解只要满足条件的接口也可以作为Lambda的目标类型

    常见的函数式接口

    • `Function``:接收一个T类型的输入,产生一个R类型的输出。
    • Consumer:接收一个输入参数并执行操作,无返回值。
    • Supplier:无参,产生一个结果。
    • Predicate:接收一个输入参数,返回一个布尔值。
    • UnaryOperator:一元操作符,输入和输出类型相同。
    • BinaryOperator:二元操作符,输入和输出类型相同。
    Function

    Function接口代表一个接受一个输入参数产生一个输出的函数。它有一个抽象方法R apply(T t)
    示例:将字符串转换为大写。

    Function<String, String> toUpperCase = String::toUpperCase;
    String result = toUpperCase.apply("hello world");
    System.out.println(result); // 输出: HELLO WORLD
    
    Consumer

    Consumer接口代表一个接收单个输入参数并且无返回的操作。它有一个抽象方法void accept(T t)
    示例:打印字符串。

        Consumer<String> printer = System.out::println;
        printer.accept("This is an example of using Consumer."); // 直接打印字符串
    
    Supplier

    Supplier接口不接受任何参数,但它可以提供一个结果。它有一个抽象方法T get()
    示例:生成随机数。

        Supplier<Integer> randomSupplier = () -> new Random().nextInt(100);
        System.out.println(randomSupplier.get()); // 输出一个0到99之间的随机数
    
    Predicate

    Predicate接口代表一个布尔值条件,它接受一个输入参数并返回一个布尔值结果。它有一个抽象方法boolean test(T t)
    示例:检查字符串是否为空。

        Predicate<String> isEmpty = String::isEmpty;
        System.out.println(isEmpty.test("")); // 输出: true
        System.out.println(isEmpty.test("not empty")); // 输出: false
    
    UnaryOperator

    UnaryOperator是一个特殊的Function,它的输入和输出类型相同
    示例:自增操作。

        UnaryOperator<Integer> increment = x -> x + 1;
        System.out.println(increment.apply(10)); // 输出: 11
    
    BinaryOperator

    BinaryOperator也是一个特殊的Function用于两个相同类型的操作数,并产生相同类型的结果
    示例:两个整数相加。

        BinaryOperator<Integer> add = Integer::sum;
        System.out.println(add.apply(5, 3)); // 输出: 8
    

    Lambda表达式的基本使用

    有返回值函数式接口

        //无参
        @FunctionalInterface
        interface RandomSupplier {
            int getRandom();
        }
        //一个参数
        @FunctionalInterface
        interface SquareFunction {
            int square(int number);
        }
        //两个参数
        @FunctionalInterface
        interface MaxFinder {
            int findMax(int a, int b);
        }
        public static void main(String[] args) {
            RandomSupplier r = () -> new Random().nextInt(100);
            System.out.println(r.getRandom()); // 输出一个0到99的随机数
    
            SquareFunction square = number -> number * number;
            System.out.println(square.square(5)); // 输出: 25
    
            MaxFinder maxFinder = (a, b) -> Math.max(a, b);
            System.out.println(maxFinder.findMax(10, 20)); // 输出: 20
        }
    

    在这里插入图片描述

    无返回值函数式接口

        //无参
        @FunctionalInterface
        interface NoReturnAction {
            void performAction();
        }
        //一个参数
        @FunctionalInterface
        interface SingleParamAction<T> {
            void execute(T param);
        }
        //两个参数
        @FunctionalInterface
        interface DualParamAction<T, U> {
            void apply(T first, U second);
        }
        public static void main(String[] args) {
            NoReturnAction greetUser = () -> System.out.println("欢迎来到我们的平台!");
            greetUser.performAction(); // 执行动作
    
            SingleParamAction<String> logMessage = message -> System.out.println("记录信息: " + message);
            logMessage.execute("用户登录事件"); // 执行动作并传递参数
    
            DualParamAction<String, Integer> processOrder = (item, quantity) ->
                    System.out.println("处理订单: " + item + ", 数量: " + quantity);
            processOrder.apply("苹果", 10); // 执行动作并传递两个参数
        }
    

    在这里插入图片描述

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

    1.所需类型

    • Lambda表达式:适用于函数式接口(即只有一个抽象方法的接口)。Lambda表达式能够直接提供这个方法的实现
    • 匿名内部类:可以用于任何类型的类或接口,不限于函数式接口。对于有多个抽象方法的接口或抽象类,必须实现所有抽象方法。

    2.使用限制不同

    • Lambda表达式:由于其简洁性,通常用于简单操作。不支持复杂的多行语句或声明额外变量(除非在局部作用域内)。
    • 匿名内部类:提供了更多的灵活性,可以在其中定义变量、实现复杂的逻辑,甚至包含构造方法

    3.使用原理不同

    • Lambda表达式:编译器会自动推断出目标类型,并将其转换为一个函数式接口的实例。Lambda表达式本质上是一种编译器的语法糖,使得代码更加简洁。 编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
    • 匿名内部类:在编译时会创建一个新的类(没有类名),这个类继承或实现了指定的父类或接口并在运行时实例化这个新类的对象
    public class LambdaVsAnonymous {
    
        public static void main(String[] args) {
            List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
    
            // 使用Lambda表达式遍历列表并打印元素
            names.forEach(name -> System.out.println(name));
            System.out.println("------------------------------");
            // 使用匿名内部类遍历列表并打印元素
            names.forEach(new Consumer<String>() {
                @Override
                public void accept(String name) {
                    System.out.println(name);
                }
            });
    
            // 使用匿名内部类实例化抽象类Shape
            Shape shape = new Shape() {
                @Override
                void draw() {
                    System.out.println("绘制形状");
                }
            };
            // 调用抽象方法
            shape.draw();
        }
    }
    abstract class Shape {
        abstract void draw();
    }
    

    在这里插入图片描述

    Lambda表达式在使用时有一些重要的注意事项

    • 类型推断:Java编译器能够根据上下文自动推断Lambda表达式的类型,因此通常不需要显式声明类型。但是,理解其背后的目标类型(通常是函数式接口)对于正确使用Lambda至关重要
    • 函数式接口:Lambda表达式只能用于实现具有一个抽象方法的接口(称为函数式接口)。如果试图将Lambda赋值给非函数式接口,将会导致编译错误
    • 简洁性:Lambda表达式旨在简化代码,通常用于简短的操作。复杂的逻辑应该考虑使用传统方法或分离成多个步骤。
    • 访问外部变量:Lambda表达式可以访问其所在范围内的最终变量和有效final变量这意味着外部变量在Lambda中要么是不可变的,要么在Lambda创建前已经赋值且不再改变
    • 捕获变量:在Java 8中,Lambda表达式通过值捕获外部变量,而在Java 9及以上版本中,也可以通过var关键字声明局部变量,但同样受到不可变性的限制。
    • 并行处理:当Lambda表达式用于并行流(例如parallelStream())时,需要特别注意变量的并发访问问题,避免数据竞争和一致性问题。
    • 异常处理:Lambda表达式中可以抛出和处理异常,但需要注意异常传播的规则,特别是在函数式接口方法声明了受检异常时。
    • 可序列化:如果Lambda表达式所实现的函数式接口是可序列化的(如Serializable),那么Lambda表达式本身也必须是可序列化的。这可能影响到捕获的外部变量是否可序列化。
  • 相关阅读:
    web基础学习之(二)HTML介绍
    5.go语言函数提纲
    【软件安装】Centos系统中安装docker容器(华为云HECS云耀服务器)
    TCP 超时重传机制
    解放工程师双手帮助网工做运维
    网络自动化运维(NetDevOps)创作者推荐
    CubeMX+VSCode+Ozone的STM32开发工作流(二)VSCode环境配置
    基于linux的操作系统的通用启动流程(一)
    金融云行至“深水区”
    【代码源每日一题Div1】路径计数2「动态规划 记忆化搜索」
  • 原文地址:https://blog.csdn.net/kdzandlbj/article/details/139602766