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

标签 java lambda method-reference

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

对于 Java-8,我使用了一个第三方库,它有一个 Optional<Foo> foo而这条违规行:

foo.orElseGet(JCacheTimeZoneCache::new);

JCacheTimeZoneCache在其构造函数中使用了可选 JCache 库中的某些内容,而我的类路径中没有这些内容。使用调试器,我验证了 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 放入类路径可以解决这个问题。但是库的作者做了一个非常不同的修复:

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

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

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

最佳答案

这 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;

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


为:

JCacheTimeZoneCache::new

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

为:

() -> new JCacheTimeZoneCache()

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

不同之处在于第一个加载 JCacheTimeZoneCache class 是必需的,其次只需要加载封闭类(可能已经加载)。只有在实际执行 lambda 时才加载 JCacheTimeZoneCache是必需的,因为那是第一次需要它的时候。


由于此“修复”基于实现细节,因此不是一个很好的修复。 future 可能会发生变化,这会影响非捕获 lambda(包括构造函数)的生成方式:JDK-8186216这可能会再次破坏代码。

关于java - Foo::new 和 () -> new Foo() 有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51113252/

相关文章:

Java Lambda 表达式和对象方法引用

c++ 转换与 if_then_else 控制结构

PHP 5.3 lambda 匿名函数不工作

java - Java 8 中的方法引用缓存是个好主意吗?

java - Comparator.comparing(...) 在采用 String::compareTo 时抛出非静态引用异常

java - Android:是否可以下载Android自定义View的类(它是LinearLayout的扩展)并在运行时实例化它?

java - Spring Web "java.lang.NoClassDefFoundError: antlr/RecognitionException] with root cause"错误

java - 当项目有多个 pom xml 文件时,如何构建单独的 war 文件?

java-8 - 此表达式的目标类型必须是 MethodReferences 中的函数式接口(interface)

java - 需要帮助才能使 Spring MVC 项目与 IntelliJ IDEA 一起使用