跨Maven依赖Java项目中类不可见时函数式接口仍可访问变量的原因
原理说明
1. 编译期约束的差异
- 项目B直接创建obj1所属类的实例触发编译错误,是因为编译阶段项目B的编译类路径下没有该类的定义,编译器找不到类的元信息,直接引用类名会触发类检查失败。
- 调用
Function#apply能正常编译的核心原因是:你传递的func变量的类型是JDK标准的java.util.function.Function<String, InputStream>,这个接口对项目A、B都完全可见。项目B的方法接收的参数是Function类型,编译时只需要校验apply方法的签名和接口定义一致即可,完全不需要感知Function实例的具体实现逻辑,也不需要知道实现内部依赖了哪些类。
2. Lambda表达式的编译实现
Java编译lambda表达式时,会在**定义lambda的类(也就是项目A的Class2)**中生成对应的私有合成方法:
- 你写的lambda
str -> obj1.get(str)会被编译成Class2的一个私有方法,逻辑和lambda完全一致,方法内部直接引用obj1所属的类,由于Class2属于项目A,编译期可以正常找到该类的定义,所以项目A的编译不会报错。 - 同时会通过
invokedynamic指令动态生成一个实现了Function接口的匿名类实例,该实例持有捕获到的obj1的引用,apply方法的实现就是调用前面生成的Class2的私有方法。
3. 运行期的执行逻辑
你把func实例传递到项目B后,B调用apply的过程是标准的接口多态调用:
- JVM只需要校验该
Function实例确实存在符合签名的apply方法,直接调用即可。 - 实际执行
apply时,逻辑会跳转到项目A的Class2中预先生成的私有方法里执行,该方法的运行上下文是项目A的类加载器,自然可以正常访问obj1所属的类,不会出现类找不到的问题。
本质上这和你手动写一个实现了
Function接口的类,在实现类的apply方法里调用obj1,再把实现类的实例传给B是完全一样的逻辑,lambda只是语法糖简化了这个过程而已。B只依赖Function接口,不依赖你的实现类,自然不需要感知实现类内部的依赖。
内容的提问来源于stack exchange,提问作者Kevin
相关产品推荐
相关产品推荐

