Mar*_*sch 20 java lambda method-reference
我的印象Foo::new只是语法糖() -> new Foo(),他们应该表现得相同.但似乎并非如此.这是背景:
使用Java-8,我使用第三方库,它有一个Optional<Foo> foo和这个违规行:
foo.orElseGet(JCacheTimeZoneCache::new);
Run Code Online (Sandbox Code Playgroud)
JCacheTimeZoneCache在构造函数中使用来自可选JCache库的东西,我没有在我的类路径中.使用调试器我验证了foo不是null,因此它实际上应该永远不会实例化JCacheTimeZoneCache实例,因此丢失的JCache库应该不是问题.然而,它确实爆炸,堆栈跟踪抱怨丢失的JCache库:
Caused by: java.lang.BootstrapMethodError: java.lang.IllegalAccessError: no such constructor: net.fortuna.ical4j.util.JCacheTimeZoneCache.<init>()void/newInvokeSpecial
at net.fortuna.ical4j.model.TimeZoneLoader.cacheInit(TimeZoneLoader.java:275) ~[ical4j-3.0.0.jar:na]
at net.fortuna.ical4j.model.TimeZoneLoader.<init>(TimeZoneLoader.java:81) ~[ical4j-3.0.0.jar:na]
at net.fortuna.ical4j.model.TimeZoneRegistryImpl.<init>(TimeZoneRegistryImpl.java:125) ~[ical4j-3.0.0.jar:na]
at net.fortuna.ical4j.model.TimeZoneRegistryImpl.<init>(TimeZoneRegistryImpl.java:116) ~[ical4j-3.0.0.jar:na]
at net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory.createRegistry(DefaultTimeZoneRegistryFactory.java:48) ~[ical4j-3.0.0.jar:na]
at net.fortuna.ical4j.data.CalendarBuilder.<init>(CalendarBuilder.java:105) ~[ical4j-3.0.0.jar:na]
at de.malkusch.trashcollection.infrastructure.schedule.ical.VEventRepository.downloadVEvents(VEventRepository.java:46) ~[classes/:na]
at de.malkusch.trashcollection.infrastructure.schedule.ical.VEventRepository.<init>(VEventRepository.java:35) ~[classes/:na]
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_172]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_172]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_172]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_172]
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:170) ~[spring-beans-5.0.7.RELEASE.jar:5.0.7.RELEASE]
... 80 common frames omitted
Caused by: java.lang.IllegalAccessError: no such constructor: net.fortuna.ical4j.util.JCacheTimeZoneCache.<init>()void/newInvokeSpecial
at java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(MethodHandleNatives.java:483) ~[na:1.8.0_172]
... 93 common frames omitted
Caused by: java.lang.NoClassDefFoundError: javax/cache/configuration/Configuration
at java.lang.invoke.MethodHandleNatives.resolve(Native Method) ~[na:1.8.0_172]
at java.lang.invoke.MemberName$Factory.resolve(MemberName.java:975) ~[na:1.8.0_172]
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1000) ~[na:1.8.0_172]
at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:1394) ~[na:1.8.0_172]
at java.lang.invoke.MethodHandles$Lookup.linkMethodHandleConstant(MethodHandles.java:1750) ~[na:1.8.0_172]
at java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(MethodHandleNatives.java:477) ~[na:1.8.0_172]
... 93 common frames omitted
Caused by: java.lang.ClassNotFoundException: javax.cache.configuration.Configuration
at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[na:1.8.0_172]
at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_172]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) ~[na:1.8.0_172]
at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_172]
... 99 common frames omitted
Run Code Online (Sandbox Code Playgroud)
首先,我对这个错误感到惊讶,因为代码根本没有实例化JCacheTimeZoneCache.好的,将JCache放入类路径会解决这个问题.但是图书馆的作者做了一个非常不同的修复:
foo.orElseGet(() -> new JCacheTimeZoneCache());
Run Code Online (Sandbox Code Playgroud)
现在我完全惊讶了?我实际上有两个问题:
() -> new JCacheTimeZoneCache()解决这个问题?Jor*_*nee 10
这些2 可能会以不同的方式实现,具体取决于您使用的java编译器以及在什么情况下(我没有缩小它,但它实际上是一个实现细节).
您可以通过查看输出javap -v <enclosing class>并查看BootstrapMethod表来检查这一点.编译器可能会为方法引用案例生成此内容:
1: #22 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#23 ()Ljava/lang/Object;
#27 REF_newInvokeSpecial MyClass."<init>":()V
#25 ()LMyClass;
Run Code Online (Sandbox Code Playgroud)
具体来说,重要的是MyClass."<init>":()V.这意味着MyClass::new直接查找表达式中使用的类的构造函数.
对于:
JCacheTimeZoneCache::new
Run Code Online (Sandbox Code Playgroud)
生成的invokedynamic指令JCacheTimeZoneCache直接在类中查找构造函数,并将其包装在功能接口(using LambdaMetafactory)中.
对于:
() -> new JCacheTimeZoneCache()
Run Code Online (Sandbox Code Playgroud)
到目前为止,我所看到的所有Java编译器都在包含lambda代码的封闭类中生成一个合成静态方法,然后由生成的函数接口封装在一个函数接口中invokedynamic.
不同之处在于,对于第一个,需要加载JCacheTimeZoneCache类,并且对于第二个,仅需要加载封闭类(可能已经加载).只有当lambda实际执行时才JCacheTimeZoneCache需要加载,因为这是首次需要时.
由于此"修复"基于实现细节,因此它不是很好.未来可能会发生变化,这会影响如何生成非捕获lambda(包括构造函数):JDK-8186216可能会再次破坏代码.