一、函数式接口

函数式接口是 Lambda 表达式和方法引用的容器,是整个 Java 函数式编程的基础,所有 Lambda 最终都要绑定到某个函数式接口的抽象方法上。

1. 核心定义

仅包含 一个抽象方法 的接口(可包含多个默认方法、静态方法、私有方法),推荐用 @FunctionalInterface 注解标记(编译器会强制校验是否符合规范)。

2. 核心价值

将「行为(方法逻辑)」抽象为接口类型,让行为可以像变量一样被传递、赋值、作为方法参数/返回值。

3. 常用内置函数式接口(java.util.function 包)

接口类型 核心方法 入参/返回值 典型用途
消费型 Consumer void accept(T t) 入参 T,无返回 遍历集合、打印数据
供给型 Supplier T get() 无入参,返回 T 生成随机数、创建对象
函数型 Function<T,R> R apply(T t) 入参 T,返回 R 数据转换(如字符串转数字)
断言型 Predicate boolean test(T t) 入参 T,返回布尔 过滤集合、条件判断
二元函数 BiFunction<T,U,R> R apply(T t, U u) 入参 T+U,返回 R 双参数转换(如两数运算)

4. 基础示例(自定义+使用)

// 自定义函数式接口(单一抽象方法)
@FunctionalInterface
interface MyFunction {
    String process(String s); // 唯一抽象方法
}

public class FunctionalInterfaceDemo {
    public static void main(String[] args) {
        // 给函数式接口赋值(后续用Lambda实现)
        MyFunction upperCase = s -> s.toUpperCase();
        MyFunction addSuffix = s -> s + "_suffix";
        
        // 调用抽象方法执行逻辑
        System.out.println(upperCase.process("hello")); // HELLO
        System.out.println(addSuffix.process("test")); // test_suffix
    }
}

二、Lambda 表达式

Lambda 是函数式接口的简洁实例化方式,替代冗长的匿名内部类,本质是「一段可传递的代码块」。

1. 核心语法

(参数列表) -> { 方法体 }
简化规则(核心!减少冗余)
场景 简化写法 原始写法
单参数 s -> s.toUpperCase() (String s) -> { return s.toUpperCase(); }
无参数 () -> Math.random() () -> { return Math.random(); }
单语句方法体 (a, b) -> a + b (int a, int b) -> { return a + b; }
多语句方法体 (a, b) -> { int c = a*2; return c + b; } 必须带大括号和return

2. 使用场景:绑定函数式接口

Lambda 不能单独存在,必须绑定到「函数式接口的抽象方法」,常见场景:

场景1:赋值给函数式接口变量
// 绑定Consumer接口
Consumer<String> print = s -> System.out.println(s);
print.accept("Lambda赋值示例"); // 输出:Lambda赋值示例

// 绑定Predicate接口
Predicate<Integer> isOdd = num -> num % 2 != 0;
System.out.println(isOdd.test(5)); // 输出:true
场景2:作为方法参数(行为参数化)
// 自定义方法:接收函数式接口参数
public static <T> List<T> filter(List<T> list, Predicate<T> condition) {
    List<T> result = new ArrayList<>();
    for (T t : list) {
        if (condition.test(t)) {
            result.add(t);
        }
    }
    return result;
}

// 调用:传入Lambda作为过滤条件
public static void main(String[] args) {
    List<Integer> nums = Arrays.asList(1,2,3,4,5);
    // 过滤偶数
    List<Integer> evens = filter(nums, num -> num % 2 == 0);
    System.out.println(evens); // 输出:[2,4]
}

3. 关键注意点

  • Lambda 可捕获外部变量,但变量必须是 final 或「有效 final」(未显式声明 final,但从未重新赋值);
  • Lambda 没有自己的 thisthis 指向外部类的对象;
  • 编译期会将 Lambda 转换为函数式接口的匿名实现类(运行期优化为 invokedynamic 指令)。

三、方法引用

方法引用是 Lambda 的简化写法,当 Lambda 体仅调用一个已有方法时,可用「方法引用」替代,让代码更简洁。

1. 语法格式

方法引用类型 语法 等效 Lambda 示例
静态方法引用 类名::静态方法名 Integer::sum(a,b) -> Integer.sum(a,b)
实例方法引用(对象) 对象名::实例方法名 str::toUpperCase() -> str.toUpperCase()
实例方法引用(类) 类名::实例方法名 String::lengths -> s.length()
构造器引用 类名::new ArrayList::new() -> new ArrayList<>()

2. 实战示例

public class MethodReferenceDemo {
    public static void main(String[] args) {
        // 1. 静态方法引用:Integer::sum
        BinaryOperator<Integer> sum = Integer::sum;
        System.out.println(sum.apply(10, 20)); // 30

        // 2. 实例方法引用(对象):自定义字符串对象的toUpperCase
        String str = "hello";
        Supplier<String> upperCase = str::toUpperCase;
        System.out.println(upperCase.get()); // HELLO

        // 3. 实例方法引用(类):String::length
        Function<String, Integer> strLength = String::length;
        System.out.println(strLength.apply("test")); // 4

        // 4. 构造器引用:ArrayList::new
        Supplier<List<String>> listSupplier = ArrayList::new;
        List<String> list = listSupplier.get();
        list.add("a");
        System.out.println(list); // [a]
    }
}

3. 使用技巧

  • 方法引用的参数列表、返回值必须和绑定的函数式接口抽象方法完全匹配
  • 优先使用方法引用替代简单 Lambda,提升代码可读性(如 Stream 操作中)。

四、Stream 流(函数式集合操作)

Stream 是 Java 提供的「函数式集合处理工具」,基于 Lambda 和函数式接口实现,支持「管道式」数据处理,替代传统的 for 循环,核心是「声明式编程」(关注做什么,而非怎么做)。

1. Stream 核心特性

  • 不存储数据:仅处理数据源(集合、数组等),不修改原数据;
  • 惰性求值:中间操作(如 filter、map)仅记录逻辑,终端操作(如 forEach、collect)才触发计算;
  • 可并行:通过 parallelStream() 实现并行处理,简化多线程编程;
  • 一次性:流只能被消费一次,再次使用需重新创建。

2. 核心操作分类

操作类型 常用方法 特点
中间操作 filter、map、distinct、sorted、limit 返回 Stream,可链式调用,惰性执行
终端操作 forEach、collect、reduce、count、anyMatch 返回非 Stream 结果,触发计算

3. 实战示例(核心场景)

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamDemo {
    // 定义测试实体类
    static class User {
        private String name;
        private int age;
        private String city;

        public User(String name, int age, String city) {
            this.name = name;
            this.age = age;
            this.city = city;
        }
        // 省略getter/setter
        public String getName() { return name; }
        public int getAge() { return age; }
        public String getCity() { return city; }
    }

    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("Alice", 22, "Beijing"),
            new User("Bob", 28, "Shanghai"),
            new User("Charlie", 22, "Beijing"),
            new User("David", 30, "Shanghai")
        );

        // 场景1:过滤+映射 → 获取北京22岁用户的姓名
        List<String> names = users.stream()
            .filter(u -> u.getAge() == 22 && "Beijing".equals(u.getCity())) // 中间操作
            .map(User::getName) // 方法引用简化Lambda
            .collect(Collectors.toList()); // 终端操作
        System.out.println(names); // [Alice, Charlie]

        // 场景2:分组 → 按城市分组用户
        Map<String, List<User>> groupByCity = users.stream()
            .collect(Collectors.groupingBy(User::getCity));
        System.out.println(groupByCity); // {Beijing=[Alice, Charlie], Shanghai=[Bob, David]}

        // 场景3:归约 → 计算所有用户的年龄总和
        int totalAge = users.stream()
            .mapToInt(User::getAge) // 避免装箱,提升性能
            .sum(); // 终端操作
        System.out.println(totalAge); // 102

        // 场景4:并行流 → 大数据量处理(自动多线程)
        long count = users.parallelStream()
            .filter(u -> u.getAge() > 25)
            .count();
        System.out.println(count); // 2
    }
}

4. 高频技巧

  • 基本类型流(IntStream/LongStream/DoubleStream):避免自动装箱/拆箱,提升性能;
  • Optional 结合 Stream:处理可能为空的结果(如 findFirst() 返回 Optional<User>);
  • collect() 是最灵活的终端操作:支持转集合、分组、拼接字符串(Collectors.joining())等。

五、核心总结

  1. 函数式接口是基础:定义了「行为的类型」,是 Lambda 和方法引用的「载体」;
  2. Lambda 表达式是函数式接口的「简洁实现」:替代匿名内部类,直接表达行为逻辑;
  3. 方法引用 简化Lambda :进一步简化仅调用单个方法的 Lambda;
  4. Stream 流是函数式接口+Lambda 的「实战场景」:通过接收函数式接口参数(如 filter 接收 Predicate),实现声明式集合处理。

六、避坑

  1. Stream 不要滥用:小数据量场景下,传统 for 循环可能比 Stream 更快(无线程开销);
  2. Lambda 不要写太复杂:复杂逻辑拆分为方法,用方法引用替代,提升可读性;
  3. 并行流注意线程安全:数据源若为线程不安全的集合(如 ArrayList),并行流处理时需注意;
  4. 函数式接口必须满足单一抽象方法:用 @FunctionalInterface 注解强制校验,避免后续误加抽象方法。

实战练习

需求:从员工列表中筛选出「部门为研发、年龄>30岁」的员工,按薪资降序排序,最终获取他们的姓名和薪资(格式:姓名-薪资),拼接成字符串。

// 自行实现以下逻辑,检验掌握程度
List<Employee> employees = // 初始化员工列表
String result = employees.stream()
    .filter(/* 研发部门+年龄>30 */)
    .sorted(/* 薪资降序 */)
    .map(/* 转换为 姓名-薪资 字符串 */)
    .collect(/* 拼接为逗号分隔的字符串 */);

(参考答案:

String result = employees.stream()
    .filter(e -> "研发".equals(e.getDept()) && e.getAge() > 30)
    .sorted((e1, e2) -> Double.compare(e2.getSalary(), e1.getSalary()))
    .map(e -> e.getName() + "-" + e.getSalary())
    .collect(Collectors.joining(","));

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐