¿Cómo puedo lanzar excepciones CHECKED desde Java 8 streams / lambdas?
En otras palabras, quiero hacer un código como este compilar:
public List<Class> getClasses() throws ClassNotFoundException {
List<Class> classes =
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(className -> Class.forName(className))
.collect(Collectors.toList());
return classes;
}
Este código no se compila, ya que el Class.forName()método anterior arroja ClassNotFoundException, que está marcado.
Tenga en cuenta que NO quiero envolver la excepción marcada dentro de una excepción de tiempo de ejecución y en su lugar lanzar la excepción envuelta sin marcar. Quiero lanzar la excepción marcada , y sin agregar feo try/ catchesa la secuencia.

throws ClassNotFoundExceptiondeclaración en el método que contiene la secuencia, para que el código de llamada espere y se le permita capturar la excepción marcada.Respuestas:
La respuesta simple a su pregunta es: no puede, al menos no directamente. Y no es tu culpa. Oracle lo estropeó. Se aferran al concepto de excepciones comprobadas, pero inconsistentemente se olvidaron de cuidar las excepciones comprobadas cuando diseñaron las interfaces funcionales, flujos, lambda, etc.
En mi opinión, este es un gran error en la API y un error menor en la especificación del lenguaje .
El error en la API es que no proporciona ninguna facilidad para reenviar excepciones comprobadas en las que esto realmente tendría mucho sentido para la programación funcional. Como demostraré a continuación, tal instalación habría sido fácilmente posible.
El error en la especificación del lenguaje es que no permite que un parámetro de tipo infiera una lista de tipos en lugar de un solo tipo, siempre que el parámetro de tipo solo se use en situaciones en las que una lista de tipos es permisible (
throwscláusula).Nuestra expectativa como programadores de Java es que se compile el siguiente código:
Sin embargo, da:
La forma en que se definen las interfaces funcionales impide actualmente el compilador de reenvío de la excepción - no hay ninguna declaración que diga
Stream.map()que siFunction.apply() throws E,Stream.map() throws Etambién.Lo que falta es una declaración de un parámetro de tipo para pasar por excepciones comprobadas. El siguiente código muestra cómo dicho parámetro de tipo de paso en realidad podría haberse declarado con la sintaxis actual. Excepto por el caso especial en la línea marcada, que es un límite que se analiza a continuación, este código se compila y se comporta como se esperaba.
En el caso de que
throwSomeMorenos gustaría ver queIOExceptionse nos extrañe, pero en realidad se pierdeException.Esto no es perfecto porque la inferencia de tipos parece estar buscando un solo tipo, incluso en el caso de excepciones. Debido a que la inferencia de tipo necesita un solo tipo,
Edebe resolverse a un comúnsuperdeClassNotFoundExceptionyIOException, que esException.Se necesita un ajuste en la definición de inferencia de tipos para que el compilador busque múltiples tipos si el parámetro de tipo se usa donde una lista de tipos es permisible (
throwscláusula). Entonces, el tipo de excepción informado por el compilador sería tan específico como lathrowsdeclaración original de las excepciones comprobadas del método referenciado, no un solo supertipo general.La mala noticia es que esto significa que Oracle lo estropeó. Ciertamente no romperán el código de aterrizaje del usuario, pero la introducción de parámetros de tipo de excepción a las interfaces funcionales existentes interrumpiría la compilación de todo el código de aterrizaje del usuario que usa estas interfaces explícitamente. Tendrán que inventar un nuevo azúcar de sintaxis para solucionar esto.
La peor noticia es que Brian Goetz ya discutió este tema en 2010 https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java (nuevo enlace: http://mail.openjdk.java.net/pipermail/lambda -dev / 2010-June / 001484.html ), pero estoy informado de que esta investigación finalmente no funcionó, y que no hay ningún trabajo actual en Oracle que conozca para mitigar las interacciones entre las excepciones comprobadas y las lambdas.
fuente
try-catchbloque dentro de la lambda, y eso simplemente no tiene ningún sentido. Tan pronto comoClass.forNamese usa de alguna manera en la lambda, por ejemplonames.forEach(Class::forName), en , el problema está ahí. Básicamente, los métodos que arrojan excepciones comprobadas han sido excluidos de participar en la programación funcional como interfaces funcionales directamente, por diseño (¡pobre!).Esta
LambdaExceptionUtilclase auxiliar le permite usar cualquier excepción marcada en las secuencias de Java, como esta:Nota
Class::forNametirosClassNotFoundException, que está marcada . La transmisión en sí también se lanzaClassNotFoundException, y NO alguna excepción de ajuste sin marcar.Muchos otros ejemplos sobre cómo usarlo (después de importar estáticamente
LambdaExceptionUtil):NOTA 1: Los
rethrowmétodos de laLambdaExceptionUtilclase anterior se pueden usar sin temor, y se pueden usar en cualquier situación . Muchas gracias al usuario @PaoloC que ayudó a resolver el último problema: ahora el compilador le pedirá que agregue cláusulas de lanzamiento y todo es como si pudiera lanzar excepciones comprobadas de forma nativa en las transmisiones de Java 8.NOTA 2: Los
uncheckmétodos de laLambdaExceptionUtilclase anterior son métodos de bonificación y pueden eliminarse de forma segura de la clase si no desea usarlos. Si los usó, hágalo con cuidado y no antes de comprender los siguientes casos de uso, ventajas / desventajas y limitaciones:• Puede usar los
uncheckmétodos si está llamando a un método que literalmente nunca puede lanzar la excepción que declara. Por ejemplo: new String (byteArr, "UTF-8") produce UnsupportedEncodingException, pero la especificación de Java garantiza que UTF-8 esté siempre presente. Aquí, la declaración de tiros es una molestia y cualquier solución para silenciarla con un mínimo repetitivo es bienvenida:String text = uncheck(() -> new String(byteArr, "UTF-8"));• Puede usar los
uncheckmétodos si está implementando una interfaz estricta en la que no tiene la opción de agregar una declaración de lanzamiento y, sin embargo, lanzar una excepción es completamente apropiado. Ajustar una excepción solo para obtener el privilegio de lanzarla 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 cualquier caso, si decide utilizar los
uncheckmétodos, tenga en cuenta estas 2 consecuencias de lanzar excepciones CHECKED sin una cláusula throws: 1) El código de llamada no podrá capturarlo por su nombre (si lo intenta, el el compilador dirá: la excepción nunca se lanza en el cuerpo de la declaració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. 2) Viola el principio de la menor sorpresa: ya no será suficiente atraparRuntimeExceptionpara poder 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.fuente
@SuppressWarnings ("unchecked")truco del compilador es completamente inaceptable.No puedes hacer esto de manera segura. Puedes hacer trampa, pero luego tu programa está roto y esto inevitablemente volverá a morder a alguien (deberías ser tú, pero a menudo nuestra trampa explota en otra persona).
Aquí hay una forma un poco más segura de hacerlo (pero todavía no lo recomiendo).
Aquí, lo que está haciendo es captar la excepción en el lambda, arrojar una señal fuera de la tubería de transmisión que indica que el cálculo falló excepcionalmente, captar la señal y actuar sobre esa señal para lanzar la excepción subyacente. La clave es que siempre está capturando la excepción sintética, en lugar de permitir que se filtre una excepción marcada sin declarar que se produce esa excepción.
fuente
Functionetc no hacenthrowsnada; Tengo curiosidad.throw w.cause;no haría que el compilador se quejara de que el método no arroja ni atrapaThrowable? Por lo tanto, es probable queIOExceptionse necesite un yeso allí. Además, si la lambda arroja más de un tipo de excepción marcada, el cuerpo de la captura se volvería algo feo con algunasinstanceofcomprobaciones (u otra cosa con un propósito similar) para verificar qué excepción comprobada fue arrojada.¡Usted puede!
Extendiendo @marcg 's
UtilExceptiony agregandothrow Edonde sea necesario: de esta manera, el compilador le pedirá que agregue cláusulas de lanzamiento y todo es como si pudiera lanzar excepciones marcadas de forma nativa en las transmisiones de java 8.Instrucciones: simplemente copie / pegue
LambdaExceptionUtilen su IDE y luego úselo como se muestra a continuaciónLambdaExceptionUtilTest.Algunas pruebas para mostrar el uso y el comportamiento:
fuente
<Integer>antesmap. De hecho, el compilador de Java no puede inferir elIntegertipo de retorno. Todo lo demás debe ser correcto.Exception.Simplemente use cualquiera de NoException (mi proyecto), jOOλ's Unchecked , throwing-lambdas , Throwable interfaces o Faux Pas .
fuente
Escribí una biblioteca que extiende la API Stream para permitirle lanzar excepciones marcadas. Utiliza el truco de Brian Goetz.
Tu código se convertiría
fuente
Esta respuesta es similar a 17 pero evitando la definición de excepción de contenedor:
fuente
No puedes.
Sin embargo, es posible que desee echar un vistazo a uno de mis proyectos que le permite manipular más fácilmente tales "arrojar lambdas".
En su caso, podría hacer eso:
y atrapar
MyException.Ese es un ejemplo. Otro ejemplo es que podría tener
.orReturn()algún valor predeterminado.Tenga en cuenta que esto TODAVÍA es un trabajo en progreso, hay más por venir. Mejores nombres, más funciones, etc.
fuente
.orThrowChecked()método a su proyecto que permita que se arroje la excepción marcada. . Mire miUtilExceptionrespuesta en esta página y vea si le gusta la idea de agregar esta tercera posibilidad a su proyecto.StreamimplementaAutoCloseable?MyExceptionnecesario que lo anterior sea una excepción no verificada?Resumiendo los comentarios anteriores, la solución avanzada es utilizar un contenedor especial para funciones no verificadas con un generador como API que proporciona recuperación, relanzamiento y supresión.
El siguiente código lo demuestra para las interfaces de consumidor, proveedor y función. Se puede ampliar fácilmente. Algunas palabras clave públicas se eliminaron para este ejemplo.
Class Try es el punto final para el código del cliente. Los métodos seguros pueden tener un nombre único para cada tipo de función. CheckedConsumer , CheckedSupplier y CheckedFunction son análogos comprobados de las funciones lib que se pueden usar independientemente de Try
CheckedBuilder es la interfaz para manejar excepciones en alguna función marcada. or Try permite ejecutar otra función del mismo tipo si la anterior fallaba. El manejador proporciona manejo de excepciones, incluido el filtrado de tipo de excepción El orden de los manejadores es importante. Reduzca los métodos inseguros y vuelva a lanzar la última excepción en la cadena de ejecución. Reduzca los métodos orElse y orElseGet devuelven valores alternativos como los opcionales si todas las funciones fallaron. También hay un método de supresión . CheckedWrapper es la implementación común de CheckedBuilder.
fuente
TL; DR Solo usa Lombok's
@SneakyThrows.Christian Hujer ya ha explicado en detalle por qué no es posible lanzar excepciones comprobadas de una transmisión, estrictamente hablando, debido a las limitaciones de Java.
Algunas otras respuestas han explicado trucos para sortear las limitaciones del lenguaje, pero aún así pueden cumplir con el requisito de lanzar "la excepción marcada en sí, y sin agregar feos intentos / capturas a la secuencia" , algunos de ellos requieren decenas de líneas adicionales de repetitivo.
Voy a destacar otra opción para hacer esto, en mi humilde opinión es mucho más limpia que todas las demás: la de Lombok
@SneakyThrows. Se ha mencionado al pasar por otras respuestas, pero estaba un poco enterrado bajo muchos detalles innecesarios.El código resultante es tan simple como:
Solo necesitábamos una
Extract Methodrefactorización (realizada por el IDE) y una línea adicional para@SneakyThrows. La anotación se encarga de agregar toda la plantilla para asegurarse de que puede lanzar su excepción marcada sin envolverlaRuntimeExceptiony sin tener que declararla explícitamente.fuente
También puede escribir un método de contenedor para ajustar excepciones no verificadas, e incluso mejorar el contenedor con parámetros adicionales que representen otra interfaz funcional (con el mismo tipo de retorno R ). En este caso, puede pasar una función que se ejecutará y devolverá en caso de excepciones. Ver ejemplo a continuación:
fuente
Aquí hay una vista o solución diferente para el problema original. Aquí muestro que tenemos una opción para escribir un código que procesará solo un subconjunto válido de valores con una opción para detectar y manejar casos cuando se produjo la excepción.
fuente
Estoy de acuerdo con los comentarios anteriores, al usar Stream.map está limitado a implementar una función que no arroje excepciones.
Sin embargo, puede crear su propia FunctionalInterface que se muestra como se muestra a continuación.
luego impleméntelo usando Lambdas o referencias como se muestra a continuación.
fuente
La única forma integrada de manejar las excepciones comprobadas que puede generar una
mapoperación es encapsularlas dentro de aCompletableFuture. (UnaOptionales una alternativa más simple si no necesita preservar la excepción). Estas clases están destinadas a permitirle representar operaciones contingentes de manera funcional.Se requieren un par de métodos auxiliares no triviales, pero puede llegar a un código que sea relativamente conciso, al tiempo que hace evidente que el resultado de su flujo depende de que la
mapoperación se haya completado con éxito. Así es como se ve:Esto produce el siguiente resultado:
El
applyOrDiemétodo toma unFunctionque arroja una excepción, y lo convierte en unFunctionque devuelve un ya completadoCompletableFuture, ya sea completado normalmente con el resultado de la función original o completado excepcionalmente con la excepción lanzada.La segunda
mapoperación ilustra que ahora tienes un enStream<CompletableFuture<T>>lugar de solo unStream<T>.CompletableFuturesolo se encarga de ejecutar esta operación si la operación ascendente tuvo éxito. El API lo hace explícito, pero relativamente indoloro.Hasta que llegues a la
collectfase, eso es. Aquí es donde requerimos un método auxiliar bastante significativo. Queremos "levantar" una operación normal de recogida (en este caso,toList()) "dentro" de laCompletableFuture-cfCollector()nos permite hacer que el uso de unasupplier,accumulator,combiner, yfinisherque no necesita saber nada en absoluto sobreCompletableFuture.Los métodos auxiliares se pueden encontrar en GitHub en mi
MonadUtilsclase, que todavía es un trabajo en progreso.fuente
Probablemente, una forma mejor y más funcional es envolver excepciones y propagarlas aún más en la secuencia. Echar un vistazo a la Trata tipo de Vavr por ejemplo.
Ejemplo:
O
La segunda implementación evita envolver la excepción en a
RuntimeException.throwUncheckedfunciona porque casi siempre todas las excepciones genéricas se tratan como no marcadas en java.fuente
Yo uso este tipo de excepción de envoltura:
Requerirá manejar estas excepciones estáticamente:
Pruébalo en línea!
Aunque la excepción se volverá a lanzar de todos modos durante la primera
rethrow()llamada (oh, genéricos de Java ...), de esta manera se obtiene una definición estática estricta de posibles excepciones (se requiere declararlasthrows). Y noinstanceofo se necesita algo.fuente
Creo que este enfoque es el correcto:
Envolviendo la excepción marcada dentro de
Callableen unUndeclaredThrowableException(ese es el caso de uso para esta excepción) y desenvolviéndola afuera.Sí, lo encuentro feo, y recomendaría no usar lambdas en este caso y simplemente recurrir a un buen ciclo anterior, a menos que esté trabajando con una secuencia paralela y la paralelización traiga un beneficio objetivo que justifique la imposibilidad de leer el código.
Como muchos otros han señalado, hay soluciones a esta situación, y espero que una de ellas la convierta en una versión futura de Java.
fuente