Java 什么';这就是Foo::new和()->;新的Foo()?

Java 什么';这就是Foo::new和()->;新的Foo()?,java,lambda,method-reference,Java,Lambda,Method Reference,我的印象是,Foo::new只是()->new Foo()的语法糖,它们的行为应该相同。然而,情况似乎并非如此。背景如下: 在Java-8中,我使用了一个第三方库,它有一个可选的foo,这一行有问题: foo.orElseGet(JCacheTimeZoneCache::new); JCacheTimeZoneCache在其构造函数中使用可选JCache库中的某些内容,而我的类路径中没有这些内容。通过一个调试器,我验证了foo不是null,因此它实际上不应该实例化JCacheTimeZoneC

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

在Java-8中,我使用了一个第三方库,它有一个
可选的foo
,这一行有问题:

foo.orElseGet(JCacheTimeZoneCache::new);
JCacheTimeZoneCache
在其构造函数中使用可选JCache库中的某些内容,而我的类路径中没有这些内容。通过一个调试器,我验证了foo不是null,因此它实际上不应该实例化JCacheTimeZoneCache实例,因此缺少的JCache库不应该是一个问题。但是,stacktrace抱怨缺少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::new首先会导致该异常, 当构造函数从未被调用时
  • 为什么新的JCacheTimeZoneCache()解决了这个问题
  • 这两种方法的实现可能有所不同,这取决于您使用的java编译器以及在何种情况下(我没有缩小范围,但它实际上是一个实现细节)

    您可以通过查看
    javap-v的输出和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;
    
    生成的
    invokedynamic
    指令直接在
    JCacheTimeZoneCache
    类中查找构造函数,并将其包装在函数接口中(使用
    LambdaMetafactory

    用于:

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

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



    由于此“修复”基于实现细节,因此它不是一个很好的修复。将来可能会有一个更改,它会影响非捕获lambda(包括构造函数)的生成方式:这可能会再次中断代码。

    堆栈跟踪是在编译时还是在运行时发生的?发布异常的准确完整堆栈跟踪。堆栈跟踪发生在运行时。了解更多上下文。您还可以在那里看到完整的堆栈跟踪。我认为堆栈跟踪的其余部分只是干扰噪声,但我会更新。该方法引用了一个
    静态
    方法。lambda将被分解为一个生成的类-这意味着除非需要,否则不会加载该类..感谢您的澄清。这是一个实现细节还是实际记录的行为?@MarkusMalkusch是的,这是一个实现细节。事实上,当我在答案中添加
    javap
    输出时,我发现eclipse编译器可以做到这一点,而不是
    javac
    。在这两种情况下,
    javac
    似乎都会生成一个合成方法。(我会在答案中加上这个)。@MarkusMalkusch或者实际上,或者你在使用Eclipse?否则这个答案是不正确的,我应该删除它。我要补充的是,所做的“修复”实际上是隐藏了错误,而不是修复它。正确的修复方法是将所需的类添加到类路径中。@BoristheSpider我明白了。那么修复不应该依赖于lambdas的这种实现细节。相反,它应该检查是否提供了特定的实现,然后检查默认实现是否在类路径中,最后返回它,或者返回一条明确的错误消息。下一个维护此代码的开发人员将看到IDE建议将其更改为方法引用,并将重新引入此错误。
      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;
    
    JCacheTimeZoneCache::new
    
    () -> new JCacheTimeZoneCache()