propagación anormal
Cuando un método genera una excepción, si el método actual no detecta la excepción, la excepción se lanzará al método de llamada de nivel superior hasta que encuentre uno que sea try ... catch
detectado:
// exception
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
process2();
}
static void process2() {
Integer.parseInt(null); // 会抛出NumberFormatException
}
}
Al printStackTrace()
imprimir la pila de llamadas del método, es similar a:
java.lang.NumberFormatException: null
at java.base/java.lang.Integer.parseInt(Integer.java:614)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at Main.process2(Main.java:16)
at Main.process1(Main.java:12)
at Main.main(Main.java:5)
printStackTrace()
Es muy útil para depurar errores. La información anterior indica que se arrojó en el método. De abajo hacia arriba, el nivel de llamada es el siguiente NumberFormatException
:java.lang.Integer.parseInt
main()调用process1();
process1()调用process2();
process2()调用Integer.parseInt(String);
Integer.parseInt(String)调用Integer.parseInt(String, int)。
Mirando el código fuente de Integer.java, podemos ver que el código del método que arroja una excepción es el siguiente:
public static int parseInt(String s, int radix) throws NumberFormatException {
if (s == null) {
throw new NumberFormatException("null");
}
...
}
Además, cada llamada de capa proporciona el número de línea del código fuente, que se puede ubicar directamente.
Lanzar excepciones
Cuando ocurre un error, por ejemplo, el usuario ingresa un carácter ilegal, podemos lanzar una excepción.
¿Cómo lanzar una excepción? Consulte Integer.parseInt()
el método, lanzando una excepción en dos pasos:
Crea Exception
una instancia de a; lanza
con throw
declaración.
A continuación se muestra un ejemplo:
void process2(String s) {
if (s==null) {
NullPointerException e = new NullPointerException();
throw e;
}
}
实际上,绝大部分抛出异常的代码都会合并写成一行:
void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}
Si un método detecta una excepción y lanza una nueva excepción en la cláusula catch, es equivalente a "convertir" el tipo de excepción lanzada:
void process1(String s) {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException();
}
}
void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}
Cuando process2()
se lanza NullPointerException
, process1()
se atrapa y luego se lanza IllegalArgumentException()
.
Si main()
está atrapado en IllegalArgumentException
, echemos un vistazo a la pila de excepciones impresa:
// exception
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException();
}
}
static void process2() {
throw new NullPointerException();
}
}
La pila de excepciones impresa es similar a:
java.lang.IllegalArgumentException
at Main.process1(Main.java:15)
at Main.main(Main.java:5)
Esto muestra que la nueva excepción ha perdido la información de la excepción original y ya no podemos ver NullPointerException
la información de la excepción original.
Para poder rastrear la pila de excepciones completa, al construir una excepción, se pasa la instancia de excepción original y la nueva excepción puede contener la Exception
información original. Las mejoras al código anterior son las siguientes:
// exception
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException(e);
}
}
static void process2() {
throw new NullPointerException();
}
}
Al ejecutar el código anterior, la pila de excepciones impresa es similar a:
java.lang.IllegalArgumentException: java.lang.NullPointerException
at Main.process1(Main.java:15)
at Main.main(Main.java:5)
Caused by: java.lang.NullPointerException
at Main.process2(Main.java:20)
at Main.process1(Main.java:13)
Tenga en cuenta Caused by: Xxx
que lo que se captura IllegalArgumentException
no es la causa raíz del problema, la causa raíz es que se incluye NullPointerException
en el método.Main.process2()
Para obtener la excepción original en el código, puede usar Throwable.getCause()
el método. Si devuelve nulo, significa que ya es una "excepción raíz".
Con la información completa de la pila de excepciones, podemos localizar y solucionar problemas de código rápidamente.
Cuando se detecta una excepción y se vuelve a generar, se debe mantener la excepción original; de lo contrario, ¡será difícil localizar la primera escena del crimen!
Si lanzamos una excepción en un bloque try o catch, ¿se ejecutará la declaración finalmente? Por ejemplo:
// exception
public class Main {
public static void main(String[] args) {
try {
Integer.parseInt("abc");
} catch (Exception e) {
System.out.println("catched");
throw new RuntimeException(e);
} finally {
System.out.println("finally");
}
}
}
Los resultados de la ejecución del código anterior son los siguientes:
catched
finally
Exception in thread "main" java.lang.RuntimeException: java.lang.NumberFormatException: For input string: "abc"
at Main.main(Main.java:8)
Caused by: java.lang.NumberFormatException: For input string: "abc"
at ...
Se imprime la primera línea catched
, indicando que se ha ingresado el bloque de sentencia catch
. Se imprime la segunda línea finally
, lo que indica que se ejecuta el bloque de oraciones finally语
.
Por lo tanto, catch
lanzar una excepción no afectará finall
la ejecución de y. JVM
se ejecutará primero finally
y luego se lanzará una excepción.
Protección de excepciones
Si se lanza una excepción cuando se ejecuta la declaración finalmente, ¿se puede seguir lanzando la excepción de la declaración catch? Por ejemplo:
// exception
public class Main {
public static void main(String[] args) {
try {
Integer.parseInt("abc");
} catch (Exception e) {
System.out.println("catched");
throw new RuntimeException(e);
} finally {
System.out.println("finally");
throw new IllegalArgumentException();
}
}
}
Ejecute el código anterior y encuentre que la información de excepción es la siguiente:
catched
finally
Exception in thread "main" java.lang.IllegalArgumentException
at Main.main(Main.java:11)
Esto muestra que después de lanzar finalmente una excepción, la excepción que se preparó originalmente para ser lanzada en el catch "desaparece", porque solo se puede lanzar una excepción. Las excepciones que no se lanzan se denominan excepciones "enmascaradas" (Suppressed Exception)
.
En casos raros, necesitamos saber acerca de todas las excepciones. ¿Cómo guardar toda la información de excepción? El método consiste en guardar primero la excepción original con la variable de origen, luego llamar a Throwable.addSuppressed()
, agregar la excepción original y finalmente lanzarla:
// exception
public class Main {
public static void main(String[] args) throws Exception {
Exception origin = null;
try {
System.out.println(Integer.parseInt("abc"));
} catch (Exception e) {
origin = e;
throw e;
} finally {
Exception e = new IllegalArgumentException();
if (origin != null) {
e.addSuppressed(origin);
}
throw e;
}
}
}
Cuando ambos atrapan y finalmente lanzan una excepción, aunque la excepción de captura está protegida, la excepción lanzada por finalmente todavía la incluye:
Exception in thread "main" java.lang.IllegalArgumentException
at Main.main(Main.java:11)
Suppressed: java.lang.NumberFormatException: For input string: "abc"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Integer.parseInt(Integer.java:652)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at Main.main(Main.java:6)
Throwable.getSuppressed()
Todo n se puede obtener por Suppressed Exceptio
.
En la mayoría de los casos, no introduzca excepciones finalmente. Por lo tanto, generalmente no necesitamos preocuparnos Suppressed Exception
.
Al hacer preguntas, publique excepciones.
La información detallada de la pila impresa por las excepciones es la clave para descubrir el problema. Muchos principiantes solo publican códigos cuando hacen preguntas, pero no publican excepciones.
¿Quién te enseñó a no publicar montones de excepciones al hacer preguntas?
Algunos zapatos para niños solo publican alguna información anormal, y se omiten las más importantes.Esta Caused by: xxx
es una forma incorrecta de hacer preguntas y debe cambiarse.
resumen
La llamada printStackTrace()
puede imprimir la pila de propagación de excepciones, que es muy útil para la depuración;
Al capturar una excepción y lanzar una nueva excepción nuevamente, se debe mantener la información de la excepción original;
Normalmente no finally
lanza excepciones en . Si finalmente se lanza una excepción, la excepción original debe agregarse a la excepción original. La persona que llama puede Throwable.getSuppressed()
obtener todo agregado por Suppressed Exception
.