【Java】Lambda表达式

Lambda表达式

Lambda 解决了什么问题

回想一下用匿名类实现 Comparator 的场景:

java 复制代码
// Java 8 之前的写法:为了一行逻辑写了六行代码
Collections.sort(list, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
});

真正有用的其实只有 o1 - o2 这个比较逻辑,其他全是语法噪音。Lambda 让这一切变得简洁:

java 复制代码
// Lambda 写法:逻辑一目了然
Collections.sort(list, (o1, o2) -> o1 - o2);

这种将行为作为参数传递的能力,正是函数式编程的核心思想。当代码中遍布此类操作时,Lambda 带来的简洁性会被成倍放大。

Lambda 语法

Lambda 表达式由三部分组成:参数列表 + 箭头符号 -> + 函数体

参数情况 有返回值 无返回值(void)
无参 () -> 结果 () -> { return 结果; } () -> 动作 () -> { 动作; }
单参 x -> 结果 (x) -> 结果 x -> 动作 (x) -> 动作
多参 (x, y) -> 结果 (x, y) -> { return 结果; } (x, y) -> 动作 (x, y) -> { 动作; }

语法精简

  • 参数类型可省略:编译器根据上下文自动推断(类型推断),如果需要省略,每个参数的类型都要省略。
  • 单参数可省略括号x -> x * 2 等价于 (x) -> x * 2
  • 单表达式可省略 return 和大括号(a, b) -> a + b 自动返回表达式结果
java 复制代码
// 无参 + 有返回值
() -> 42
() -> { return 42; }

// 无参 + 无返回值  
() -> System.out.println("Hi")
() -> { System.out.println("Hi"); }

// 单参 + 有返回值
x -> x * 2
(x) -> { return x * 2; }

// 单参 + 无返回值
x -> System.out.println(x)
(x) -> { System.out.println(x); }

// 多参 + 有返回值
(a, b) -> a + b
(a, b) -> { return a + b; }

// 多参 + 无返回值
(a, b) -> System.out.println(a + b)
(a, b) -> { System.out.println(a + b); }

函数式接口:Lambda 的载体⭐

@FunctionalInterface⭐

Lambda 表达式本身必须依附于一个函数式接口 。所谓函数式接口,就是只有一个抽象方法的接口。

一个接口要成为函数式接口,可以在接口上写注解@FunctionalInterface,告诉编译器,这个接口必须满足以下条件:

  • 唯一的抽象方法: 接口中只能声明一个没有方法体的方法。

  • 可以包含其他方法: 允许有任意数量的 default(默认方法)static(静态方法),因为它们已经有了具体实现。

  • Object 类方法: 重写 Object 类的方法(如 equals())不计入抽象方法计数。

java 复制代码
@FunctionalInterface
public interface MyChecker {
    // 唯一的抽象方法
    boolean test(int value);

    // 允许有默认方法
    default void log(int value) {
        System.out.println("Checking value: " + value);
    }

    // 编译报错:如果再取消下面注释,就会报错,因为有两个抽象方法了
    // void clear(); 
}

函数式接口的使用⭐

java 复制代码
@FunctionalInterface
public interface Calculator {
    int operate(int a, int b);
}

// Lambda 实现了这个唯一的抽象方法
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;

等价的匿名内部类 (Anonymous Inner Class) 写法如下,只是等价的写法两者并不完全一样

java 复制代码
// 1. 加法操作的匿名内部类写法
Calculator add = new Calculator() {
    @Override
    public int operate(int a, int b) {
        return a + b;
    }
};

// 2. 乘法操作的匿名内部类写法
Calculator multiply = new Calculator() {
    @Override
    public int operate(int a, int b) {
        return a * b;
    }
};

常见的内置函数式接口⭐

Java 8 在 java.util.function 包中内置了四大核心函数式接口,覆盖了绝大多数使用场景:

接口名 抽象方法 描述 应用场景
Predicate<T> boolean test(T t) 接收一个参数,返回布尔值 数据过滤、条件校验
Consumer<T> void accept(T t) 接收参数,不返回值 日志记录、数据输出、修改对象状态
Function<T, R> R apply(T t) 接收 T,返回 R 类型转换、属性提取
Supplier<T> T get() 不接收参数,返回结果 懒加载、初始化对象、生成随机数
Runnable void run() 执行无参无返回的任务 线程执行、异步任务
Callable<V> V call() 执行任务并返回值 带返回值的异步任务
Comparator<T> int compare(T o1, T o2) 比较两个对象 排序比较、优先级判断
java 复制代码
// 接收一个字符串 s,判断其长度是否大于 5,返回布尔值。
// Lambda 写法
Predicate<String> isLong = s -> s.length() > 5;

// 匿名内部类等价写法
Predicate<String> isLongAnon = new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.length() > 5;
    }
};
java 复制代码
//接收一个字符串 s,直接执行打印操作,没有返回值。

// Lambda 写法
Consumer<String> printer = s -> System.out.println(s);

// 匿名内部类等价写法
Consumer<String> printerAnon = new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
};
java 复制代码
// 接收一个字符串 s,将其映射(转换)为它的长度(Integer 类型)。

// Lambda 写法
Function<String, Integer> length = s -> s.length();

// 匿名内部类等价写法
Function<String, Integer> lengthAnon = new Function<String, Integer>() {
    @Override
    public Integer apply(String s) {
        return s.length();
    }
};
java 复制代码
//不需要任何输入参数,每次调用时返回一个随机的 Double 值。

// Lambda 写法
Supplier<Double> random = () -> Math.random();

// 匿名内部类等价写法
Supplier<Double> randomAnon = new Supplier<Double>() {
    @Override
    public Double get() {
        return Math.random();
    }
};
java 复制代码
// Runnable:线程执行
// Lambda 写法
new Thread(() -> System.out.println("新线程运行中")).start();
// Runnable 的匿名内部类等价写法
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("新线程运行中");
    }
}).start();
java 复制代码
// Comparator:排序比较
// Lambda 写法
List<String> list = Arrays.asList("apple", "banana", "cat");
list.sort((a, b) -> a.length() - b.length());  // 按长度排序

// Comparator 的匿名内部类等价写法
List<String> list = Arrays.asList("apple", "banana", "cat");
list.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

Lambda 与匿名类的 this ⭐

在 Java 中,Lambda 表达式并不是匿名内部类的语法糖,它们在作用域(Scope)的处理上完全不同。

维度 匿名内部类 Lambda 表达式
this 的含义 指向内部类对象本身 指向外层类对象 (和外层方法内的 this 一模一样)。
作用域 引入了新的作用域 。可以在内部声明和 this 绑定的变量。 处于词法作用域中。它没有自己的作用域,而是共享外层的。
变量遮蔽 内部定义的变量会遮蔽外部类的同名变量。 不能在 Lambda 参数中定义与局部变量同名的变量(会编译报错)。

通过下面的例子,可以清晰地看到 this 的指向变化:

java 复制代码
public class ThisDemo {
    private String name = "外部类实例变量";

    public void test() {
        // 1. 匿名内部类实现
        Runnable anonRunnable = new Runnable() {
            String name = "匿名内部类局部变量";
            @Override
            public void run() {
                // 此处的 this 指向的是 new Runnable() 产生的对象
                System.out.println("匿名内部类中的 this: " + this.getClass().getSimpleName());
                System.out.println("匿名内部类访问自己的变量: " + this.name);
            }
        };

        // 2. Lambda 表达式实现
        Runnable lambdaRunnable = () -> {
            // 此处的 this 指向的是外部的 ThisDemo 实例
            // Lambda 内部甚至不能直接定义一个叫 name 的成员变量(除非是局部变量)
            System.out.println("Lambda 中的 this: " + this.getClass().getSimpleName());
            System.out.println("Lambda 访问外部类的变量: " + this.name);
        };

        new Thread(anonRunnable).start();
        new Thread(lambdaRunnable).start();
    }

    public static void main(String[] args) {
        new ThisDemo().test();
    }
}

输出结果

复制代码
匿名内部类中的 this: Test0414.ThisDemo$1
匿名内部类访问自己的变量: 匿名内部类局部变量
Lambda 中的 this: ThisDemo
Lambda 访问外部类的变量: 外部类实例变量

Lambda的变量捕获⭐

Lambda 表达式可以访问外部的局部变量和成员变量,但有严格的限制。

变量类型 限制 原因
局部变量 初始化后不再修改(effectively final) 局部变量存于栈,Lambda 可能异步执行,复制值保证一致性
实例成员变量 无限制,可修改 存于堆,Lambda 通过 this 引用访问
静态成员变量 无限制,可修改 全局共享,生命周期与类相同
java 复制代码
public class LambdaCaptureDemo {
    
    // ✅ 正确:变量初始化后不再修改(effectively final)
    public void validCapture() {
        int x = 10;
        Runnable r = () -> System.out.println(x);
        r.run();  // 输出 10
    }
    
    // ❌ 错误:变量被重新赋值,破坏了 effectively final
    public void invalidCapture() {
        int y = 20;
        y = 30;  // 重新赋值
        // Runnable r = () -> System.out.println(y);  // 编译报错
    }
    
    // ✅ 实例变量无限制,可读可写
    private int count = 0;
    public void instanceVariable() {
        Runnable r = () -> {
            count++;  // 可以修改
            System.out.println(count);
        };
        r.run();
    }
}

为什么有这个限制?

Lambda 可能在不同线程、不同时机执行。如果允许修改局部变量,会出现数据不一致问题。Java 选择让 Lambda 持有变量的副本,强制原变量不可变来保证安全。

Lambda在集合当中的使用⭐

Java 8 为配合 Lambda 表达式,在集合框架中新增的方法。

Collection 新增方法

方法 参数类型 作用
removeIf(Predicate<? super E> filter) Predicate 批量删除符合条件的元素
stream() 返回顺序流
parallelStream() 返回并行流
spliterator() 返回可分割迭代器(用于并行遍历)
forEach(Consumer<? super E> action) Consumer 内部迭代遍历元素
java 复制代码
// Collection.removeIf():删除偶数
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
numbers.removeIf(n -> n % 2 == 0);  // [1, 3, 5]

// Collection.forEach
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(s -> System.out.println("Hello, " + s));

List 新增方法

方法 参数类型 作用
replaceAll(UnaryOperator<E> operator) UnaryOperator 对每个元素应用操作并替换
sort(Comparator<? super E> c) Comparator 按指定规则排序
java 复制代码
// List.replaceAll():所有元素转大写
List<String> words = Arrays.asList("hello", "world");
words.replaceAll(s -> s.toUpperCase());  // ["HELLO", "WORLD"]

// List.sort():按长度排序
words.sort((a, b) -> a.length() - b.length());

Map 新增方法

方法 参数类型 作用
getOrDefault(Object key, V defaultValue) 无函数式接口 取值,不存在时返回默认值
forEach(BiConsumer<? super K, ? super V> action) BiConsumer 遍历键值对
replaceAll(BiFunction<? super K, ? super V, ? extends V> function) BiFunction 对每个键值对应用操作并替换值
putIfAbsent(K key, V value) 无函数式接口 键不存在时才放入
remove(Object key, Object value) 无函数式接口 键值都匹配时才删除
replace(K key, V oldValue, V newValue) 无函数式接口 键和旧值都匹配时才替换
replace(K key, V value) 无函数式接口 键存在时才替换值
computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) Function 键不存在时计算值并放入
computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) BiFunction 键存在时重新计算值
compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) BiFunction 无论键是否存在都重新计算
merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) BiFunction 合并新旧值
java 复制代码
// Map.forEach():遍历键值对
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.forEach((k, v) -> System.out.println(k + " = " + v));

// Map.computeIfAbsent():键不存在时计算放入
map.computeIfAbsent("cherry", k -> k.length());  // "cherry" = 6

// Map.merge():合并值
map.merge("apple", 1, (oldVal, newVal) -> oldVal + newVal);  // "apple" = 2

方法引用:让 Lambda 更简洁

当 Lambda 表达式的逻辑仅仅是调用一个已有方法时,方法引用更简洁:

类型 语法 Lambda 等价形式
静态方法引用 Integer::parseInt s -> Integer.parseInt(s)
实例方法引用 System.out::println x -> System.out.println(x)
对象方法引用 String::length s -> s.length()
构造器引用 ArrayList::new () -> new ArrayList()
java 复制代码
// 方法引用让代码更加声明式
List<String> words = Arrays.asList("apple", "banana", "cherry");
words.forEach(System.out::println);  // 比 x -> System.out.println(x) 更干净

Lambda + Stream

Lambda 的真正威力在与 Stream API 结合时彻底释放。Stream 提供了一套声明式的数据处理流水线,配合 Lambda 让集合操作变得行云流水:

java 复制代码
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);

// 需求:找出所有大于3的偶数,排序后取前两个
List<Integer> result = numbers.stream()
    .filter(n -> n > 3)          // 过滤:Predicate
    .filter(n -> n % 2 == 0)     // 再次过滤
    .sorted()                    // 排序
    .limit(2)                    // 截取
    .collect(Collectors.toList()); // 收集结果

// 用传统循环写这段逻辑,代码量至少翻倍

Stream 操作分为两类:

  • 中间操作 (Intermediate):filtermapsorted 等,返回新 Stream,惰性执行
  • 终端操作 (Terminal):collectforEachcount 等,触发实际计算

Lambda 异常处理

Java 的函数式编程有一个不大不小的痛点:java.util.function 包中的标准接口(如 FunctionConsumerPredicate 等)的抽象方法没有声明 throws

这意味着,当 Lambda 表达式中需要抛出受检异常(Checked Exception)时,编译器会直接报错。

假设我们有一个操作:读取文件的第一行,并转换为大写。传统写法:

java 复制代码
// 传统写法:正常抛出异常
public String readFirstLine(File file) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(file))) {
        return br.readLine().toUpperCase();
    }
}

把这个逻辑封装成一个 Function 传递给 Stream 的 map 操作时,问题来了:

java 复制代码
List<File> files = Arrays.asList(new File("a.txt"), new File("b.txt"));

// ❌ 编译错误:未报告的异常 java.io.IOException
files.stream()
    .map(file -> {
        BufferedReader br = new BufferedReader(new FileReader(file));
        return br.readLine().toUpperCase();
    })
    .collect(Collectors.toList());

编译器会提示:IOException 是受检异常,而 Functionapply 方法没有声明抛出任何异常。

内部 try-catch(推荐简单场景)

最直接的方式是在 Lambda 内部捕获并处理异常。这适合能够就地处理异常的场景。

java 复制代码
// 方案1:内部捕获,转换为运行时异常
files.stream()
    .map(file -> {
        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
            return br.readLine().toUpperCase();
        } catch (IOException e) {
            // 根据业务决定是抛运行时异常还是返回默认值
            throw new RuntimeException("读取文件失败: " + file, e);
        }
    })
    .collect(Collectors.toList());

优点 :简单直接,无需额外定义接口。
缺点:代码臃肿,原本一行 Lambda 被 try-catch 撑到了四行;而且检查异常被包装成了运行时异常,调用方无法感知原始异常类型。

自定义抛出异常的函数式接口

如果想保持 Lambda 的简洁性,并保留受检异常的类型信息,可以自定义允许抛出异常的函数式接口。

java 复制代码
// 定义允许抛出异常的 Function 接口
@FunctionalInterface
interface ThrowingFunction<T, R> {
    R apply(T t) throws Exception;
}

直接使用这个接口:

java 复制代码
ThrowingFunction<File, String> readFirstLine = file -> {
    try (BufferedReader br = new BufferedReader(new FileReader(file))) {
        return br.readLine().toUpperCase();
    }
};

// 调用时需要处理 Exception
String result = readFirstLine.apply(new File("a.txt"));

但这只能在显式调用时使用,不能直接传给 Stream 的 map (因为 map 要求标准的 Function)。

通用包装器工具类(工程实践首选)

编写一个工具方法,将抛异常的 Lambda 包装成标准接口,并在包装层统一处理异常策略。

java 复制代码
public class LambdaExceptionUtils {

    @FunctionalInterface
    public interface ThrowingFunction<T, R> {
        R apply(T t) throws Exception;
    }

    /**
     * 将抛出受检异常的 Function 包装为标准 Function
     * @param throwingFunction 原始抛异常的函数
     * @param <T> 输入类型
     * @param <R> 返回类型
     * @return 标准 Function,异常时抛出 RuntimeException
     */
    public static <T, R> Function<T, R> wrap(ThrowingFunction<T, R> throwingFunction) {
        return t -> {
            try {
                return throwingFunction.apply(t);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
    }
}

使用示例

java 复制代码
List<File> files = Arrays.asList(new File("a.txt"), new File("b.txt"));

List<String> firstLines = files.stream()
    .map(LambdaExceptionUtils.wrap(file -> {
        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
            return br.readLine().toUpperCase();
        }
    }))
    .collect(Collectors.toList());

进阶版:支持多种异常策略

java 复制代码
public class LambdaExceptionUtils {
    
    // 策略1:抛出运行时异常
    public static <T, R> Function<T, R> wrapRuntime(ThrowingFunction<T, R> f) {
        return t -> {
            try { return f.apply(t); } 
            catch (Exception e) { throw new RuntimeException(e); }
        };
    }
    
    // 策略2:异常时返回默认值
    public static <T, R> Function<T, R> wrapWithDefault(ThrowingFunction<T, R> f, R defaultValue) {
        return t -> {
            try { return f.apply(t); } 
            catch (Exception e) { 
                System.err.println("操作失败,返回默认值: " + e.getMessage());
                return defaultValue; 
            }
        };
    }
    
    // 策略3:异常时返回 Optional
    public static <T, R> Function<T, Optional<R>> wrapOptional(ThrowingFunction<T, R> f) {
        return t -> {
            try { return Optional.ofNullable(f.apply(t)); } 
            catch (Exception e) { return Optional.empty(); }
        };
    }
}

笔试面试高频题

Lambda 表达式基础语法

java 复制代码
// ①
() -> {}

// ②
x -> return x * 2;

// ③
(x, y) -> { x + y; }

// ④
(String s) -> s.length()

// ⑤
x, y -> x + y

// ⑥
() -> { return 42; }

答案

java 复制代码
// ① ✅ 正确,无参无返回值
() -> {}

// ② ❌ 错误,单表达式不能用 return,应为 x -> x * 2
x -> return x * 2;

// ③ ❌ 错误,语句块中需要 return,应为 (x, y) -> { return x + y; }
(x, y) -> { x + y; }

// ④ ✅ 正确,单参带类型声明,单表达式自动返回
(String s) -> s.length()

// ⑤ ❌ 错误,多参必须加括号,应为 (x, y) -> x + y
x, y -> x + y

// ⑥ ✅ 正确,语句块中显式 return
() -> { return 42; }

写出以下 Lambda 表达式等价的匿名内部类

java 复制代码
Runnable r = () -> System.out.println("Hello");
Comparator<String> c = (s1, s2) -> s1.length() - s2.length();

答案

java 复制代码
// Runnable 匿名内部类
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
};

// Comparator 匿名内部类
Comparator<String> c = new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
};

函数式接口识别⭐

java 复制代码
// A
interface A {
    void doSomething();
}

// B
interface B {
    void method1();
    void method2();
}

// C
interface C {
    void method();
    default void defaultMethod() {}
    static void staticMethod() {}
}

// D
interface D {
    boolean equals(Object obj);
    void execute();
}

// E
@FunctionalInterface
interface E {
    void run();
    // void stop();  // 取消注释会编译报错
}

A :✅ 只有一个抽象方法
B :❌ 两个抽象方法
C :✅ 只有一个抽象方法,default 和 static 方法不计数
D :❌ equals 是 Object 的方法,不计数,但 execute 算一个,所以是函数式接口?等一等 ------equals 来自 Object,不计入抽象方法数,所以 D 只有一个抽象方法 execute是函数式接口
E :✅ 有 @FunctionalInterface 且只有一个抽象方法

关键知识点 :函数式接口 = 只有一个抽象方法的接口。Object 类的 public 方法(equals、hashCode、toString 等)不计入。

变量捕获题⭐

以下代码能否编译通过?如果不能,说明原因。

java 复制代码
public class CaptureTest {
    public void test() {
        int a = 10;
        Runnable r1 = () -> System.out.println(a);
        
        int b = 20;
        b = 30;
        Runnable r2 = () -> System.out.println(b);
        
        int c = 40;
        Runnable r3 = () -> {
            // c++;  // 取消注释会怎样?
            System.out.println(c);
        };
    }
}

r1:✅ 编译通过,变量 a 是 effectively final
r2:❌ 编译报错,b 被重新赋值,不是 effectively final
r3:如果取消注释 c++,❌ 编译报错,不能修改局部变量

核心考点 :Lambda 捕获的局部变量必须是 effectively final(初始化后不再修改)。

this识别⭐

写出以下代码的输出结果。

java 复制代码
public class ThisTest {
    private String name = "Outer";
    
    public void test() {
        // 匿名内部类
        new Thread(new Runnable() {
            private String name = "Inner";
            @Override
            public void run() {
                System.out.println(this.name);
                System.out.println(ThisTest.this.name);
            }
        }).start();
        
        // Lambda
        new Thread(() -> {
            System.out.println(this.name);
        }).start();
    }
    
    public static void main(String[] args) {
        new ThisTest().test();
    }
}

答案

text 复制代码
Inner
Outer
Outer

匿名内部类中,this 指向内部类实例,所以 this.name 输出 "Inner"

匿名内部类中,ThisTest.this 指向外部类实例,输出 "Outer"

Lambda 中,this 直接指向外部类实例,输出 "Outer"

核心考点 :Lambda 中的 this 指向外层实例,匿名内部类的 this 指向自身。

Lambda 表达式和匿名内部类有什么区别?⭐

区别点 Lambda 匿名内部类
实现机制 通过 invokedynamic 动态生成,不产生独立 .class 文件 编译后生成 Outer$1.class 文件
this 指向 指向外层类实例 指向匿名内部类自身实例
变量作用域 不能定义与外部同名的局部变量(无遮蔽) 可以定义成员变量,会遮蔽外部同名变量
适用范围 只能是函数式接口 任意接口或抽象类
代码简洁度 极简 模板代码多
性能 通常优于匿名内部类(无类加载开销) 有类加载和实例化开销

为什么 Lambda 中引用的局部变量必须是 final 或 effectively final?⭐

主要原因是线程安全内存可见性

  1. 局部变量生命周期:局部变量存储在栈上,方法执行完就销毁。Lambda 可能被传递到其他线程异步执行,此时原方法的栈帧已销毁,变量不存在了。
  2. 实现机制 :Lambda 实际上是对捕获变量的值进行复制,而非引用。如果不强制 final,会出现"改了值但 Lambda 看不到"或"Lambda 改了值外面看不到"的混乱情况。
  3. 并发安全:强制不可变避免了多线程环境下的数据竞争问题,符合函数式编程的不可变理念。

Stream + Lambda 综合题

给定 List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),用一行 Stream 实现:

  • ① 求所有偶数的平方和
  • ② 找出所有大于 5 的奇数,按降序排列
  • ③ 将列表按奇偶分组

答案

java 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// ① 所有偶数的平方和
int sum = numbers.stream()
    .filter(n -> n % 2 == 0)
    .map(n -> n * n)
    .reduce(0, Integer::sum);
// 或者 .mapToInt(n -> n * n).sum()

// ② 大于5的奇数,降序排列
List<Integer> result = numbers.stream()
    .filter(n -> n > 5 && n % 2 == 1)
    .sorted(Comparator.reverseOrder())
    .collect(Collectors.toList());

// ③ 按奇偶分组
Map<Boolean, List<Integer>> groups = numbers.stream()
    .collect(Collectors.partitioningBy(n -> n % 2 == 0));

给定 List<String> words = Arrays.asList("apple", "banana", "cat", "dog", "elephant"),用 Stream 实现:

  • ① 统计每个单词的长度,输出 Map<String, Integer>
  • ② 找出最长的单词(如果有多个取第一个)
  • ③ 将所有单词按首字母分组

答案

java 复制代码
List<String> words = Arrays.asList("apple", "banana", "cat", "dog", "elephant");

// ① 单词 -> 长度
Map<String, Integer> lengthMap = words.stream()
    .collect(Collectors.toMap(Function.identity(), String::length));

// ② 最长的单词
String longest = words.stream()
    .max(Comparator.comparing(String::length))
    .orElse(null);

// ③ 按首字母分组
Map<Character, List<String>> groupByFirst = words.stream()
    .collect(Collectors.groupingBy(s -> s.charAt(0)));
相关推荐
Irene19912 小时前
配置 PyCharm(汉化版操作指南)
python·pycharm
来自远方的老作者2 小时前
第9章 函数-9.7 函数嵌套
开发语言·python·函数·函数嵌套
7年前端辞职转AI2 小时前
Python 错误和异常处理
python·编程语言
7年前端辞职转AI2 小时前
Python 面向对象编程
python·编程语言
隔山打牛牛2 小时前
Spring的两大核心
java·开发语言
皮卡蛋炒饭.2 小时前
Linux进程信号
开发语言·c++
kishu_iOS&AI2 小时前
机器学习 —— 总结
人工智能·python·机器学习·线性回归
Elastic 中国社区官方博客2 小时前
用于 IntelliJ IDEA 的新 ES|QL 插件
java·大数据·数据库·ide·elasticsearch·搜索引擎·intellij-idea
API快乐传递者2 小时前
Python 爬虫获取 1688 商品详情 API 接口实战指南
java·前端·python