1. Tipos de excepciones

Todas las excepciones se dividen en 4 tipos, que son en realidad clases que se heredan unas a otras.

Clase Throwable

La clase base para todas las excepciones es la clase Throwable. La clase Throwable contiene el código que escribe la pila de llamadas actual (rastreo de pila del método actual) en una matriz. Aprenderemos qué es una pila de llamadas un poco más adelante.

El operador throw solo puede aceptar un objeto que se derive de la clase Throwable. Y aunque teóricamente puedes escribir código como throw new Throwable();, nadie suele hacer esto. El objetivo principal de la clase Throwable es tener una única clase padre para todas las excepciones.

Clase Error

La siguiente clase de excepción es la clase Error, que hereda directamente la clase Throwable. La máquina Java crea objetos de la clase Error (y sus descendientes) cuando se han producido problemas graves. Por ejemplo, un fallo de hardware, memoria insuficiente, etc.

Por lo general, como programador, no hay nada que puedas hacer en una situación en la que se ha producido un error de este tipo (el tipo para el que se debe lanzar un Error) en el programa: estos errores son demasiado graves. Todo lo que puedes hacer es notificar al usuario de que el programa se está bloqueando y/o escribir toda la información conocida sobre el error en el registro del programa.

Clase Exception

Las clases Exception y RuntimeException son para errores comunes que ocurren en la operación de muchos métodos. El objetivo de cada excepción lanzada es ser capturada por un bloque catch que sepa cómo manejarla adecuadamente.

Cuando un método no puede completar su trabajo por alguna razón, debe notificar de inmediato al método de llamada lanzando una excepción del tipo correspondiente.

In other words, if a variable is equal to null, the method will throw a NullPointerException. If the incorrect arguments were passed to the method, it will throw an InvalidArgumentException. If the method accidentally divides by zero, it will throw an ArithmeticException.

RuntimeException class

RuntimeExceptions son un subconjunto de Exceptions. Podríamos decir que RuntimeException es una versión más liviana de las excepciones comunes (Exception) - se imponen menos requisitos y restricciones en dichas excepciones.

Aprenderá la diferencia entre Exception y RuntimeException más adelante.


2. Throws: excepciones verificadas

Todas las excepciones de Java se dividen en 2 categorías: excepciones verificadas y excepciones no verificadas.

Todas las excepciones que heredan de RuntimeException o Error se consideran excepciones no verificadas. Todas las demás son excepciones verificadas.

Important!

Veinte años después de que se introdujeran las excepciones verificadas, casi todos los programadores de Java lo consideran un error. En los marcos modernos populares, el 95% de todas las excepciones son no verificadas. El lenguaje C#, que copió casi exactamente a Java, no agregó excepciones verificadas.

¿Cuál es la principal diferencia entre excepciones verificadas y no verificadas?

Las excepciones verificadas tienen requisitos adicionales impuestos sobre ellas. A grandes rasgos, son los siguientes:

Requisito 1

Si un método lanza una excepción verificada, debe indicar el tipo de excepción en su firma. De esa manera, cada método que lo llama es consciente de que esta "excepción significativa" puede ocurrir en él.

Indique excepciones verificadas después de los parámetros del método después de la palabra clave throws (no use por error la palabra clave throw). Se ve algo así:

type method (parameters) throws exception

Ejemplo:

checked exception unchecked exception
public void calculate(int n) throws Exception
{
   if (n == 0)
      throw new Exception("n is null!");
}
public void calculate(n)
{
   if (n == 0)
      throw new RuntimeException("n is null!");
}

En el ejemplo de la derecha, nuestro código lanza una excepción no comprobada - no se requiere ninguna acción adicional. En el ejemplo de la izquierda, el método lanza una excepción comprobada, por lo que se agrega la palabra clave throws a la firma del método junto con el tipo de la excepción.

Si un método espera lanzar múltiples excepciones comprobadas, todas deben especificarse después de la palabra clave throws, separadas por comas. El orden no es importante. Ejemplo:

public void calculate(int n) throws Exception, IOException
{
   if (n == 0)
      throw new Exception("n is null!");
   if (n == 1)
      throw new IOException("n is 1");
}

Requisito 2

Si llamas a un método que tiene excepciones revisadas en su firma, no puedes ignorar el hecho de que las lanza.

Debes capturar todas estas excepciones agregando bloques catch para cada una, o agregándolas a una cláusula throws para tu método.

Es como si dijéramos: "Estas excepciones son tan importantes que debemos capturarlas. Y si no sabemos cómo manejarlas, entonces debemos notificar a cualquiera que pueda llamar a nuestro método que tales excepciones pueden ocurrir en él.

Ejemplo:

Imagina que estamos escribiendo un método para crear un mundo poblado por humanos. El número inicial de personas se pasa como argumento. Por lo tanto, necesitamos agregar excepciones si hay muy pocas personas.

Creando la Tierra Nota
public void createWorld(int n) throws EmptyWorldException, LonelyWorldException
{
   if (n == 0)
      throw new EmptyWorldException("There are no people!");
   if (n == 1)
      throw new LonelyWorldException ("There aren't enough people!");
   System.out.println("A wonderful world was created. Population: " + n);
}
El método potencialmente lanza dos excepciones checked:

  • EmptyWorldException
  • LonelyWorldException

Esta llamada al método se puede manejar de 3 formas:

1. No capturar ninguna excepción

Esto se hace con mayor frecuencia cuando el método no sabe cómo manejar adecuadamente la situación.

Código Nota
public void createPopulatedWorld(int population)
throws EmptyWorldException, LonelyWorldException
{
   createWorld(population);
}
El método que llama no maneja las excepciones y debe informar a otros sobre ellas: las agrega a su propia cláusula de throws.

2. Capturar algunas de las excepciones

Manejamos los errores que podemos manejar. Pero los que no entendemos, los pasamos al método que llama. Para hacer esto, necesitamos agregar su nombre a la cláusula de throws:

Código Nota
public void createNonEmptyWorld(int population)
throws EmptyWorldException
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
}
El llamador solo captura una excepción comprobadaLonelyWorldException. La otra excepción debe ser agregada a su firma, indicándola después de la palabra clave throws

3. Captura todas las excepciones

Si el método no lanza excepciones al método llamador, entonces el método llamador siempre está seguro de que todo funcionó bien. Y no podrá tomar ninguna acción para solucionar situaciones excepcionales.

Código Nota
public void createAnyWorld(int population)
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
   catch (EmptyWorldException e)
   {
      e.printStackTrace();
   }
}
Todas las excepciones son atrapadas en este método. El llamador estará seguro de que todo salió bien.


3. Captura de múltiples excepciones

Los programadores odian duplicar código. Incluso han creado un principio de desarrollo correspondiente: DRY (No te repitas). Pero cuando se manejan excepciones, a menudo ocurre que un bloque try está seguido por varios bloques catch con el mismo código.

O puede haber 3 bloques catch con el mismo código y otros 2 bloques catch con otro código idéntico. Esta es una situación estándar cuando su proyecto maneja excepciones de manera responsable.

A partir de la versión 7, en el lenguaje Java se agregó la capacidad de especificar múltiples tipos de excepciones en un solo bloque catch. Se ve aproximadamente así:

try
{
   // Code where an exception might occur
}
catch (ExceptionType1 | ExceptionType2 | ExceptionType3 name)
{
   // Exception handling code
}

Puede tener tantos bloques catch como desee. Sin embargo, un solo bloque catch no puede especificar excepciones que hereden una de otra. En otras palabras, no puede escribir catch (Exception | RuntimeException e), porque la clase RuntimeException hereda de Exception.