Day31 | Lambda表达式与函数式接口

47 阅读5分钟

Day26~Day30的文章都是围绕Java中的集合框架来讲的。

其中集合的排序、过滤等操作有些使用的是匿名内部类,有些则是使用的lambda形式。

Lambda表达式和函数式接口是Java8引入的,这个新引入让程序员在一定程度上可以写出函数式语言一样的代码。

本文就来讲一下Lambda表达式和函数式接口,这对于之前的集合以及后续其他模块的学习都大有裨益。

一、匿名内部类说起

举一个之前我们在学习集合时的例子,假设我们要对学生列表按照年龄进行排序。

以前的我们的代码可能是长这个样子的:

Collections.sort(students, new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getAge() - o2.getAge();
    }
});

这种写法是典型的匿名内部类的写法。

其实这段代码看着就很啰嗦,只是比较两个学生年龄,写了那么多代码。

接口名、方法名、参数类型都得重复。

然后核心的逻辑还被样板代码包裹起来了。

这就是Java引入Lambda表达式的原因之一。

二、什么是Lambda表达式

Lambda表达式是Java中的一种语法糖,用于表示一个匿名函数,它可以作为方法参数或变量传递,让代码更加简洁。

Lambda表达式的本质就是对函数式接口的简洁实现。

语法格式:

(参数列表) -> { 方法体 }

这里面还有一部分在某些情况下可以省略的东西。

参数类型可以省略,交给编译器自动推导。

如果只有一个参数而且类型是可以推导的,那小括号也可以省略。

如果方法体里面只有一条语句,那大括号和return都可以省略。

还是学生列表排序的案例,我们看看lambda形式是怎么写的。

// 完整的lambda
Comparator<Student> cmp = (Student s1, Student s2) -> {
    return s1.getAge() - s2.getAge();
};

// 参数类型去掉了
Comparator<Student> cmp = (s1, s2) -> {
    return s1.getAge() - s2.getAge();
};

// 方法体只有一句,大括号和return都省略掉了
Comparator<Student> cmp = (s1, s2) -> s1.getAge() - s2.getAge();

三、什么是函数式接口?

函数式接口(Functional Interface)是指只包含一个抽象方法的接口。

@FunctionalInterface
public interface MyFunction {
    void execute();
}

这里的@FunctionalInterface注解不是必须的,但是最好加上,防止你在写的时候不小心写了多个抽象方法。

注解是一种给代码添加元数据的特殊标记,它本身不直接影响程序逻辑,而是为编译器、框架或运行时环境提供附加信息。 可暂时不关注,后续讲反射的时候会讲。

Java中用它来作为Lambda表达式的类型。

Runnable接口就是一个典型的函数式接口,因为它只有一个抽象方法run()。

@FunctionalInterface注解会强制编译器检查该接口是不是真的只有一个抽象方法。如果接口不满足条件(例如,没有抽象方法或多于一个),编译器就会报错。

当我们写下 () -> System.out.println(...) 并把它传递给new Thread() 的时候,编译器发现Thread的构造方法需要一个Runnable 对象。

于是,它检查Runnable接口,发现它只有一个抽象方法run(),这个方法没有参数、没有返回值。

这跟Lambda表达式 () -> ... 的结构完全匹配!

所以,编译器自动把这个Lambda表达式视为Runnable接口的一个实现。

Lambda表达式提供了方法体,函数式接口提供了方法签名。

Java8内置了很多函数式接口,集中在java.util.function包中,常用的有:

接口抽象方法描述应用场景
Predicateboolean test(T t)断言/判断:接收一个参数,返回布尔值过滤数据
Consumervoid accept(T t)消费:接收一个参数,没有返回值遍历、打印、处理数据
Function<T, R>R apply(T t)功能/转换:接收一个参数,返回一个结果数据映射、类型转换
SupplierT get()供给:不接收参数,返回一个结果创建对象、提供数据
package com.lazy.snail.day31;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * @ClassName Day31Demo
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/7/8 11:02
 * @Version 1.0
 */
public class Day31Demo {
    public static void main(String[] args) {
        Predicate<String> isLong = s -> s.length() > 5;
        System.out.println(isLong.test("懒惰蜗牛"));
        System.out.println(isLong.test("lazysnail"));

        Consumer<String> printer = s -> System.out.println("打印: " + s);
        printer.accept("懒惰蜗牛");

        Function<String, Integer> toLength = s -> s.length();
        System.out.println(toLength.apply("懒惰蜗牛"));

        Supplier<Double> randomGenerator = () -> Math.random();
        System.out.println(randomGenerator.get());
    }
}

四、方法引用

方法引用是Lambda表达式的一种特殊的写法。

如果你的Lambda主体只是调用一个已经存在的方法,那就可以使用方法引用。

package com.lazy.snail.day31;


import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @ClassName Day31Demo2
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/7/8 13:53
 * @Version 1.0
 */
public class Day31Demo2 {
    public static void main(String[] args) {
        // 静态方法引用
        System.out.println("-----------------静态方法引用---------------------");
        Function<String, Integer> m1 = s -> Integer.parseInt(s);
        System.out.println("Lambda: " + m1.apply("123"));

        Function<String, Integer> m2 = Integer::parseInt;
        System.out.println("方法引用: " + m2.apply("123"));

        // 特定实例方法引用
        System.out.println("-----------------特定实例方法引用---------------------");
        String str = "懒惰蜗牛";
        Consumer<String> q1 = s -> System.out.println(s);
        q1.accept(str);

        Consumer<String> q2 = System.out::println;
        q2.accept(str);


        // 特定类型任意对象方法引用
        System.out.println("-----------------特定类型任意对象方法引用---------------------");
        Comparator<String> l1 = (s1, s2) -> s1.compareTo(s2);
        System.out.println("Lambda: " + l1.compare("Java", "C++"));

        Comparator<String> l2 = String::compareTo;
        System.out.println("方法引用: " + l2.compare("Java", "Python"));


        // 构造方法引用
        System.out.println("-----------------构造方法引用---------------------");
        Function<String, Person> lambdaFactory = name -> new Person(name);
        Person p1 = lambdaFactory.apply("懒惰蜗牛");
        System.out.println("Lambda : " + p1);

        Function<String, Person> methodRefFactory = Person::new;
        Person p2 = methodRefFactory.apply("lazysnail");
        System.out.println("方法引用 : " + p2);
    }
}

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
        System.out.println("构造方法被调用, name=" + name);
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }
}

小结

类型语法Lambda 等价形式
静态方法引用ClassName::staticMethod(args) -> ClassName.staticMethod(args)
特定实例方法引用instance::instanceMethod(args) -> instance.instanceMethod(args)
特定类型任意对象方法引用ClassName::instanceMethod(obj, args) -> obj.instanceMethod(args)
构造方法引用ClassName::new(args) -> new ClassName(args)

结语

今天一起了解了Java函数式编程,熟悉了Java的Lambda表达式。

希望通过这篇文章,你能正确的回答以下这几个问题:

什么是 Lambda 表达式?为什么需要它?

什么是函数式接口?怎么定义和使用?

Lambda 表达式的语法细节有哪些?

下一篇预告

Day32 | Java Stream流式编程详解

如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!

更多文章请关注我的公众号《懒惰蜗牛工坊》