当前位置: 首页 > news >正文

【Java基础14】函数式接口、lamba表达式、方法引用一网打尽(下)

方法引用是 Lambda 表达式的“语法糖”。理解了它,你写的 Stream API 代码会立刻变得非常专业和简洁。

什么是方法引用?

核心思想:当你写的 Lambda 表达式的唯一工作就是去调用一个已经存在的方法时,你就可以使用“方法引用”(::)来代替这个 Lambda 表达式。

为什么需要它?

我们来看一个简单的 forEach 例子:

  • Lambda 写法

    List<String> list = Arrays.asList("a", "b", "c");
    list.stream().forEach(s -> System.out.println(s));
    
  • 分析:这个 Lambda s -> System.out.println(s) 接收一个参数 s,然后原封不动地把它传给 System.out.println 方法。s 这个变量显得有些多余,我们真正的意图是“对于流中的每个元素,都去执行 System.out.println 这个操作”。

  • 方法引用写法

    list.stream().forEach(System.out::println);
    
  • 解读System.out::println 这段代码的意思就是:“请使用 System.out 这个实例的 println 方法”。编译器会自动推断,流中的每个元素(s)都应该被当作参数传递给 println 方法。

你看,代码是不是立刻就干净了?:: 运算符用于分隔类名/对象名方法名


方法引用的四种主要类型

1. 静态方法引用 (Static Method Reference)
  • 语法ClassName::staticMethod

  • Lambda 对比(args) -> ClassName.staticMethod(args)

  • 场景:你调用的方法是一个静态方法

示例:把一个 String 列表转换为 Integer 列表。

List<String> strNums = Arrays.asList("1", "2", "3");// Lambda 写法
List<Integer> nums1 = strNums.stream().map(s -> Integer.parseInt(s)).collect(Collectors.toList());// 方法引用写法
List<Integer> nums2 = strNums.stream().map(Integer::parseInt) // parseInt 是 Integer 类的静态方法.collect(Collectors.toList());
  • 解读map 操作需要一个 Function<String, Integer>。Lambda s -> Integer.parseInt(s) 完美符合。

  • Integer::parseInt 告诉 map:“请用 Integer 类的 parseInt 静态方法来处理流中的每个元素(s)”。

2. 特定对象的实例方法引用 (Instance Method Reference of a Particular Object)
  • 语法objectInstance::instanceMethod

  • Lambda 对比(args) -> objectInstance.instanceMethod(args)

  • 场景:你调用的方法是一个已经存在的、特定的实例对象的方法。

示例:就是我们最开始的打印例子。

List<String> list = Arrays.asList("a", "b", "c");// Lambda 写法
list.stream().forEach(s -> System.out.println(s));// 方法引用写法
// System.out 是一个已经存在的实例对象 (PrintStream 类的实例)
list.stream().forEach(System.out::println);
  • 解读System.out 是一个具体的对象实例forEach 需要一个 Consumer<String>。Lambda s -> System.out.println(s) 告诉它去调用 System.out 对象的 println 方法。System.out::println 是更直接的表达。

3. 特定类型的任意对象的实例方法引用 (Instance Method Reference of an Arbitrary Object of a Particular Type)
  • (这是最常用,也是最容易混淆的一种,请重点理解)

  • 语法ClassName::instanceMethod

  • Lambda 对比(obj, args) -> obj.instanceMethod(args) 或者 (obj) -> obj.instanceMethod()

  • 场景:当 Lambda 的第一个参数成为了方法调用者时,就可以使用这种形式。

示例 1:获取所有 User 对象的名字。

class User {private String name;public String getName() { return this.name; }// ...
}
List<User> users = ... ;// Lambda 写法
List<String> names1 = users.stream().map(user -> user.getName()).collect(Collectors.toList());// 方法引用写法
List<String> names2 = users.stream().map(User::getName) // getName 是 User 类的实例方法.collect(Collectors.toList());
  • 解读

    1. map 需要一个 Function<User, String>,它的抽象方法是 String apply(User user)

    2. 我们的 Lambda 是 user -> user.getName()

    3. 注意看:Lambda 的第一个(也是唯一一个)参数 user,变成了 getName() 方法的调用者

    4. 编译器看到这个模式 (user -> user.someMethod()),就允许你简写为 User::getName。它的意思是:“对于流中的任意一个 User 对象,请调用它自己getName 方法”。

示例 2:将字符串列表转为大写。

List<String> list = Arrays.asList("a", "b", "c");// Lambda 写法
List<String> upperList1 = list.stream().map(s -> s.toUpperCase()).collect(Collectors.toList());// 方法引用写法
List<String> upperList2 = list.stream().map(String::toUpperCase).collect(Collectors.toList());
  • 解读:同理,s -> s.toUpperCase() 中,参数 s 成为了 toUpperCase() 的调用者。因此简写为 String::toUpperCase

4. 构造函数引用 (Constructor Reference)
  • 语法ClassName::new

  • Lambda 对比(args) -> new ClassName(args)

  • 场景:当你需要“生产”一个新的对象时(常用于 Supplier 接口或 Stream 的 collect)。

示例:把一个名字列表转换成 User 对象列表。

List<String> names = Arrays.asList("Alice", "Bob");// Lambda 写法
List<User> users1 = names.stream().map(name -> new User(name)) // 假设 User 有一个 User(String name) 构造函数.collect(Collectors.toList());// 方法引用写法
List<User> users2 = names.stream().map(User::new) // 编译器会自动匹配合适的构造函数.collect(Collectors.toList());
  • 解读map 需要一个 Function<String, User>。Lambda name -> new User(name) 刚好符合。编译器看到 ::new,就会自动去 User 类里寻找一个接收 String(即流中元素 name 的类型)的构造函数。

核心规则:当你的 Lambda 表达式包含了额外的逻辑时,就不能用方法引用。、

Stream 的用法“三步曲”

使用 Stream 几乎总遵循这三个步骤,像个“公式”:

  1. 创建流:把原材料(List)放到流水线上。

  2. 中间操作:在流水线上对原材料进行加工(可以有多道工序)。

  3. 终止操作:把加工好的成品打包消费(启动流水线)。

我们用一个 User 列表来举例:

// 原材料仓库
List<User> users = ... ; // 假设里面有很多 User 对象// 目标:找到所有 30 岁以上的用户,获取他们的名字,并返回一个新列表

1. 创建流 (Get the Stream)

这是第一步:告诉 Stream 你的数据源是什么。最常用的就是 list.stream()

users.stream() // 从这里开始,users 列表中的数据就被放到了流水线上

注意,这里的流不是输入输出流

对比维度I/O StreamJava 8 Stream (我们刚讲的)
中文比喻数据的管道🚰数据的流水线🏭
所属包java.iojava.util.stream
处理对象外部数据(字节或字符)内存数据(Java 对象)
来源/去向文件、网络套接字(Socket)、字节数组List, Set, Map, 数组
核心目的I/O 读写(把数据读进来/写出去)计算和转换(筛选、排序、分组)
核心操作read(), write(), close()filter(), map(), collect()
举例FileInputStream, BufferedReaderlist.stream()

2. 中间操作 (Process the Stream)

这是最核心的部分,你可以对流水线上的数据进行一道或多道“工序”。

最常用的工序有两个:

  • filter(Predicate p):筛选

    • 作用:像一个筛子,只保留你想要的。

    • Lambdauser -> user.getAge() > 30 (只保留年龄大于30的)

    • Predicate 就是我们之前说的“断言型”接口,返回 boolean

  • map(Function f):转换

    • 作用:把流水线上的东西变成另一个东西。

    • Lambdauser -> user.getName() (把 User 对象转换String 名字)

    • Function 就是“功能型”接口,有输入有输出)

特点:

  • 链式调用:你可以把很多操作串起来,比如 stream().filter(...).map(...)

  • 惰性执行:你定义这些操作时,它们并不会立即执行。它们只是在“搭建流水线”。

3. 终止操作 (End the Stream)

这是最后一步,也是真正触发流水线启动的一步。

最常用的终止操作有两个:

  • collect(Collectors.toList()):打包

    • 作用:把流水线上所有处理完的成品,收集到一个新的 List 中。

    • 这是 90% 的情况下你想要的。

  • forEach(Consumer c):消费

    • 作用:不打包,而是对流水线上的每个成品执行一个操作(比如打印)。

    • Lambdaname -> System.out.println(name)

    • Consumer 就是“消费型”接口,只进不出)

总结

到此我们讲完了函数式接口、lamba表达式、方法引用这三部分的内容,相信你对Java的语法理解有精进了一步。不知道你会不会这样想:Java本身都这么繁琐了,你还总是搞一些其他的语法来简化这种繁琐,我怎么学的过来啊!

后面我们会以重点八股的形式继续学习Java基础语法,敬请期待!

http://www.dtcms.com/a/573141.html

相关文章:

  • 金仓KES vs. 达梦DM:全面对比解析迁移、运维与授权成本
  • 国际网站如何推做推广个人做百度云下载网站吗
  • 【Python爬虫基础-1】爬虫开发基础
  • 外贸设计网站邯郸微信托管
  • 深度学习_原理和进阶_PyTorch入门(1)
  • C# 实现在 Excel 中高效生成和操作表格
  • OpenTeleDB xstore vs GaussDB ustore表膨胀测试
  • 使用 OpenAI Responses API 构建生产级应用的终极指南—— 状态、流式、异步与文件处理
  • 2025/11/5 IO流(字节流、字符流、字节缓冲流、字符缓冲流) 计算机存储规则(ASCII、GBK、Unicode)
  • 解决excel复制页面行高无法复制的问题
  • SSO登录验证设计要点细节(以微软 Microsoft SSO为例) 基于react python
  • 郑州网站备案地址移动互联网开发工程师证书
  • 网站建设的难处wordpress 臃肿
  • 芯谷科技--D29152高性能低降压可调稳压器,驱动高效电源管理新体验
  • 代码随想录第59天 | 最短路算法:dijkstra和Bellman_ford
  • web自动化测试详解
  • 网站建设文章官网小程序定制开发中心
  • PortSwigger靶场之利用开放重定向漏洞绕过过滤器的 SSRF 攻击通关秘籍
  • 深入理解 Spring 原理:IOC、AOP 与事务管理
  • 做网站公司赚钱吗怎么怎么做网站
  • 使用ESP8266+SG90舵机实现物理远程开机
  • 第四阶段C#通讯开发-5:TCP
  • WABT 项目全解析:WebAssembly 二进制工具套件
  • 第四阶段C#通讯开发-5:Socket与RS485 / Modbus联调
  • 辽宁建设资质申报网站国外直播sdk
  • 适配的 GPU 服务器能让 AI 模型充分发挥算力优势
  • 【高并发服务器:HTTP应用】十五、HttpRequest请求模块 HttpResponse响应模块设计
  • 两台服务器 NFS 共享目录实战
  • 在服务器已有目录中部署 Git 仓库
  • 宝塔Linux部署 一个基于uni-app 系统指南