¿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
/ catches
a la secuencia.
throws ClassNotFoundException
declaració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 (
throws
clá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 E
tambié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
throwSomeMore
nos gustaría ver queIOException
se 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,
E
debe resolverse a un comúnsuper
deClassNotFoundException
yIOException
, 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 (
throws
cláusula). Entonces, el tipo de excepción informado por el compilador sería tan específico como lathrows
declaració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-catch
bloque dentro de la lambda, y eso simplemente no tiene ningún sentido. Tan pronto comoClass.forName
se 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
LambdaExceptionUtil
clase auxiliar le permite usar cualquier excepción marcada en las secuencias de Java, como esta:Nota
Class::forName
tirosClassNotFoundException
, 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
rethrow
métodos de laLambdaExceptionUtil
clase 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
uncheck
métodos de laLambdaExceptionUtil
clase 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
uncheck
mé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
uncheck
mé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
uncheck
mé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 atraparRuntimeException
para 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
Function
etc no hacenthrows
nada; 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 queIOException
se 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 algunasinstanceof
comprobaciones (u otra cosa con un propósito similar) para verificar qué excepción comprobada fue arrojada.¡Usted puede!
Extendiendo @marcg 's
UtilException
y agregandothrow E
donde 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
LambdaExceptionUtil
en 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 elInteger
tipo 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 miUtilException
respuesta en esta página y vea si le gusta la idea de agregar esta tercera posibilidad a su proyecto.Stream
implementaAutoCloseable
?MyException
necesario 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 Method
refactorizació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 envolverlaRuntimeException
y 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
map
operación es encapsularlas dentro de aCompletableFuture
. (UnaOptional
es 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
map
operación se haya completado con éxito. Así es como se ve:Esto produce el siguiente resultado:
El
applyOrDie
método toma unFunction
que arroja una excepción, y lo convierte en unFunction
que 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
map
operación ilustra que ahora tienes un enStream<CompletableFuture<T>>
lugar de solo unStream<T>
.CompletableFuture
solo 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
collect
fase, 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
, yfinisher
que no necesita saber nada en absoluto sobreCompletableFuture
.Los métodos auxiliares se pueden encontrar en GitHub en mi
MonadUtils
clase, 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
.throwUnchecked
funciona 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 noinstanceof
o se necesita algo.fuente
Creo que este enfoque es el correcto:
Envolviendo la excepción marcada dentro de
Callable
en 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