Foo::new 和 () -> new Foo() 有什么区别?
What's the difference between Foo::new and () -> new Foo()?
我的印象是 Foo::new
只是 () -> new Foo()
的语法糖,它们的行为应该相同。然而,似乎并非如此。这是背景:
使用 Java-8 我使用第三方库,它有一个 Optional<Foo> foo
和这个违规行:
foo.orElseGet(JCacheTimeZoneCache::new);
JCacheTimeZoneCache
在其构造函数中使用了可选的 JCache 库中的一些东西,我没有在我的 class 路径中。我使用调试器验证 foo 不为空,因此它实际上永远不会实例化 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
首先我对这个错误感到惊讶,因为代码根本没有实例化 JCacheTimeZoneCache。好的,将 JCache 放入 class 路径可以解决这个问题。但是库的作者做了一个非常不同的修复:
foo.orElseGet(() -> new JCacheTimeZoneCache());
现在我完全惊讶了?我实际上有两个问题:
- 为什么 JCacheTimeZoneCache::new 一开始就导致了那个异常,
何时从未调用构造函数?
- 为什么
() -> new JCacheTimeZoneCache()
解决了这个问题?
这 2 可能 的实现方式不同,具体取决于您使用的 java 编译器以及在什么情况下(我没有缩小范围,但它是确实是一个实现细节。
您可以通过查看 javap -v <enclosing class>
的输出和 BootstrapMethod table 来检查这一点。编译器可能会为方法参考案例生成此文件:
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;
具体来说,重要的是MyClass."<init>":()V
。这意味着正在直接查找 MyClass::new
表达式中使用的 class 的构造函数。
对于:
JCacheTimeZoneCache::new
生成的invokedynamic
指令直接在JCacheTimeZoneCache
class中查找构造函数,并将其包装在函数式接口中(使用LambdaMetafactory
)。
对于:
() -> new JCacheTimeZoneCache()
到目前为止我看过的所有 Java 编译器都会在包含 lambda 代码的封闭 class 中生成一个合成静态方法,然后将其包装在一个功能接口中生成的 invokedynamic
.
不同之处在于,对于第一个,需要加载 JCacheTimeZoneCache
class,对于第二个,只需要加载封闭的 class(大概已经加载)是必需的。只有在实际执行 lambda 时才需要加载 JCacheTimeZoneCache
,因为那是第一次需要它的时候。
由于这个 'fix' 是基于一个实现细节,所以它不是一个很好的实现细节。未来可能会发生变化,这会影响非捕获 lambda(包括构造函数)的生成方式:JDK-8186216 这可能会再次破坏代码。
我的印象是 Foo::new
只是 () -> new Foo()
的语法糖,它们的行为应该相同。然而,似乎并非如此。这是背景:
使用 Java-8 我使用第三方库,它有一个 Optional<Foo> foo
和这个违规行:
foo.orElseGet(JCacheTimeZoneCache::new);
JCacheTimeZoneCache
在其构造函数中使用了可选的 JCache 库中的一些东西,我没有在我的 class 路径中。我使用调试器验证 foo 不为空,因此它实际上永远不会实例化 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
首先我对这个错误感到惊讶,因为代码根本没有实例化 JCacheTimeZoneCache。好的,将 JCache 放入 class 路径可以解决这个问题。但是库的作者做了一个非常不同的修复:
foo.orElseGet(() -> new JCacheTimeZoneCache());
现在我完全惊讶了?我实际上有两个问题:
- 为什么 JCacheTimeZoneCache::new 一开始就导致了那个异常, 何时从未调用构造函数?
- 为什么
() -> new JCacheTimeZoneCache()
解决了这个问题?
这 2 可能 的实现方式不同,具体取决于您使用的 java 编译器以及在什么情况下(我没有缩小范围,但它是确实是一个实现细节。
您可以通过查看 javap -v <enclosing class>
的输出和 BootstrapMethod table 来检查这一点。编译器可能会为方法参考案例生成此文件:
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;
具体来说,重要的是MyClass."<init>":()V
。这意味着正在直接查找 MyClass::new
表达式中使用的 class 的构造函数。
对于:
JCacheTimeZoneCache::new
生成的invokedynamic
指令直接在JCacheTimeZoneCache
class中查找构造函数,并将其包装在函数式接口中(使用LambdaMetafactory
)。
对于:
() -> new JCacheTimeZoneCache()
到目前为止我看过的所有 Java 编译器都会在包含 lambda 代码的封闭 class 中生成一个合成静态方法,然后将其包装在一个功能接口中生成的 invokedynamic
.
不同之处在于,对于第一个,需要加载 JCacheTimeZoneCache
class,对于第二个,只需要加载封闭的 class(大概已经加载)是必需的。只有在实际执行 lambda 时才需要加载 JCacheTimeZoneCache
,因为那是第一次需要它的时候。
由于这个 'fix' 是基于一个实现细节,所以它不是一个很好的实现细节。未来可能会发生变化,这会影响非捕获 lambda(包括构造函数)的生成方式:JDK-8186216 这可能会再次破坏代码。