What's the difference between Foo::new and () -> new Foo()?

  • A+
Category:Languages

I was under the impression that Foo::new is just syntactic sugar for () -> new Foo() and they should behave identically. However it seems not to be the case. Here's the background:

With Java-8 I use a third party library which has an Optional<Foo> foo and this offending line:

foo.orElseGet(JCacheTimeZoneCache::new); 

JCacheTimeZoneCache uses in its constructor something from the optional JCache library, which I have not in my class path. With a debugger I verified that foo is not null, so it should actually never instantiate a JCacheTimeZoneCache instance and therefore the missing JCache library should not be an issue. However it does explode with stacktrace complaining about the missing JCache library:

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 

First I am surprised by this error, as the code does not instantiate JCacheTimeZoneCache at all. Ok, putting JCache into the class path would fix that. But the author of the library did a very different fix:

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

Now I'm totally surprised? I have actually two questions:

  1. Why did JCacheTimeZoneCache::new cause that exception in the first place, when the constructor was never called?
  2. Why did () -> new JCacheTimeZoneCache() fix that issue?

 


These 2 might be implemented differently, depending on the java compiler you're using and in what case (I haven't narrowed this down, but it's really an implementation detail any ways).

You can check this by looking at the output of javap -v <enclosing class>, and looking at the BootstrapMethod table. The compiler might generate this for the method reference case:

  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; 

Specifically, what's important is the MyClass."<init>":()V. Which means that the constructor of the class used in a MyClass::new expression is being looked up directly.


For:

JCacheTimeZoneCache::new 

The generated invokedynamic instruction looks up the constructor in the JCacheTimeZoneCache class directly, and wraps it in a functional interface (using LambdaMetafactory).

For:

() -> new JCacheTimeZoneCache() 

All of the Java compilers that I've looked at so far generate a synthetic static method in the enclosing class containing the lambda's code, and then that is wrapped in a functional interface by the generated invokedynamic.

The difference being that for the first, the loading of the JCacheTimeZoneCache class is required, and for the second only the loading of the enclosing class (which is presumably already loaded) is required. Only when the lambda is actually executed the loading of JCacheTimeZoneCache is required, because that's when it's first needed.


Since this 'fix' is based on an implementation detail, it's not a very good one. There might be a change in the future which affects how non-capturing lambdas (including constructors) are generated: JDK-8186216 which could break the code again.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: