Lambda表达式(JAVA基础)

本文介绍了Java中的Lambda表达式,包括其基本语法、在实现函数接口、创建线程、操作集合以及闭包问题中的应用。重点讲解了如何使用Lambda表达式简化代码,以及与StreamAPI的集成,同时讨论了闭包的原理和注意事项。
摘要由CSDN通过智能技术生成

目录

一.基本语法

二.实现函数接口

三.引用方法创建线程

四.操作集合

五.闭包问题


一.基本语法

在Java中,lambda表达式是一种简洁、函数式的方法来表示匿名函数。它们主要用于需要函数式接口的场合,如Java 8引入的Stream API、Comparator接口等。

Lambda表达式的基本语法如下:

(parameter-list) -> { body-of-lambda }

其中:

parameter-list:参数列表,可以有一个或多个参数,也可以没有参数。
->:Lambda操作符,用于分隔参数列表和Lambda主体。
{ body-of-lambda }:Lambda主体,包含了Lambda表达式要执行的代码。

Lambda表达式的几个例子:

1.无参数、无返回值的Lambda表达式:

() -> System.out.println("Hello, Lambda!")

2.单个参数、无返回值的Lambda表达式(参数类型可以省略):

x -> System.out.println(x)

3.多个参数、有返回值的Lambda表达式:

(int x, int y) -> x + y

4.如果Lambda表达式只有一个参数,那么圆括号可以省略:

x -> x * x

5.如果Lambda表达式的主体只有一条语句,那么花括号可以省略,并且这条语句的返回值会被自动返回:

x -> x * x

6.使用Lambda表达式实现Runnable接口:

Runnable runnable = () -> System.out.println("Running Lambda!");
runnable.run();

7.使用Lambda表达式作为比较器(Comparator):

List<String> list = Arrays.asList("Apple", "Banana", "Cherry");
list.sort((s1, s2) -> s1.compareTo(s2));

在Java中,Lambda表达式主要用于替代匿名内部类,尤其是当接口只有一个抽象方法(即函数式接口)时。Lambda表达式提供了一种更简洁、更直观的方式来表达这些函数式接口的实现。

要注意的是,Lambda表达式的类型是由上下文推断的,即Lambda表达式需要赋值给一个函数式接口的变量,或者作为参数传递给一个接受函数式接口的方法。Java编译器会根据Lambda表达式的参数和返回类型,以及上下文中的函数式接口,来推断Lambda表达式的类型。

二.实现函数接口

在Java中,Lambda表达式通常用于实现只有一个抽象方法的接口,这种接口被称为函数式接口(Functional Interface)。从Java 8开始,接口可以声明为函数式接口,通过添加@FunctionalInterface注解来明确这一点。如果接口包含超过一个抽象方法,那么添加这个注解会导致编译错误。

函数式接口的一个典型例子是Runnable接口,它只有一个run方法。使用Lambda表达式,我们可以很方便地实现这个接口,而不需要创建匿名内部类。下面是一个函数式接口的定义和如何使用Lambda表达式来实现它的例子:

// 定义一个函数式接口
@FunctionalInterface
interface MyFunction {
    void apply(int x, int y);
}

public class LambdaDemo {
    public static void main(String[] args) {
        // 使用Lambda表达式实现MyFunction接口
        MyFunction myLambda = (x, y) -> System.out.println("Result: " + (x + y));

        // 调用Lambda表达式
        myLambda.apply(10, 20);
    }
}

在这个例子中,MyFunction是一个函数式接口,它有一个名为apply的抽象方法,接受两个int参数并没有返回值。然后,我们使用Lambda表达式(x, y) -> System.out.println("Result: " + (x + y))来实现这个接口,并将Lambda表达式赋值给MyFunction类型的变量myLambda。最后,我们调用myLambda的apply方法,它会执行Lambda表达式中的代码。

除了Runnable和自定义的函数式接口,Java库中还提供了许多其他的函数式接口,如Consumer、Supplier、Function、Predicate等,它们都在java.util.function包中定义。这些接口可以与Lambda表达式一起使用,以简化代码并增加可读性。例如,使用Consumer接口来接受一个参数并执行某个操作:

import java.util.function.Consumer;

public class LambdaWithConsumer {
    public static void main(String[] args) {
        // 使用Lambda表达式实现Consumer接口
        Consumer<String> consumer = (s) -> System.out.println("Consumed: " + s);

        // 使用Consumer接口的方法accept来调用Lambda表达式
        consumer.accept("Hello, Consumer!");
    }
}

在这个例子中,Consumer接口有一个accept方法,它接受一个参数并没有返回值。Lambda表达式(s) -> System.out.println("Consumed: " + s)实现了这个接口,并打印出传入的字符串。

三.引用方法创建线程

在Java中,使用Lambda表达式和方法引用可以非常方便地创建线程。从Java 8开始,你可以使用Thread类的构造函数,它接受一个Runnable对象,而Runnable是一个函数式接口,所以你可以使用Lambda表达式来创建线程。下面是一个使用Lambda表达式创建线程的示例:

public class LambdaThreadExample {
    public static void main(String[] args) {
        // 使用Lambda表达式创建线程
        Thread thread = new Thread(() -> {
            System.out.println("线程正在运行,由Lambda表达式创建");
        });

        // 启动线程
        thread.start();
    }
}

在这个例子中,我们创建了一个Thread对象,并传递了一个Lambda表达式作为参数给Thread的构造函数。这个Lambda表达式实现了Runnable接口的run方法,当线程启动时,它会执行这个Lambda表达式中的代码。

除了使用Lambda表达式,你还可以使用方法引用来创建线程。方法引用实际上是对已有方法的引用,它使得Lambda表达式的写法更加简洁。
下面是一个使用方法引用创建线程的示例:

public class LambdaThreadExample {
    public static void main(String[] args) {
        // 使用方法引用创建线程
        Thread thread = new Thread(LambdaThreadExample::printThreadRunning);

        // 启动线程
        thread.start();
    }

    // 这是一个静态方法,它符合Runnable接口的run方法签名
    public static void printThreadRunning() {
        System.out.println("线程正在运行,由方法引用创建");
    }
}

在这个例子中,我们使用了LambdaThreadExample类的静态方法printThreadRunning作为Thread构造函数的参数。这个方法的签名与Runnable接口的run方法相匹配,所以我们可以直接使用方法引用来代替Lambda表达式。

使用Lambda表达式或方法引用来创建线程可以使代码更加简洁和易读。同时,它们也是Java 8引入的Lambda特性的一部分,使得函数式编程在Java中更加容易实现。

四.操作集合

在Java中,Lambda表达式可以非常方便地用于操作集合(如List、Set等),尤其是与Stream API结合使用时。Stream API允许你以声明性方式处理数据集合(即你可以描述你想要做什么,而不是描述如何去做)。

以下是一些使用Lambda表达式操作集合的例子:

1.遍历集合(forEach)

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));


2.或者使用方法引用:

names.forEach(System.out::println);

3.过滤集合(filter)

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> filteredNames = names.stream()
                                  .filter(name -> name.startsWith("A"))
                                  .collect(Collectors.toList());
filteredNames.forEach(System.out::println); // 输出 Alice

4.映射集合(map)

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
                                 .map(name -> name.length())
                                 .collect(Collectors.toList());
nameLengths.forEach(System.out::println); // 输出名字长度

5.排序集合(sorted)

List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
List<String> sortedNames = names.stream()
                                .sorted()
                                .collect(Collectors.toList());
sortedNames.forEach(System.out::println); // 输出排序后的名字

6.聚合操作(reduce)

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
                .reduce(0, Integer::sum);
System.out.println(sum); // 输出 15

7.收集到不同的集合类型(collect)

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 收集到 Set
Set<String> uniqueNames = names.stream()
                               .collect(Collectors.toSet());

// 收集到 List,并排序
List<String> sortedNames = names.stream()
                                .sorted()
                                .collect(Collectors.toList());

// 收集到自定义的类
class Person {
    String name;
    // 构造器、getter和setter等
}

List<Person> people = names.stream()
                           .map(name -> new Person(name))
                           .collect(Collectors.toList());

8.查找元素(findAny, findFirst)

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> anyName = names.stream().findAny();
Optional<String> firstName = names.stream().findFirst();

anyName.ifPresent(System.out::println); // 输出任意一个名字
firstName.ifPresent(System.out::println); // 输出第一个名字

这些示例展示了如何使用Lambda表达式与Stream API结合来操作集合。Lambda表达式允许你简洁地表达操作逻辑,而Stream API则提供了丰富的函数式操作来处理数据。使用这些特性,你可以更加高效地处理集合数据。

五.闭包问题

在Java中,Lambda表达式可以捕获其外部作用域的变量,使得这些变量在Lambda体内部可用。这个过程被称为闭包(Closure)。闭包是函数式编程的一个重要概念,它允许函数记住并访问其词法作用域,即使该函数在其原始作用域之外执行。

在Java中,当一个Lambda表达式捕获其外部变量时,这些变量实际上是被隐式地“传递”给了Lambda表达式。如果Lambda表达式修改了捕获的变量,并且这个变量是基本数据类型,那么实际上会捕获该变量的一个副本,因此原始变量的值不会被改变。但如果捕获的是对象引用,那么Lambda表达式和原始变量将引用同一个对象,因此对该对象的任何修改都会反映在原始变量上。

这里有几个关于Java中Lambda闭包的要点:

1.值捕获:对于局部变量,Lambda表达式只能捕获final或effectively final(实际上是final,即初始化后不再改变的变量)的变量。这是因为在Lambda表达式创建时,需要确保捕获的变量不会在其后的代码中改变,从而保持Lambda表达式的稳定性。

2.引用捕获:对于成员变量和静态变量,没有这样的限制,因为它们的生命周期与类的实例或类本身相同。Lambda表达式可以自由地捕获和修改这些变量的值。

3.逃逸分析:Java编译器会进行逃逸分析,以确定一个对象是否可能被外部方法所访问。如果一个对象只被一个线程访问,并且不会被外部方法所逃逸,那么编译器可能会对该对象进行优化,比如标记为栈上分配(Stack Allocation),而不是堆上分配。这可以提高性能并减少垃圾收集的压力。但是,如果Lambda表达式捕获了一个对象,并且这个Lambda表达式可能被传递到其他方法中执行,那么这个对象就不能被逃逸分析优化,它必须被分配在堆上。

4.序列化问题:如果一个Lambda表达式捕获了非瞬态(non-transient)和非静态(non-static)的字段,那么该Lambda表达式不能被序列化,因为序列化机制无法处理这种捕获关系。为了解决这个问题,可以将这些字段标记为瞬态(transient)或静态(static),或者提供一个自定义的序列化机制。

5.性能考虑:虽然Lambda表达式和闭包提供了很大的便利性和灵活性,但它们也可能带来一些性能开销。捕获变量会增加Lambda表达式的大小,并可能导致额外的对象创建和内存分配。因此,在性能敏感的应用中,应谨慎使用Lambda表达式和闭包,并进行适当的性能测试和分析。

下面是一个简单的Java Lambda闭包示例:

public class LambdaClosureExample {
    public static void main(String[] args) {
        String str = "Hello, Lambda!";
        
        Runnable runnable = () -> System.out.println(str); // 捕获str变量
        
        str = "Changed!"; // 修改str变量,但不会影响Lambda表达式中的str
        
        runnable.run(); // 输出:Hello, Lambda!
    }
}

在这个例子中,str变量被Lambda表达式捕获并用于打印。随后,我们修改了str变量的值,但这并不影响已经创建的Lambda表达式中的str变量的值,因为Lambda表达式捕获的是str的一个副本(对于基本数据类型)或一个引用(对于对象)。


 文章制作不易,如果有帮助的话,还希望能给个点赞关注支持一下,谢谢大家!🙏🙏🙏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只藏羚吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值