Aprecio mucho las nuevas características de Java 8 sobre lambdas e interfaces de métodos predeterminados. Sin embargo, todavía me aburro con las excepciones marcadas. 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 get
método podría arrojar una excepción marcada, que no está de acuerdo con el Consumer
contrato de interfaz, entonces debo detectar 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 arroje como ay RuntimeException
deje que el programa maneje, o no, la excepción sin errores de compilación.
Por lo tanto, me gustaría tener su opinión sobre mi solución controvertida para las molestias de excepciones comprobadas. Con ese fin, creé una interfaz auxiliar ConsumerCheckException<T>
y una función de utilidad rethrow
( actualizada de acuerdo con la sugerencia del 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 marcadas, pero como expliqué, me gustaría tener una forma más conveniente de lograr mi primer ejemplo sin tratar con las excepciones marcadas y esta es la forma más simple que encontré para hacerlo.
fuente
sneakyThrow
dentrorethrow
para lanzar la excepción original, marcada, en lugar de envolverla en unRuntimeException
. Alternativamente, podría usar la@SneakyThrows
anotación del Proyecto Lombok que hace lo mismo.Consumer
s enforEach
puede ejecutarse en forma paralela cuando se usaStream
s paralela . Un lanzamiento arrojado desde el consumidor se propagará al hilo de llamada, que 1) no detendrá a los otros consumidores que se ejecutan simultáneamente, lo que puede ser apropiado o no, y 2) si más de uno de los consumidores arroja algo, solo uno de los lanzamientos será visto por el hilo de llamada.Respuestas:
Ventajas, desventajas y limitaciones de su técnica:
Si el código de llamada es para manejar la excepción marcada, DEBE agregarlo a la cláusula throws del método que contiene la secuencia. El compilador ya no te obligará a agregarlo, por lo que es más fácil olvidarlo. Por ejemplo:
Si el código de llamada ya maneja la excepción marcada, el compilador le recordará que agregue la cláusula throws a la declaración del método que contiene la secuencia (si no lo hace, dirá: la excepción nunca se arroja en el cuerpo de la instrucción try correspondiente) .
En cualquier caso, no podrá rodear la secuencia en sí para detectar la excepción marcada DENTRO del método que contiene la secuencia (si lo intenta, el compilador dirá: La excepción nunca se arroja en el cuerpo de la declaración de prueba 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 throws. Por ejemplo: new String (byteArr, "UTF-8") produce UnsupportedEncodingException, pero la especificación de Java garantiza que UTF-8 siempre esté presente. Aquí, la declaración de tiros es una molestia y cualquier solución para silenciarla con un mínimo repetitivo es bienvenida.
Si odia las excepciones marcadas y cree que nunca deberían agregarse al lenguaje Java para empezar (un número creciente de personas piensan de esta manera, y NO soy uno de ellos), entonces no agregue la excepción marcada a los lanzamientos cláusula del método que contiene la secuencia. La excepción marcada se comportará como una excepción NO marcada.
Si está implementando una interfaz estricta donde no tiene la opción de agregar una declaración de lanzamiento y, sin embargo, lanzar una excepción es completamente apropiado, entonces envolver una excepción solo para obtener el privilegio de lanzarlo da como resultado un seguimiento de pila con excepciones espurias que no aportan información sobre lo que realmente salió mal. Un buen ejemplo es Runnable.run (), que no arroja ninguna excepción marcada. En este caso, puede decidir no agregar la excepción marcada a la cláusula throws del método que contiene la secuencia.
En cualquier caso, si decide NO agregar (u olvidarse de agregar) la excepción marcada a la cláusula throws del método que contiene la secuencia, tenga en cuenta estas 2 consecuencias de lanzar excepciones CHECKED:
El código de llamada no podrá capturarlo por su nombre (si lo intenta, el compilador dirá: la excepción nunca se arroja en el cuerpo de la instrucción de prueba correspondiente). Burbujeará y probablemente quedará atrapado en el bucle principal del programa por alguna "excepción de captura" o "captura Throwable", que puede ser lo que desee de todos modos.
Viola el principio de la menor sorpresa: ya no será suficiente capturar RuntimeException para garantizar la captura de todas las posibles excepciones. Por esta razón, creo que esto no debe hacerse en el código marco, sino solo en el código comercial que usted controla por completo.
Referencias
NOTA: Si decide utilizar esta técnica, puede copiar la
LambdaExceptionUtil
clase auxiliar de StackOverflow: https://stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-java-8 -corrientes . Le brinda la implementación completa (Función, Consumidor, Proveedor ...), con ejemplos.fuente
En este ejemplo, ¿puede realmente fallar alguna vez? No lo creas, pero quizás tu caso sea especial. Si lo que realmente "no puede fallar", y es sólo una cosa compilador molesto, me gusta envolver la excepción y lanzar una
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 por lo que YMMV. Probablemente obtendré algunos votos negativos.
fuente
clone
a un tipo selladoFoo
que se sabe que lo admite, y arrojaCloneNotSupportedException
. ¿Cómo podría suceder eso a menos que el código se vincule con algún otro tipo inesperadoFoo
? Y si eso sucede, ¿se puede confiar en algo?An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.
(Citado del Javadoc deError
) Esta es exactamente esa clase de situación, por lo tanto, lejos de ser inadecuada, lanzar unError
aquí es la opción más adecuada.otra versión de este código donde la excepción marcada solo se retrasa:
la magia es que si llamas:
entonces su código estará obligado a detectar la excepción
fuente
sneakyThrow es genial! Entiendo totalmente tu dolor.
Si tienes que quedarte en Java ...
Paguro tiene interfaces funcionales que envuelven 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 transductores Clojure (o Java 8 Streams) que toman las interfaces funcionales y envuelven excepciones comprobadas.
También hay VAVr y The Eclipse Collections para probar.
De lo contrario, use Kotlin
Kotlin es compatible de 2 vías con Java, no tiene excepciones comprobadas, no tiene primitivas (bueno, no es que el programador tenga que pensar), un mejor sistema de tipos, asume inmutabilidad, etc. A pesar de que selecciono la biblioteca de Paguro anterior, yo ' Estoy convirtiendo todo el código Java que puedo a Kotlin. Todo lo que solía hacer en Java ahora prefiero hacerlo en Kotlin.
fuente
Las excepciones marcadas son muy útiles y su manejo depende del tipo de producto del que forma parte:
Por lo general, una biblioteca no debe manejar excepciones comprobadas, sino declarar como arrojada por API pública. El raro caso en que podrían envolverse en RuntimeExcepton simple es, por ejemplo, crear dentro del código de la biblioteca algún documento xml privado y analizarlo, crear un archivo temporal y luego leerlo.
Para las aplicaciones de escritorio, debe comenzar el desarrollo del producto creando su propio marco de manejo de excepciones. Usando su propio marco, generalmente envolverá una excepción marcada en algunos de dos a cuatro envoltorios (sus subclases personalizadas de RuntimeExcepion) y las manejará con un solo controlador de excepciones.
Para los servidores, generalmente su código se ejecutará dentro de algún marco. Si no utiliza ninguno (un servidor básico), cree su propio marco similar (basado en tiempos de ejecución) descrito anteriormente.
Para ejercicios académicos, siempre puede envolverlos directamente en RuntimeExcepton. Alguien puede crear un pequeño marco también.
fuente
Es posible que desee echar un vistazo a este proyecto ; Lo creé específicamente debido al problema de las excepciones comprobadas y las interfaces funcionales.
Con él puedes hacer esto en lugar de escribir tu código personalizado:
Dado que todas esas interfaces Throwing * extienden sus contrapartes no Throwing, puede usarlas directamente en la secuencia:
O simplemente puedes:
Y otras cosas.
fuente
fields.forEach((ThrowingConsumer<Field>) f -> out.println(f.get(o)))