Lanzar excepciones checked como si fueran unchecked en Java

Escrito por el .
java planeta-codigo
Comentarios

Java

No es una buena práctica al igual que al no recomendado antipatrón de inicialización de variables con dobles llaves pero en el uso de streams que aceptan lambdas es un rodeo a la limitación de no poder lanzar excepciones checked por no estar definida en su API.

En Java existen dos tipos de excepciones las checked que son de obligada captura o ser lanzadas y las unchecked que no son de obligada captura ni ser declaradas. Al usar streams y algunas interfaces funcionales de Java como Consumer que no lanzan excepciones el compilador generará un error de compilación si la implementación lanza una excepción.

En el siguiente código el compilador producirá un error de compilación ya que intenta lanzar una excepción pero la interfaz funcional que implementa no lo permite.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import java.util.List;

public class Main1 {

    public static void main(String[] args) {
        List<String> list = List.of("a", "b", "c");
        list.stream().forEach(i -> {
            if (i.equals("d")) {
                throw new Exception();
            }
        });
    }
}
1
2
3
4
5
6
$ java Main1.java 
Main1.java:9: error: unreported exception Exception; must be caught or declared to be thrown
                throw new Exception();
                ^
1 error
error: compilation failed

Aunque en Java existen las excepciones checked y estas han de ser declaradas no es una limitación a nivel de la máquina virtual, se puede lanzar una excepción checked aunque no esté declarada. El siguiente código compila sin errores y se ejecutan, lanzándose la excepción aunque el método main() no la declare. Esto es debido a que en el método sneakyThrow() T es inferido como del tipo RuntimeException.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class Main2 {

    @SuppressWarnings("unchecked")
    static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
        throw (T) t;
    }

    static <T extends Throwable> void nonSneakyThrow(T t) throws T {
        throw t;
    }

    public static void main(String[] args) {
        Exception e = new Exception();
        sneakyThrow(e);    // No problems here
        //nonSneakyThrow(e); // Error: Unhandled exception: java.lang.Exception
    }
}
1
2
3
$ java Main2.java 
Exception in thread "main" java.lang.Exception
    at Main2.main(Main2.java:15)

Con la clase Unsafe interna del JDK (que tampoco es recomendable usar porque en el futuro será eliminada) también es posible lanzar una excepción checked sin declararla, aunque Main3.getUnsafe().throwException(e) lanza una excepción el método main() no la declara.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import java.lang.reflect.Field;

import sun.misc.Unsafe;

public class Main3 {

    private static Unsafe getUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        Exception e = new Exception();
        Main.getUnsafe().throwException(e);
    }
}
1
2
3
$ java Main3.java 
Exception in thread "main" java.lang.Exception
    at Main3.main(Main3.java:18)

Es posible lanzar excepciones checked como si fuesen uncheked, no es una buena práctica ya que no permite al compilador cumplir con la tarea a la que está destinada que es detectar errores en tiempo de compilación potenciales problemas además de no indicar en la API que un método lanza una excepción que debería se tratada. En la librería Vavr con la clase Try se puede usar un método que lanza una excepción, tratarla si se produce y convertir el método en uno que no lanza excepciones adecuado para el uso en los streams.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import java.util.List;

import io.vavr.control.Try;

public class Main4 {

    public static void action(String string) throws Exception {
        if (i.equals("d")) {
            throw new Exception();
        }
    }

    public static void main(String[] args) {
        List<String> list = List.of("a", "b", "c");
        list.stream().forEach(i -> {
            Try.of(() -> { action(i); });
        });
    }
}

La opción más recomendable es crear una clase como Try o usar la de la librería Vavr en vez de una de las posibilidades no recomendadas anteriores.