Solución alternativa para las excepciones comprobadas de Java

49

Aprecio mucho las nuevas características de Java 8 sobre las interfaces de métodos predeterminados y lambdas. Sin embargo, todavía me aburro con las excepciones comprobadas. Por ejemplo, si solo quiero enumerar todos los campos visibles de un objeto, me gustaría simplemente escribir esto:

    Arrays.asList(p.getClass().getFields()).forEach(
        f -> System.out.println(f.get(p))
    );

Sin embargo, dado que el método get puede generar una excepción marcada, que no está de acuerdo con el contrato de interfaz Consumer , entonces debo capturar esa excepción y escribir el siguiente código:

    Arrays.asList(p.getClass().getFields()).forEach(
            f -> {
                try {
                    System.out.println(f.get(p));
                } catch (IllegalArgumentException | IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }
            }
    );

Sin embargo, en la mayoría de los casos, solo quiero que la excepción se lance como RuntimeException y deje que el programa maneje, o no, la excepción sin errores de compilación.

Por lo tanto, me gustaría conocer su opinión sobre mi polémica solución para el problema de las excepciones verificadas. Para ese fin, creé una interfaz auxiliar ConsumerCheckException<T> y una función de utilidad rethrow ( se actualizó según la sugerencia de Comentario de Doval ) de la siguiente manera:

  @FunctionalInterface
  public interface ConsumerCheckException<T>{
      void accept(T elem) throws Exception;
  }

  public class Wrappers {
      public static <T> Consumer<T> rethrow(ConsumerCheckException<T> c) {
        return elem -> {
          try {
            c.accept(elem);
          } catch (Exception ex) {
            /**
             * within sneakyThrow() we cast to the parameterized type T. 
             * In this case that type is RuntimeException. 
             * At runtime, however, the generic types have been erased, so 
             * that there is no T type anymore to cast to, so the cast
             * disappears.
             */
            Wrappers.<RuntimeException>sneakyThrow(ex);
          }
        };
      }

      /**
       * Reinier Zwitserloot who, as far as I know, had the first mention of this
       * technique in 2009 on the java posse mailing list.
       * http://www.mail-archive.com/[email protected]/msg05984.html
       */
      public static <T extends Throwable> T sneakyThrow(Throwable t) {
          throw (T) t;
      }
  }

Y ahora solo puedo escribir:

    Arrays.asList(p.getClass().getFields()).forEach(
            rethrow(f -> System.out.println(f.get(p)))
    );

No estoy seguro de que este sea el mejor modismo para cambiar las excepciones comprobadas, pero como expliqué, me gustaría tener una forma más conveniente de lograr mi primer ejemplo sin tratar las excepciones comprobadas y esta es la forma más sencilla. que encontré para hacerlo.

    
pregunta Fernando Miguel Carvalho 29.01.2014 - 14:56

6 respuestas

16

Ventajas, desventajas y limitaciones de su técnica:

  • Si el código de llamada es para manejar la excepción marcada, DEBE agregarla a la cláusula de lanzamientos del método que contiene el flujo. El compilador no te obligará a agregarlo más, por lo que es más fácil olvidarlo. Por ejemplo:

    public void test(Object p) throws IllegalAccessException {
        Arrays.asList(p.getClass().getFields()).forEach(rethrow(f -> System.out.println(f.get(p))));
    }
    
  • Si el código de llamada ya maneja la excepción marcada, el compilador le recordará que agregue la cláusula de lanzamientos a la declaración del método que contiene el flujo (si no lo hace, dirá: la excepción nunca se lanza en el cuerpo de la declaración de prueba correspondiente).

  • En cualquier caso, no podrá rodear la transmisión en sí para capturar la excepción marcada DENTRO del método que contiene la transmisión. (Si lo intentas, el compilador dirá: La excepción nunca se lanza en el cuerpo de la instrucción try correspondiente).

  • Si está llamando a un método que literalmente nunca puede lanzar la excepción que declara, entonces no debe incluir la cláusula de lanzamientos. Por ejemplo: new String (byteArr, "UTF-8") lanza la excepción UnsupportedEncodingException, pero la especificación de Java garantiza que UTF-8 siempre estará presente. Aquí, la declaración de lanzamientos es una molestia y cualquier solución para silenciarla con un mínimo de repetición es bienvenida.

  • Si odias las excepciones marcadas y sientes que nunca deberían agregarse al lenguaje Java para comenzar (un número creciente de personas piensa de esta manera, y NO soy uno de ellos), entonces simplemente no agregue la excepción marcada a la cláusula de lanzamientos del método que contiene el flujo. El comprobado la excepción, entonces, se comportará como una excepción sin marcar.

  • Si está implementando una interfaz estricta en la que no tiene la opción de agregar una declaración de lanzamientos y, sin embargo, lanzar una excepción es totalmente apropiado, luego envolver una excepción solo para obtener el privilegio de lanzarla da lugar a un seguimiento de pila con excepciones espurias que No aportes información sobre lo que realmente salió mal. Un buen ejemplo es Runnable.run (), que no arroja ninguna excepción comprobada. En este caso, puede decidir no agregar la excepción marcada a la cláusula de lanzamientos del método que contiene el flujo.

  • En cualquier caso, si decide NO agregar (u olvidarse de agregar) la excepción marcada a la cláusula de tiros del método que contiene el flujo, tenga en cuenta estas 2 consecuencias de lanzar excepciones COMPROBADAS:

    1. El código de llamada no podrá capturarlo por su nombre (si lo intentas, el compilador dirá: La excepción nunca se incluye en el cuerpo del intento correspondiente declaración). Se burbujeará y probablemente quedará atrapado en el bucle del programa principal por alguna "Excepción de captura" o "Captura que se puede tirar", que puede ser lo que usted desee. quiero de todos modos.

    2. Viola el principio de menos sorpresa: ya no será suficiente para capturar RuntimeException para poder garantizar la captura de todos posibles excepciones. Por este motivo, creo que esto no se debe hacer en el código del marco, sino solo en el código de negocio que usted controla completamente.

Referencias:

NOTA: Si decide utilizar esta técnica, puede copiar la clase LambdaExceptionUtil helper de StackOverflow: enlace . Le proporciona la implementación completa (Función, Consumidor, Proveedor ...), con ejemplos.

    
respondido por el MarcG 26.12.2014 - 20:29
6

En este ejemplo, ¿puede realmente fallar? No lo creo, pero tal vez su caso es especial. Si realmente "no puede fallar", y es solo una cosa molesta del compilador, me gusta envolver la excepción y lanzar un Error con un comentario "no puede suceder". Deja las cosas muy claras para el mantenimiento. De lo contrario, se preguntarán "¿cómo puede suceder esto" y "quién diablos maneja esto?"

Esto en una práctica controvertida para YMMV. Probablemente obtendré algunos votos negativos.

    
respondido por el user949300 30.01.2014 - 07:31
1

otra versión de este código donde la excepción marcada solo se retrasa:

public class Cocoon {
         static <T extends Throwable> T forgetThrowsClause(Throwable t) throws T{
            throw (T) t;
        }

        public static <X, T extends Throwable> Consumer<X> consumer(PeskyConsumer<X,T> touchyConsumer) throws T {
            return new Consumer<X>() {
                @Override
                public void accept(X t) {
                    try {
                        touchyConsumer.accept(t);
                    } catch (Throwable exc) {
                        Cocoon.<RuntimeException>forgetThrowsClause(exc) ;
                    }

                }
            } ;
        }
// and so on for Function, and other codes from java.util.function
}

la magia es que si llamas:

 myArrayList.forEach(Cocoon.consumer(MyClass::methodThatThrowsException)) ;

entonces su código estará obligado a detectar la excepción

    
respondido por el java bear 06.10.2014 - 18:07
-1

Las excepciones comprobadas son muy útiles y su manejo depende del tipo de producto que codifique como parte de:

  • una biblioteca
  • una aplicación de escritorio
  • un servidor que se ejecuta dentro de algún cuadro
  • un ejercicio académico

Por lo general, una biblioteca no debe manejar excepciones marcadas, sino declarar como lanzadas por la API pública. El caso raro en el que podrían incluirse en RuntimeExcepton simple es, por ejemplo, crear dentro del código de la biblioteca un documento XML privado y analizarlo, crear un archivo temporal y luego leerlo.

Para las aplicaciones de escritorio, debe iniciar el desarrollo del producto creando su propio marco de manejo de excepciones. Usando su propio marco, normalmente envolverá una excepción marcada en algunos de dos a cuatro envoltorios (sus subclases personalizadas de RuntimeExcepion) y las manejará mediante un único controlador de excepciones.

Por lo general, para los servidores, su código se ejecutará dentro de algún marco. Si no usa ninguno (un servidor simple) cree su propio marco similar (basado en los tiempos de ejecución) descrito anteriormente.

Para ejercicios académicos, siempre puedes envolverlos directamente en RuntimeExcepton. Alguien puede crear un pequeño marco también.

    
respondido por el Razmik 04.06.2015 - 08:19
-1

Es posible que desee consultar este proyecto ; Lo creé específicamente debido al problema de las excepciones comprobadas y las interfaces funcionales.

Con esto puedes hacer esto en lugar de escribir tu código personalizado:

// I don't know the type of f, so it's Foo...
final ThrowingConsumer<Foo> consumer = f -> System.out.println(f.get(p));

Dado que todas esas interfaces de Throwing * extienden sus contrapartes que no lanzan, puedes usarlas directamente en la secuencia:

Arrays.asList(p.getClass().getFields()).forEach(consumer);

O simplemente puedes:

import static com.github.fge.lambdas.consumers.Consumers.wrap;

Arrays.asList(p.getClass().getFields())
    .forEach(wrap(f -> System.out.println(f.get(p)));

Y otras cosas.

    
respondido por el fge 29.12.2014 - 23:07
-1

sneakyThrow es genial! Entiendo totalmente tu dolor. Paguro tiene interfaces funcionales que envuelven las excepciones comprobadas para que no tenga que pensar más en esto. Incluye colecciones inmutables y transformaciones funcionales a lo largo de las líneas de los transductores Clojure o Java 8 Streams que toman las interfaces funcionales y envuelven las excepciones comprobadas.

    
respondido por el GlenPeterson 02.12.2015 - 17:54

Lea otras preguntas en las etiquetas