Las excepciones para gestionar errores en Java

Escrito por picodotdev el .
java planeta-codigo programacion
Enlace permanente Comentarios

En la ejecución de los programas se pueden producir numerosas condiciones de error, algunas condiciones son un dato con un formato inesperado como una letra cuando se espera un número o fallos en la entrada o salida al trabajar con archivos o comunicación por red si un archivo que se quiere utilizar en realidad no existe en el sistema de archivos o el servidor no está disponible en la comunicación por red, estos son solo unos pocos ejemplo de errores posibles en un programa. Pero hay muchas otras condiciones de error que se puedan producir para las que un programa ha de estar preparado.

En la documentación Javadoc de cada clase y utilizando la asistencia de código de los entornos integrados de desarrollo al usar un método es posible conocer que excepciones checked lanza, el saber que excepciones lanza un método permite añadir el código necesario para tratar cada una de ellas de forma adecuada o lanzarlas para que el método invocante las trate.

Qué son las excepciones de Java

Las excepciones son un mecanismo para capturar y generar errores en un programa en tiempo de ejecución. Son una de las formas de gestionar errores como alternativa al retorno de valores especiales, otra forma de gestionar errores alternativa a las excepciones son gestionar errores con la clase Either de Vavr.

En Java las excepciones están incorporadas en el lenguaje desde Java desde la primera versión y que en posteriores se han mejorado. Las palabras reservadas del lenguaje para las excepciones son try, catch, finally para la captura y throw y throws para lanzar excepciones en los métodos. Las excepciones son uno de los tipos de sentencias y estructuras de control de flujo básicas de Java.

En Java las excepciones son objetos que extienden la clase Throwable de la que en el JDK es extendida por la clase Error y Exception de la que de esta última a su vez hereda RuntimeException. Entre las comprobaciones que realiza el compilador está que las excepciones checked lanzadas por un método son capturadas por el código que lo llama. Solo los objetos que hereden de Throwable pueden ser lanzados y capturados en los bloques try-catch.

Tipos de excepciones, checked y unchecked

Las excepciones que heredan de Exception se denominan cheked exceptions, han declararse en los métodos siendo de obligada captura o relanzado para su tratamiento en el método que invoca al que lanza la excepción. Estas son los tipos de excepciones que normalmente crea un programa para gestionar sus propias excepciones. Para crear una nueva excepción basta con crear una nueva clase que extienda de Exception, para lanzar una excepción hay que crear una instancia de la clase y lanzarla con la palabra reservada throws, en la firma del método ha de declararse que puede lanzar una excepción con la palabra reservada throws.

Las excepciones que heredan de RuntimeExcepcion también se les conoce como uncheked exception, no necesitan declararse en los métodos para ser lanzadas, son empleadas para advertir de errores de programación como dividir por cero produciendo ArithmeticException, desreferenciar un puntero nulo produciendo NullPointerException o acceder a una posición inválida de un array produciendo ArrayIndexOutOfBoundsException.

Las excepciones que heredan de Error son empleadas para casos en los que se han agotado recursos del sistema como la memoria o condiciones de error en el sistema que generalmente impiden el correcto funcionamiento del programa y de difícil tratamiento salvo terminar la ejecución.

Excepciones comunes de Java

En la colección de clases de la API de Java se incluyen muchas excepciones, en la documentación Javadoc como en el resto de clases incluyen una descripción con la condición de error que indican. Algunas de las excepciones más comunes de Java son:

  • ArrayIndexOutOfBounds: se lanza al acceder a una posición ilegal de un array al ser el índice negativo, mayor o igual que el tamaño del array.
  • NullPointerException: se lanza cuando se intenta usar una referencia nula en el uso de un objeto. Es una de las excepciones más comunes y es debido a un error en la programación del programa.
  • ClassCastException: se lanza cuando se hace una operación de cast a un tipo de objeto de la que no es una instancia.
  • IllegalArgumentException: se lanza para indicar que el valor de un argumento es inválido, se suele utilizar para comprobar una precondición al inicio del método.
  • IOException: indica algún tipo de error en una operación de entrada/salida. El error de entrada/salida es posible al trabajar con archivos o con operaciones de red.
  • FileNotFoundException: es un tipo de error de entrada/salida que indica que al abrir el archivo este no existe en el sistema de archivos.
  • ClassNotFoundException: se lanza cuando se intenta cargar una clase pero esta no existe usando forName, findSystemClass o loadClass. Su causa suele ser porque una librería no se ha proporcionado en el classpath al iniciar el programa o la aplicación requiere otra versión de alguna librería.
  • NoClassDefFoundError: es similar a ClassNotFoundException pero se produce cuando la clase existe en tiempo de compilación pero no en tiempo de ejecución.
  • NoSuchMethodException: se lanza cuando un método no se encuentra. Es posible cuando una nueva versión de una librería ha eliminado un método o ha cambiado su firma con un cambio no compatible con versiones anteriores.
  • SQLException: se produce en el acceso a bases de datos relacionales, por ejemplo cuando la sentencia SQL no tiene una sintaxis correcta.
  • StackOverflowError: se produce cuando se hacen demasiadas llamadas recursivas a un método.
  • InterruptedException: se produce cuando un thread es interrumpido. Esto ocurre al trabajar en Java con hilos y operaciones de concurrencia.

Ejemplo de código usando excepciones

Este sería un ejemplo de código que hace uso de una excepción propia para detectar una condición de error, una execpción es checked y otra unchecked.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Account {

    private BigDecimal amount;

    public Account(BigDecimal amount) {
        checkIsPositiveAmount(amount);

        this.amount = amount;
    }

    public void transfer(Account account, BigDecimal amount) throws InvalidOperationException {
        checkIsPositiveAmount(amount);
        
        if (this.amount.compareTo(amount) == -1) {
            throw new InvalidOperationException("No enough amount to do transfer");
        }

        substract(amount);
        account.add(amount);
    }

    public BigDecimal getAmount() {
        return this.amount;
    }

    public void add(BigDecimal amount) {
        checkIsPositiveAmount(amount);

        this.amount = this.amount.add(amount);
    }

    public void substract(BigDecimal amount) {
        checkIsPositiveAmount(amount);

        this.amount = this.amount.subtract(amount);
    }

    private void checkIsPositiveAmount(BigDecimal amount) {
        if (amount.signum() == -1) {
            throw new InvalidAmountException("Negative amount is invalid");
        }
    }
}
Account.java
1
2
3
4
5
public class InvalidAmountException extends RuntimeException {

    public InvalidAmountException() {}
    public InvalidAmountException(String message) { super(message); }
}
InvalidAmountException.java
1
2
3
4
5
public class InvalidOperationException extends Exception {

    public InvalidOperationException() {}
    public InvalidOperationException(String message) { super(message); }
}
InvalidOperationException.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ jshell
|  Welcome to JShell -- Version 9
|  For an introduction type: /help intro

jshell> /edit
|  created class InvalidAmountException
|  created class InvalidOperationException
|  created class Account

jshell> Account account1 = new Account(new BigDecimal("600.50"));
account1 ==> Account@4dfa3a9d

jshell> Account account2 = new Account(new BigDecimal("100"));
account2 ==> Account@464bee09

jshell> account1.transfer(account2, new BigDecimal("150"));

jshell> account1.getAmount();
$7 ==> 450.50

jshell> account2.getAmount();
$8 ==> 250

jshell> account1.transfer(account2, new BigDecimal("-150"));
|  REPL.$JShell$11$InvalidAmountException thrown: Negative amount is invalid
|        at Account.checkIsPositiveAmount (#3:40)
|        at Account.transfer (#3:12)
|        at (#9:1)

jshell> account1.transfer(account2, new BigDecimal("1050"));
|  REPL.$JShell$12$InvalidOperationException thrown: No enough amount to do transfer
|        at Account.transfer (#3:15)
|        at (#10:1)

jshell>
jshell.out

Aunque las excepciones son un buen mecanismo para el tratamiento de errores se les critica que rompen el flujo de ejecución de un programa y tienen un coste en rendimiento, aunque esta penalización de rendimiento en la mayoría de programas es irrelevante. Algunas recomendaciones que se hace para usar de forma efectiva las excepciones son:

  • Las excepciones no deben reemplazar comprobaciones simples con sentencias if.
  • No se deben microgestionar las excepciones. Los bloques try-catch deben contener bloques de código de varias líneas de código en vez una única sentencia por cada bloque try-catch.
  • Se debe hacer un buen uso de la jerarquía de excepciones y capturar la excepción que se vaya a tratar en el bloque catch. Una excepción se puede convertir en otra por ejemplo convertir un NumberFormatException a IOException.
  • No se deben capturar excepciones para no hacer nada con ellas, esto es no debe haber bloques catch vacíos.
  • En algunos casos es mejor lanzar una excepción que un valor null que posiblemente produzca un NullPointerException en otra parte distante del código de dónde se devolvió el valor null.
  • Propagar excepciones no es un signo de poca sabiduría, puede haber motivos para ello sobre todo si no se le puede dar un tratamiento adecuado.

Cuando se produce una excepción la clase Throwable posee métodos para emitir en la salida un informe de la pila de llamadas, también se puede personalizar la salida. Esta información es esencial y muy útil para conocer la causa de un error ya que indica entre otras cosas cada uno de los métodos, línea en el código fuente y clases donde se ha producido la excepción.

Hay una cierta controversia sobre las excepciones checked y unchecked ya que mejoran el control de errores pero pueden generar otros problemas. Ambas opciones tienen sus lados positivos y otros negativos. En Java se optó por implementar el mecanismo de excepciones donde las checked es obligatorio tratarlas o lanzarlas a un nivel superior de la pila de llamadas.

El siguiente tutorial sobre el manejo de las excepciones en Java las comenta desde el punto de vista teórico de como se usan y mejores prácticas al usarlas.

Comparte el artículo: