java - expresiones - lambda c# tutorial
java.lang.NullPointerException se genera utilizando una referencia de método pero no una expresión lambda (2)
Este comportamiento se basa en una sutil diferencia entre el proceso de evaluación de referencias de método y expresiones lambda.
De la evaluación de tiempo de ejecución JLS de las referencias de métodos :
Primero, si la expresión de referencia del método comienza con ExpressionName o Primary, se evalúa esta subexpresión. Si la subexpresión se evalúa como
null
, seNullPointerException
unaNullPointerException
y la expresión de referencia del método se completa abruptamente .
Con el siguiente código:
Thread t = new Thread(s::toLowerCase); // <-- s is null, NullPointerException thrown here
t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));
la expresión
s
se evalúa como
null
y se
null
una excepción exactamente cuando se evalúa esa referencia de método.
Sin embargo, en ese momento, no se adjuntó ningún controlador de excepción, ya que este código se ejecutaría después.
Esto no sucede en el caso de una expresión lambda, porque la lambda se evaluará sin que se ejecute su cuerpo. De la evaluación en tiempo de ejecución de las expresiones Lambda :
La evaluación de una expresión lambda es distinta de la ejecución del cuerpo lambda.
Thread t = new Thread(() -> s.toLowerCase());
t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));
Incluso si
s
es
null
, la expresión lambda se creará correctamente.
Luego se adjuntará el controlador de excepciones, se iniciará el subproceso, lanzando una excepción, que será capturada por el controlador.
Como nota al margen, parece que Eclipse Mars.2 tiene un pequeño error al respecto: incluso con la referencia del método, invoca el controlador de excepciones.
Eclipse no está lanzando una
NullPointerException
en
s::toLowerCase
cuando debería, aplazando así la excepción más adelante, cuando se agregó el controlador de excepciones.
He notado algo extraño sobre las excepciones no controladas que utilizan la referencia de método Java 8.
Este es mi código, usando la expresión lambda
() -> s.toLowerCase()
:
public class Test {
public static void main(String[] args) {
testNPE(null);
}
private static void testNPE(String s) {
Thread t = new Thread(() -> s.toLowerCase());
// Thread t = new Thread(s::toLowerCase);
t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));
t.start();
}
}
Imprime "Excepción", por lo que funciona bien.
Pero cuando cambio
Thread t
para usar una referencia de método (incluso IntelliJ sugiere eso):
Thread t = new Thread(s::toLowerCase);
la excepción no está siendo atrapada:
Exception in thread "main" java.lang.NullPointerException
at Test.testNPE(Test.java:9)
at Test.main(Test.java:4)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
¿Alguien puede explicar lo que está pasando aquí?
Guau. Has descubierto algo interesante. Echemos un vistazo a lo siguiente:
Function<String, String> stringStringFunction = String::toLowerCase;
Esto nos devuelve una función que acepta un parámetro de tipo
String
y devuelve otra
String
, que es una minúscula del parámetro de entrada.
Esto es algo equivalente al
s.toLowerCase()
, donde
s
es el parámetro de entrada.
stringStringFunction(param) === param.toLowerCase()
próximo
Function<Locale, String> localeStringFunction = s::toLowerCase;
es una función de
Locale
a
String
.
Esto es equivalente a la invocación del método
s.toLowerCase(Locale)
.
Funciona, bajo el capó, en 2 parámetros: uno es
s
otro es algo local.
Si
s
es
null
, entonces esta creación de función
NullPointerException
una
NullPointerException
.
localeStringFunction(locale) === s.toLowerCase(locale)
El siguiente es
Runnable r = () -> s.toLowerCase()
Que es una implementación de la interfaz
Runnable
que, cuando se ejecuta, llamará al método
toLowerCase
en una cadena dada.
Entonces en tu caso
Thread t = new Thread(s::toLowerCase);
intenta crear un nuevo
Thread
pase el resultado de la invocación de
s::toLowerCase
.
Pero esto arroja un
NPE
a la vez.
Incluso antes de que se inicie el hilo.
Y así,
NPE
se lanza en su hilo actual, no desde el hilo interno
t
.
Es por eso que su controlador de excepciones no se ejecuta.