
Java Lambda 表达式入门怎么写、怎么用、常用方法有哪些 先说结论Lambda 表达式就是一个匿名函数的简写。// 以前要写一个完整的匿名内部类RunnablernewRunnable(){Overridepublicvoidrun(){System.out.println(干活);}};// 现在一行搞定Runnabler()-System.out.println(干活);它只能用在函数式接口上——就是只有一个抽象方法的接口。原理一句话说完Java 8 之后Lambda 编译后不再生成独立的匿名类而是用invokedynamic指令在运行时动态生成。你不需要懂这个会用就行。一、Lambda 的写法5 种形式1.1 五种写法一次看懂// ① 无参数SupplierStrings()-hello;// ② 一个参数括号可以省略FunctionString,Integerlenstr-str.length();// ③ 多个参数BinaryOperatorIntegeradd(a,b)-ab;// ④ 带参数类型一般省略编译器能推断BiFunctionInteger,Integer,Integermul(Integera,Integerb)-a*b;// ⑤ 代码块多条语句要用大括号 returnFunctionInteger,Stringblockn-{if(n10)return大;return小;};实测无参: hello 单参: 5 多参: 7 带类型: 12 代码块: 大 / 小1.2 语法速记情况写法无参数() - ...1 个参数x - ...括号可省2 个以上参数(a, b) - ...括号必须有单行表达式x - x 1不用return多行代码x - { ...; return ...; }必须return记住一个规则就够了只有一行时大括号和return都能省多行时必须写大括号有返回值就要写return。二、函数式接口Lambda 能用的前提Lambda 不能随便赋给任何类型只能是函数式接口——有且仅有一个抽象方法的接口。2.1 JDK 内置的四大接口必须记住接口签名干什么例子FunctionT,RR apply(T)转换T→Rs - s.length()ConsumerTvoid accept(T)消费只进不出s - System.out.println(s)SupplierTT get()供给只出不进() - helloPredicateTboolean test(T)判断返回布尔n - n 0按有没有进、有没有出记Function : 有进有出转换 Consumer : 有进没出消费 Supplier : 没进有出供给 Predicate: 有进有布尔判断2.2 常用变体接口说明BiFunctionT,U,R两个参数的 FunctionUnaryOperatorTT → T同类型转换BinaryOperatorT(T,T) → T两个同类型返回同类型IntFunctionR/ToIntFunctionT避免装箱的原始类型版本BiConsumerT,U两个参数的 Consumer为什么要有IntFunction这些因为泛型不能是基本类型FunctionInteger,Integer每次调用都要装箱拆箱。特化版直接操作int没开销。这也是 Stream 里mapToInt存在的原因。三、方法引用Lambda 的进一步简写当 Lambda 体只是调用一个已有方法时可以再简化成::。3.1 四种形式// ① 静态方法引用类::静态方法FunctionString,IntegerparseInteger::parseInt;// 等价于 s - Integer.parseInt(s)// ② 绑定对象的实例方法对象::方法Stringprefix用户-;FunctionString,Stringconcatprefix::concat;// 等价于 s - prefix.concat(s)// ③ 类::实例方法第一个参数作为调用者FunctionString,StringupperString::toUpperCase;// 等价于 s - s.toUpperCase()// ④ 构造器引用类::newSupplierListStringlistFactoryArrayList::new;// 等价于 () - new ArrayList()实测静态: Integer::parseInt(100) 100 绑定对象: prefix::concat(A) 用户-A 类::实例: String::toUpperCase(abc) ABC 构造器: ArrayList::new 创建了 list, size03.2 什么时候用方法引用判断方法Lambda 体是不是直接把参数传给某个方法是的话就能换。// ❌ 能用方法引用却写了 lambdanames.forEach(n-System.out.println(n));// ✅ 用方法引用names.forEach(System.out::println);写法等价s - System.out.println(s)System.out::printlns - s.length()String::length() - new User()User::newu - u.getName()User::getName不用强求。方法引用只是更简洁lambda 写法同样正确团队里统一就行。四、Stream 常用操作Lambda 的主战场Lambda 最有价值的地方就是配合 Stream 处理集合。4.1 准备数据recordUser(Stringname,intage,Stringcity){}ListUserusersList.of(newUser(张三,28,北京),newUser(李四,22,上海),newUser(王五,35,北京),newUser(赵六,19,深圳));4.2 八个最常用的操作// ① filter过滤ListUseradultsusers.stream().filter(u-u.age()25).toList();// ② map转换取某个字段ListStringnamesusers.stream().map(User::name).toList();// ③ sorted排序ListUserbyAgeusers.stream().sorted(Comparator.comparingInt(User::age)).toList();// ④ 统计sum / max / min / averageinttotalAgeusers.stream().mapToInt(User::age).sum();// ⑤ groupingBy分组相当于 SQL 的 GROUP BYMapString,ListUserbyCityusers.stream().collect(Collectors.groupingBy(User::city));// ⑥ distinct去重longcityCountusers.stream().map(User::city).distinct().count();// ⑦ anyMatch / allMatch判断booleanhasYoungusers.stream().anyMatch(u-u.age()20);// ⑧ collect收集成其他结构MapString,IntegernameToAgeusers.stream().collect(Collectors.toMap(User::name,User::age));实测输出filtermap: 北京的用户 [张三, 王五] sorted(按年龄): [赵六, 李四, 张三, 王五] sum104, average26.00 groupingBy: {上海[李四], 北京[张三, 王五], 深圳[赵六]} distinct: 不同城市数 34.3 Stream 的三个坑坑一没有终止操作什么都不会执行// ❌ 不打印任何东西因为缺终止操作users.stream().filter(u-{System.out.println(过滤 u.name());returntrue;});// ✅ 加上 toList() / count() / forEach() 才会执行users.stream().filter(...).toList();Stream 是惰性的filter、map这些只是记下要做什么必须有终止操作toList、count、forEach、sum才真正跑。坑二Collectors.toMap遇到重复 key 会抛异常// ❌ key 重复 → IllegalStateException: Duplicate key.collect(Collectors.toMap(User::city,User::name));// ✅ 提供冲突策略.collect(Collectors.toMap(User::city,User::name,(oldV,newV)-newV));坑三parallelStream()不是万能加速// ⚠️ 数据量小、有共享状态、有 IO 时别用users.parallelStream().filter(...)什么时候能用并行流数据量大上万条 纯计算 无共享状态。其他情况用了反而更慢。五、一个必须知道的小坑变量捕获Lambda 里能用外部变量但那个变量必须是effectively final事实不变。5.1 什么算 effectively finalintbase100;// 声明后没改过 → 可以IntUnaryOperatorfx-xbase;System.out.println(f.applyAsInt(5));// 105实测捕获 base100f(5)105但如果改了就不行intcount0;count;// ❌ 改了Runnabler()-System.out.println(count);// 编译报错报错信息Local variable count defined in an enclosing scope must be final or effectively final5.2 为什么有这个限制因为Lambda 捕获的是变量的值副本不是变量本身。局部变量在方法结束后就没了而 Lambda 可能在之后才执行所以必须在创建时拷贝一份。如果允许外部改就会出现副本和本体不一致的混乱——所以 Java 直接禁止。解决办法要么别改要么用final数组 / 对象的字段它们不是栈上的局部变量int[]counter{0};// 数组元素可以改Runnabler()-counter[0];六、面试常问的 6 个问题1. Lambda 表达式是什么答匿名函数的简写用来实现函数式接口。核心作用是把行为当参数传递代码更简洁。原理上Java 8 用invokedynamic在运行时动态生成实现不产生独立的匿名类文件。2. 什么是函数式接口答有且仅有一个抽象方法的接口用FunctionalInterface标注该注解只做编译期检查。default方法、static方法、Object的 public 方法都不计入抽象方法数量。JDK 四大核心接口Function/Consumer/Supplier/Predicate。3. 方法引用有几种形式答四种——① 静态方法Integer::parseInt② 绑定对象的实例方法prefix::concat③ 类的实例方法String::toUpperCase第一个参数作为调用者④ 构造器ArrayList::new。4. Lambda 里的this指向什么答指向外部类实例因为 Lambda 不引入新作用域函数体逻辑上长在外部类里。和匿名内部类不同——匿名内部类的this指向匿名类自己。5. 为什么 Lambda 引用的外部变量必须是 effectively final答因为 Lambda捕获的是变量的值副本不是变量本身。局部变量存在栈上方法返回后就销毁了而 Lambda 可能之后才执行所以必须创建时拷贝。若允许外部修改副本和本体就会不一致因此编译期强制要求声明后未被修改过。6. Stream 的map和flatMap有什么区别答map是一对一转换StreamT→StreamRflatMap是一对多后展平StreamT→StreamStreamR→StreamR。比如一篇文章拆成多个单词map会得到ListListStringflatMap得到ListString。七、总结六件事记住就够了是什么—— 匿名函数的简写只能用在函数式接口上。怎么写——() - ...单行省return多行要写大括号。四大接口——Function转换、Consumer消费、Supplier供给、Predicate判断。方法引用——类::方法/对象::方法/类::news - System.out.println(s)可换成System.out::println。配合 Stream——filter/map/sorted/groupingBy/toList是最常用的五个别忘了终止操作。一个限制—— 引用的外部变量必须是 effectively final没被改过。一句话Lambda 的价值是把行为变成参数——这也是 Stream 能做到filter(u - u.age() 18)这种声明式写法的根本原因。如果本文对你有帮助欢迎点赞 收藏 关注如果疑问或补充欢迎在评论区交流关注我后续分享更多 Java 核心知识点和实战技巧