Foo :: new和()-> new Foo()有什么区别?

时间:2018-06-30 08:39:28

标签: java lambda method-reference

我的印象是Foo::new只是() -> new Foo()的语法糖,它们的行为应相同。但是,似乎并非如此。这是背景:

在Java-8中,我使用了一个第三方库,该库具有Optional<Foo> foo和以下违规行:

foo.orElseGet(JCacheTimeZoneCache::new);

JCacheTimeZoneCache在其构造函数中使用了可选JCache库中的某些内容,而我不在类路径中。使用调试器,我验证了foo不为null,因此它实际上不应实例化JCacheTimeZoneCache实例,因此缺少的JCache库应该不是问题。但是,它会因抱怨缺少JCache库的stacktrace而爆炸:

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放入类路径可以解决该问题。但是该库的作者做了一个非常不同的修复:

foo.orElseGet(() -> new JCacheTimeZoneCache());

现在我完全感到惊讶吗?我实际上有两个问题:

  1. JCacheTimeZoneCache :: new为什么首先导致该异常, 什么时候从未调用过构造函数?
  2. 为什么() -> new JCacheTimeZoneCache()解决了该问题?

1 个答案:

答案 0 :(得分:10)

这2个可能的实现方式有所不同,具体取决于您所使用的Java编译器以及在哪种情况下(我没有缩小范围,但实际上是一种以任何方式详细说明的实现)。 / p>

您可以通过查看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;

具体来说,重要的是MyClass."<init>":()V。这意味着直接在MyClass::new表达式中使用的类的构造函数。


针对:

JCacheTimeZoneCache::new

生成的invokedynamic指令直接在JCacheTimeZoneCache类中查找构造函数,并将其包装在功能接口中(使用LambdaMetafactory)。

针对:

() -> new JCacheTimeZoneCache()

到目前为止,我研究过的所有Java编译器都在包含lambda代码的封闭类中生成一个合成静态方法,然后将其生成的invokedynamic包裹在一个函数接口中。

区别在于,对于第一个,需要加载JCacheTimeZoneCache类,对于第二个,仅需要加载封闭类(可能已经加载)。仅当lambda实际执行时,才需要加载JCacheTimeZoneCache,因为这是第一次需要。


由于此“修复”是基于实现细节的,因此它不是一个很好的解决方案。将来可能会发生变化,从而影响非捕获的lambda(包括构造函数)的生成方式:{​​{3}},这可能会再次破坏代码。