资讯动态

方法引用四种形态::: 到底省了什么?面试必考简写全家桶

发布时间:2026/10/24 5:40:54 来源:尧图企业网站定制
系列Lambda 函数式实战精通营 · 作者鱼宵配套课程仓库https://gitee.com/j67mk2/lambda-journey 第 3 课先看一个每天都在发生的现场你刚学会 lambda把排序从匿名类改成了(a, b) - Double.compare(...)正美滋滋。结果 review 代码时组长说“这个 lambda 的体里就调了一个现成方法为什么不直接写Integer::parseInt”你看着Integer::parseInt这个两个冒号的写法一脸懵它和s - Integer.parseInt(s)到底等不等价为什么有的是对象::方法有的是类名::方法为什么还有ArrayList::new这种把 new 递出去的写法这不是语法炫技——方法引用是面试必考点也是你把 lambda 写像样的分水岭。本文把四种形态一次讲透。学完本文你能回答方法引用有哪四种形态什么 lambda 能用::简写、什么不行构造器引用ClassName::new到底是怎么 new 出对象的一句话原理lambda 体只干一件事——调一个已有方法——就能用 ::先摆结论方法引用method reference是 lambda 的极简写法当 lambda 体里只调用一个已经存在的方法时把整个 lambda 简写成类名::方法名或对象::方法名四种形态静态方法、绑定对象的实例方法、类名实例方法最绕的一种、构造器能用::的条件有两个lambda 体里只有一个方法调用且这个方法的参数列表和函数式接口抽象方法的签名对得上。类比快递面单上的按原地址退回。你不用把地址再抄一遍写个代号退回就行——仓库编译器知道这个代号对应哪条已有规则。::就是这个代号。完整可运行代码四种形态并排对比直接复制就能跑JDK 21packagecom.lambda.lesson03;importjava.util.ArrayList;importjava.util.List;importjava.util.function.Function;importjava.util.function.Supplier;publicclassMain{publicstaticvoidmain(String[]args){System.out.println( 形态一静态方法引用Integer::parseInt );// lambda 版参数 s 原样传给静态方法 Integer.parseIntFunctionString,IntegerparseLambdas-Integer.parseInt(s);// 方法引用版把静态方法整个递进去——签名对得上编译器认得FunctionString,IntegerparseRefInteger::parseInt;System.out.println(lambda 版 \123\ → parseLambda.apply(123));System.out.println(::版 \123\ → parseRef.apply(123));System.out.println(两版结果一样parseLambda.apply(456).equals(parseRef.apply(456)));System.out.println();System.out.println( 形态二实例方法引用 · 绑定对象greeting::toUpperCase );// 接收者是一个已经存在的对象greeting调用它的 toUpperCase()不再传参Stringgreetinghello world;SupplierStringupperLambda()-greeting.toUpperCase();SupplierStringupperRefgreeting::toUpperCase;// :: 左边是对象不是类System.out.println(lambda 版 → upperLambda.get());System.out.println(::版 → upperRef.get());System.out.println(两版结果一样upperLambda.get().equals(upperRef.get()));System.out.println();System.out.println( 形态三实例方法引用 · 类名String::length );// 接收者还没定——第一个传进来的参数 s 就是接收者s.length()// 这是最容易晕的一种:: 左边写的是类名但调的是实例方法FunctionString,IntegerlenLambdas-s.length();FunctionString,IntegerlenRefString::length;System.out.println(lambda 版 \apple\ 长度 → lenLambda.apply(apple));System.out.println(::版 \banana\ 长度 → lenRef.apply(banana));System.out.println(两版结果一样lenLambda.apply(cherry).equals(lenRef.apply(cherry)));System.out.println();System.out.println( 形态四构造器引用ArrayList::new );// lambda 里写 () - new ArrayList()太啰嗦——直接把new 这个动作递进去SupplierArrayListStringlistLambda()-newArrayList();SupplierArrayListStringlistRefArrayList::new;ArrayListStringlist1listLambda.get();ArrayListStringlist2listRef.get();list1.add(lambda 造的);list2.add(:: 造的);System.out.println(lambda 版造出的集合list1);System.out.println(::版 造出的集合list2);System.out.println(两个都是 ArrayList(list1instanceofArrayListlist2instanceofArrayList));System.out.println();System.out.println( 综合方法引用给名单加工lambda 与 :: 并排 );ListStringnamesList.of(tom,jerry,spike);FunctionString,StringupperString::toUpperCase;// 形态三类名::实例方法FunctionString,Stringquotes-「s」;// 普通 lambda不是调现成方法不能用 ::System.out.println(名单加工结果);for(Stringname:names){System.out.println( quote.apply(upper.apply(name)));}System.out.println(String::toUpperCase 是形态三quote 那行因为多包了一层「」没法直接 ::);}}真实运行输出本机 JDK 24.0.1 实测 形态一静态方法引用Integer::parseInt lambda 版 123 → 123 ::版 123 → 123 两版结果一样true 形态二实例方法引用 · 绑定对象greeting::toUpperCase lambda 版 → HELLO WORLD ::版 → HELLO WORLD 两版结果一样true 形态三实例方法引用 · 类名String::length lambda 版 apple 长度 → 5 ::版 banana 长度 → 6 两版结果一样true 形态四构造器引用ArrayList::new lambda 版造出的集合[lambda 造的] ::版 造出的集合[:: 造的] 两个都是 ArrayListtrue 综合方法引用给名单加工lambda 与 :: 并排 名单加工结果 「TOM」 「JERRY」 「SPIKE」 String::toUpperCase 是形态三quote 那行因为多包了一层「」没法直接 ::这段代码讲什么形态一静态方法引用Integer::parseIntFunctionString,IntegerparseLambdas-Integer.parseInt(s);FunctionString,IntegerparseRefInteger::parseInt;lambda 体就是把参数原样传给静态方法parseInt于是能简写不写会怎样不简写就得每次都写s - Integer.parseInt(s)一个长串::把参数搬运这步全省了编译器知道要把 apply 的参数喂给 parseInt。形态二绑定对象的实例方法greeting::toUpperCaseSupplierStringupperRefgreeting::toUpperCase;// :: 左边是对象::左边是对象greeting不是类。因为接收者已经钉死就是这个字符串调用时不再需要传参所以配的是无参的Supplier你天天用的System.out::println也是形态二——System.out是一个对象println是它的方法。形态三类名实例方法String::length最容易晕的一种FunctionString,IntegerlenRefString::length;// lenRef.apply(banana) banana.length() 6::左边写的是类名String但length()明明是实例方法——编译器的约定是第一个参数当接收者为什么要绕这一层因为调用方手里还没有对象对象是调用 apply 那一刻才传进来的只能说等参数来了拿它调 length记忆法左边是值对象就是绑定左边是类就是未绑定/静态——形态二greeting::toUpperCase和形态三String::toUpperCase就差左边一个对象 vs 一个类名。形态四构造器引用ArrayList::newSupplierArrayListStringlistRefArrayList::new;// 等价于 () - new ArrayList()new也是一种方法只是没有名字::new把它递出去不写会怎样每个要造空列表的地方都手写() - new ArrayList()用ArrayList::new直接把工厂递出去配合stream.collect(Collectors.toCollection(ArrayList::new))这种写法后面课会见到特别顺这是工厂模式你把造对象这个动作当成参数递出去谁拿到它谁就能get()出一个新对象却不用知道具体调用哪个构造器。什么时候不能用 ::分界线FunctionString,StringupperString::toUpperCase;// 现成方法 签名对上 → 能用 ::FunctionString,Stringquotes-「s」;// 多包了一层「」 → 不能用 ::条件一lambda 体里只有一个方法调用——quote里有拼接操作不是调一个现成方法条件二签名对得上——FunctionString, Integer f String::valueOf;这种valueOf收 Object 返回 String和apply(String)-Integer对不上编译报错判别口诀把::左边念成谁是类名还是对象右边念成干什么再看接口要的参数和它对不对得上三秒判型。类比插座形状。你家插头lambda 体必须刚好插进墙插接口签名一个孔都不能多。想加个打印前先换行那就插不进去了老老实实写 lambda。 进阶深挖选学构造器引用还能带参数FunctionInteger, int[] arrayNew int[]::new;——arrayNew.apply(5)造出一个长度 5 的数组。JDK 里Stream.toArray(int[]::new)就是这么用的。方法引用的底层和 lambda 一样走invokedynamicInteger::parseInt不会每次调用都生成新对象和第 9 课lambda 复用直接相关它本质是对已有方法的再贴一层函数式接口标签。this::方法名和super::方法名绑定对象形态的变体this::handler就是绑定当前对象在把本类方法当回调传出去时常用。面试回答模板问方法引用有哪几种一句话结论四种——静态方法、绑定对象的实例方法、类名实例方法、构造器引用。展开 1~2 句Integer::parseInt等价s - Integer.parseInt(s)String::length把第一个参数当接收者lenRef.apply(banana)banana.length()ArrayList::new等价() - new ArrayList()。收一句日常写代码时只要 lambda 体只是调一个现成方法且签名对得上就用::代码更短、更像把方法当参数传递指向课程 lesson-03 §8。问什么 lambda 不能改写成方法引用一句话结论lambda 体里不止一个方法调用、或者签名对不上的都不能用::。展开比如s - 「 s.trim() 」里有两个操作trim 拼字符串只能用String::trim替掉s - s.trim()这一句替不掉整个表达式指向课程 lesson-03 §7。总结表坑 → 原因 → 解法坑原因解法形态二和形态三看反greeting::toUpperCase左边是对象String::toUpperCase左边是类名记忆“左边是值就是绑定左边是类就是未绑定/静态”签名对不上硬写::FunctionString,Integer f String::valueOf类型不匹配先默念接口抽象方法要什么签名再看方法引用是否刚好匹配以为 lambda 体能塞别的逻辑s - 「 s.trim() 」有两个操作::只能替调一个现成方法那一句构造器引用配错接口FunctionInteger, ArrayListString f ArrayList::new要求有收 int 的构造器构造器引用匹配有对应参数列表的构造器JDK 低于 21版本不满足先java -version确认 21挑战题答案都在课程源码里⭐ 把静态方法引用换成Long::parseLong配FunctionString, Long跑一下看结果。⭐⭐ 把greeting::toUpperCase改成绑定你自己的对象——比如一句中文我是方法引用看看大写转换对中文是什么效果。⭐⭐ 故意写FunctionString, String bad s - 「 s 」;然后硬改成String::toUpperCase之外的::写法读一读编译器报错信息。⭐⭐⭐ 用构造器引用把造一个 HashMap递出去Supplierjava.util.HashMapString, Integer mapSupplier HashMap::new;塞 3 个键值对再打印。⭐⭐⭐ 给名单加工加一步FunctionString, String trim用String::trim形态三插在upper之前——先去空白、再变大写、再包「」。源码在哪怎么跑完整代码在lambda-journey 课程第 3 课gitclone https://gitee.com/j67mk2/lambda-journey.gitcdlambda-journey/lesson-03 mvn-qcleaninstall-DskipTestsjava-cptarget/classes com.lambda.lesson03.Main第 3 课方法引用四形态 构造器引用全解 5 道面试题 课后作业 5 道本文属于Lambda 函数式实战精通营系列10 课L1 世界观 → L2 四大接口 →L3 方法引用→ L4 闭包 → L5 异常 → L6 重构 → L7/L8 Lambda×Stream 组合 → L9 性能 → L10 企业级收官。下一篇闭包与变量捕获——lambda 里能抓住外面的变量吗为什么必须是 effectively final跑出来的输出和我不一样把现象发评论区一起查。觉得有用就收藏一下追更这个系列。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑