Какая разница между Foo :: new и() → новым Foo()?
У меня создалось впечатление, что Foo::new
- это просто синтаксический сахар для () → new Foo()
и они должны вести себя одинаково. Однако, похоже, это не так. Здесь фон:
С Java-8 я использую стороннюю библиотеку с Optional<Foo> foo
и этой оскорбительной строкой:
foo.orElseGet(JCacheTimeZoneCache::new);
JCacheTimeZoneCache
использует в своем конструкторе что-то из дополнительной библиотеки JCache, которой у меня нет в моем классе. С отладчиком я проверил, что foo не является нулевым, поэтому он фактически не должен создавать экземпляр 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. Хорошо, добавив JCache в путь класса, это исправит. Но автор библиотеки сделал совсем другое решение:
foo.orElseGet(() -> new JCacheTimeZoneCache());
Теперь я полностью удивлен? У меня на самом деле два вопроса:
- Почему JCacheTimeZoneCache :: new вызывает это исключение в первую очередь, когда конструктор никогда не вызывался?
- Почему
() → new JCacheTimeZoneCache()
исправить эту проблему?
Ответы
Ответ 1
Эти 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
и переносит ее в функциональный интерфейс (используя LambdaMetafactory
).
За:
() -> new JCacheTimeZoneCache()
Все компиляторы Java, на которые я сейчас смотрел, генерируют синтетический статический метод в охватывающем классе, содержащем лямбда-код, а затем он завернут в функциональный интерфейс сгенерированной invokedynamic
.
Разница заключается в том, что для первого требуется JCacheTimeZoneCache
класса JCacheTimeZoneCache
, а для второго требуется только загрузка входящего класса (который предположительно уже загружен). Только когда на самом деле выполняется JCacheTimeZoneCache
, требуется загрузка JCacheTimeZoneCache
, потому что это когда нужно сначала.
Поскольку это "исправление" основано на деталях реализации, оно не очень хорошее. В будущем может произойти изменение в будущем, которое влияет на то, как генерируются незахватывающие lambdas (включая конструкторы): JDK-8186216, которые могут снова сломать код.