В чем разница между Foo :: new и () -> new Foo ()? - PullRequest
0 голосов
/ 30 июня 2018

У меня сложилось впечатление, что Foo::new - это просто синтаксический сахар для () -> new Foo(), и они должны вести себя одинаково. Однако, похоже, это не так. Вот фон:

В Java-8 я использую стороннюю библиотеку, которая имеет Optional<Foo> 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. Хорошо, размещение JCache в пути к классам исправит это. Но автор библиотеки сделал совсем другое исправление:

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

Теперь я полностью удивлен? У меня на самом деле два вопроса:

  1. Почему JCacheTimeZoneCache :: new вызвало это исключение, когда конструктор никогда не вызывался?
  2. Почему () -> new JCacheTimeZoneCache() решил эту проблему?

1 Ответ

0 голосов
/ 30 июня 2018

Эти 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, потому что именно тогда она и нужна.


Поскольку это «исправление» основано на деталях реализации, оно не очень хорошее. Возможно, в будущем произойдут изменения, которые повлияют на то, как генерируются неперехватывающие лямбды (включая конструкторы): JDK-8186216 , что может снова нарушить код.

...