example - getpath() java
¿Cuál es la diferencia entre Foo:: new y()-> new Foo()? (1)
Tenía la impresión de que Foo::new
es solo azúcar sintáctica para () -> new Foo()
y deberían comportarse de manera idéntica. Sin embargo, parece que no es el caso. Aquí está el fondo:
Con Java-8, uso una biblioteca de terceros que tiene un Optional<Foo> foo
y esta línea ofensiva:
foo.orElseGet(JCacheTimeZoneCache::new);
JCacheTimeZoneCache
usa en su constructor algo de la biblioteca de JCache opcional, que no tengo en la ruta de mi clase. Con un depurador, verifiqué que foo no es nulo, por lo que nunca debería crear una instancia de JCacheTimeZoneCache y, por lo tanto, la biblioteca de JCache que falta no debería ser un problema. Sin embargo, explota con stacktrace quejándose de la biblioteca JCache que falta:
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
Primero, me sorprende este error, ya que el código no crea una instancia de JCacheTimeZoneCache. Ok, poner a JCache en la ruta de clase lo arreglaría. Pero el autor de la biblioteca hizo una solución muy diferente:
foo.orElseGet(() -> new JCacheTimeZoneCache());
Ahora estoy totalmente sorprendido? En realidad tengo dos preguntas:
- ¿Por qué JCacheTimeZoneCache :: new causó esa excepción en primer lugar, cuando el constructor nunca fue llamado?
- ¿Por qué
() -> new JCacheTimeZoneCache()
solucionó ese problema?
Estos 2 podrían implementarse de manera diferente, dependiendo del compilador de java que estés usando y en qué caso (no lo he reducido, pero en realidad es un detalle de implementación).
Puede verificar esto mirando la salida de javap -v <enclosing class>
, y mirando la tabla BootstrapMethod. El compilador podría generar esto para el caso de referencia del método:
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;
Específicamente, lo que es importante es MyClass."<init>":()V
Lo que significa que el constructor de la clase utilizada en una expresión MyClass::new
se está buscando directamente.
Por:
JCacheTimeZoneCache::new
La instrucción invokedynamic
generada busca el constructor en la clase JCacheTimeZoneCache
directamente y lo envuelve en una interfaz funcional (usando LambdaMetafactory
).
Por:
() -> new JCacheTimeZoneCache()
Todos los compiladores de Java que he visto hasta ahora generan un método estático sintético en la clase adjunta que contiene el código de lambda, y luego se envuelve en una interfaz funcional por el invokedynamic
generado.
La diferencia es que para la primera, se requiere la carga de la clase JCacheTimeZoneCache
, y para la segunda, solo se requiere la carga de la clase JCacheTimeZoneCache
(que probablemente ya está cargada). Solo cuando la lambda se ejecuta realmente se requiere la carga de JCacheTimeZoneCache
, porque es cuando primero se necesita.
Dado que este ''arreglo'' se basa en un detalle de implementación, no es muy bueno. Podría haber un cambio en el futuro que afecte la forma en que se generan las lambdas no capturadoras (incluidos los constructores): JDK-8186216 que podría volver a romper el código.