bigywater.tech
Java 内部原理 · 85

方法引用四种形式真实验证:和lambda底层都是同一套 invokedynamic 机制

方法引用(::)有四种写法,大多数人只记得"能代替lambda"这个用法,这篇真实把四种形式各自跑一遍,再用反射看它们和lambda在运行时的类名,验证它们到底是不是"同一套东西"。

四种形式真实计算结果一致
真实验证:静态方法、特定对象、任意对象、构造器四种引用形式,和等价lambda表达式的计算结果真实完全一致
运行时类名真实都含Lambda
真实验证:f1(方法引用)和lambda1(lambda表达式)的getClass().getName()真实都带有Lambda字样,证明两者共享同一套运行时生成机制

上一篇:双亲委派真实验证

真实验证:四种方法引用形式都能正常工作

静态方法引用、特定对象的实例方法引用、任意对象的实例方法引用、构造器引用——依次真实跑一遍。

MethodReferenceDemo.java Function<Integer, Integer> f1 = MethodReferenceDemo::staticDouble; // 静态方法引用 Function<Integer, Integer> f2 = instance::instanceTriple; // 特定对象的实例方法引用 Function<String, Integer> f3 = String::length; // 任意对象的实例方法引用 Function<String, Person> f4 = Person::new; // 构造器引用
真实实测
静态方法引用 f1.apply(5) = 10
特定对象方法引用 f2.apply(5) = 15
任意对象方法引用 f3.apply("hello") = 5
构造器引用 f4.apply("Alice") = Person(Alice)

真实结果验证了四种形式各自的语义:静态方法引用 直接对应一个静态方法调用;特定对象的实例方法引用 真实绑定到了一个具体实例(instance),调用时不需要再传入调用者;任意对象的实例方法引用(String::length)真实把函数式接口的第一个参数当作方法的调用者,f3.apply("hello") 真实等价于 "hello".length();构造器引用 真实等价于一个"专门用来 new 对象"的函数。

真实验证:方法引用和等价lambda共享同一套运行时机制

用反射查看方法引用和手写lambda生成的对象,运行时类名到底长什么样。

MethodReferenceDemo.java Function<Integer, Integer> f1 = MethodReferenceDemo::staticDouble; Function<Integer, Integer> lambda1 = x -> staticDouble(x); System.out.println(f1.getClass().getName()); System.out.println(lambda1.getClass().getName());
真实实测
方法引用与等价lambda结果是否一致: true
f1 的运行时类型: MethodReferenceDemo$$Lambda$1/0x0000007001000a10
lambda1 的运行时类型: MethodReferenceDemo$$Lambda$5/0x0000007001004000
两者运行时类名是否都含Lambda字样: true
真实证据说明了什么。两个运行时类名都真实带有 $$Lambda$ 这个特征字样,只是编号($1、$5)不同——这证明方法引用不是编译器在语法层面把 MethodReferenceDemo::staticDouble 简单转写成一个手写的匿名内部类,而是和 lambda 表达式走了完全相同的底层实现路径:编译期生成一条 invokedynamic 指令,运行时首次执行到这行代码时,由 LambdaMetafactory 动态生成一个实现了目标函数式接口(这里是 Function)的类,这个类的名字真实是运行时按需生成的,不在源代码编译产物里以具名 .class 文件的形式存在。这也是为什么方法引用能享受和 lambda 完全一样的性能特性(比传统匿名内部类更轻量,首次调用有一次性的启动开销,之后接近直接调用)。

真实验证:方法引用在真实场景里的典型用法

补充验证两个常见的实际使用场景:排序用的Comparator,以及数组构造器引用。

真实实测
用方法引用Comparator排序后: [Person(Alice), Person(Bob), Person(Charlie)]
数组构造器引用 int[]::new 创建长度: 5

真实结果验证了 Comparator.comparing(Person::getName) 这种"任意对象方法引用"作为排序键提取器的经典用法,以及 int[]::new 这种数组构造器引用(常用于 Stream.toArray(size -> new T[size]) 场景的简写形式)——两者都真实给出了正确结果。

分步演示:同样是 ::,四种方法引用的接收者真实来自哪里

每一行都是真实运行这段代码得到的结果。

四种方法引用真实的区别就在"接收者从哪来":写死的类、绑定的对象、参数里的第一个,还是全新创建
这一步新验证的引用形式

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,四种方法引用形式和lambda对照重复运行结果完全一致(具体的Lambda编号在不同运行间可能变化,但特征字样固定)。
    • 和VarHandle那篇一样,属于"看起来是语法糖,实际有独立运行时机制"的一类特性,值得结合字节码/反射一起验证而不是停留在表面用法。
    • 没有涉及:LambdaMetafactory的引导方法(bootstrap method)在字节码invokedynamic指令里的具体参数结构、方法引用捕获外部变量时和lambda表达式在闭包语义上是否有细微差异——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号