Tengo un problema para probar las expresiones Lambda de Java 8. Por lo general, funciona bien, pero ahora tengo métodos que arrojan IOException
. Es mejor si observa el siguiente código:
class Bank{
....
public Set<String> getActiveAccountNumbers() throws IOException {
Stream<Account> s = accounts.values().stream();
s = s.filter(a -> a.isActive());
Stream<String> ss = s.map(a -> a.getNumber());
return ss.collect(Collectors.toSet());
}
....
}
interface Account{
....
boolean isActive() throws IOException;
String getNumber() throws IOException;
....
}
El problema es que no se compila, porque tengo que detectar las posibles excepciones de los métodos isActive y getNumber-Methods. Pero incluso si utilizo explícitamente un try-catch-Block como se muestra a continuación, todavía no se compila porque no capto la excepción. Entonces, o hay un error en JDK, o no sé cómo atrapar estas excepciones.
class Bank{
....
//Doesn't compile either
public Set<String> getActiveAccountNumbers() throws IOException {
try{
Stream<Account> s = accounts.values().stream();
s = s.filter(a -> a.isActive());
Stream<String> ss = s.map(a -> a.getNumber());
return ss.collect(Collectors.toSet());
}catch(IOException ex){
}
}
....
}
¿Cómo puedo hacer que funcione? ¿Alguien puede indicarme la solución correcta?
java
exception-handling
lambda
java-8
Martin Weber
fuente
fuente
Respuestas:
Debe atrapar la excepción antes de que escape de la lambda:
Considere el hecho de que el lambda no se evalúa en el lugar donde lo escribe, sino en un lugar completamente no relacionado, dentro de una clase JDK. Entonces ese sería el punto donde se lanzaría esa excepción marcada, y en ese lugar no se declara.
Puede lidiar con esto utilizando un contenedor de su lambda que traduzca las excepciones marcadas a las no verificadas:
Tu ejemplo se escribiría como
En mis proyectos trato con este problema sin envolver; en su lugar, uso un método que desactiva efectivamente la comprobación de excepciones del compilador. Huelga decir que esto debe manejarse con cuidado y todos en el proyecto deben ser conscientes de que puede aparecer una excepción marcada donde no se declara. Este es el código de plomería:
y puede esperar recibir un
IOException
tiro en la cara, aunquecollect
no lo declare. En la mayoría de los casos de la vida real , pero no en todos , querrá volver a lanzar la excepción, de todos modos, y manejarla como una falla genérica. En todos esos casos, nada se pierde en claridad o corrección. Solo tenga cuidado con esos otros casos, en los que realmente desea reaccionar ante la excepción en el acto. El compilador no leIOException
informará al desarrollador que hay que atraparlo y, de hecho, el compilador se quejará si intenta atraparlo porque lo hemos engañado haciéndole creer que no se puede lanzar tal excepción.fuente
También puede propagar su dolor estático con lambdas, por lo que todo parece legible:
propagate
aquí recibejava.util.concurrent.Callable
como parámetro y convierte cualquier excepción detectada durante la llamada enRuntimeException
. Hay un método de conversión similar Throwables # propagate (Throwable) en Guava.Este método parece ser esencial para el encadenamiento del método lambda, por lo que espero que algún día se agregue a una de las bibliotecas populares o este comportamiento de propagación sería por defecto.
fuente
Esta
UtilException
clase auxiliar le permite usar cualquier excepción marcada en las secuencias de Java, como esta:Nota
Class::forName
tirosClassNotFoundException
, que está marcada . La secuencia 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
UtilException
):Pero no lo use antes de comprender las siguientes ventajas, desventajas y limitaciones :
• 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.
• 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 instrucció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 esté siempre presente. Aquí, la declaración de tiros es una molestia y cualquier solución para silenciarla con un mínimo estándar es bienvenida.
• Si odia las excepciones marcadas y siente que, para empezar, nunca deberían agregarse al lenguaje Java (cada vez más personas piensan de esta manera, y NO soy uno de ellos), entonces no agregue la excepción marcada a arroja la 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 resulta en un stacktrace 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:
1) El código de llamada no podrá capturarlo por su nombre (si lo intentas, el compilador dirá: la excepción nunca se arroja 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 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.
En conclusión: creo que las limitaciones aquí no son serias, y la
UtilException
clase se puede usar sin temor. Sin embargo, ¡depende de ti!fuente
Potencialmente, puede lanzar su propia
Stream
variante envolviendo su lambda para lanzar una excepción no verificada y luego desenvolviendo esa excepción no verificada en las operaciones de terminal:Entonces podría usar esto de la misma manera que
Stream
:Esta solución requeriría un poco de repetitivo, así que le sugiero que eche un vistazo a la biblioteca que ya hice, que hace exactamente lo que he descrito aquí para toda la
Stream
clase (¡y más!).fuente
new StreamBridge<>(ts, IOException.class);
->new StreamBridge<>(s, IOException.class);
StreamAdapter
.Use el método #propagate (). Muestra de implementación no guayaba de Java 8 Blog por Sam Beran :
fuente
Esto no responde directamente a la pregunta (hay muchas otras respuestas que sí lo hacen), pero intenta evitar el problema en primer lugar:
En mi experiencia, la necesidad de manejar excepciones en una
Stream
(u otra expresión lambda) a menudo proviene del hecho de que las excepciones se declaran lanzadas desde métodos donde no deberían lanzarse. Esto a menudo proviene de mezclar la lógica empresarial con la entrada y la salida. SuAccount
interfaz es un ejemplo perfecto:En lugar de lanzar un
IOException
sobre cada captador, considere este diseño:El método
AccountReader.readAccount(…)
podría leer una cuenta de una base de datos o un archivo o lo que sea y lanzar una excepción si no tiene éxito. Construye unAccount
objeto que ya contiene todos los valores, listo para ser utilizado. Como los valores ya han sido cargados porreadAccount(…)
, los captadores no lanzarían una excepción. Por lo tanto, puede usarlos libremente en lambdas sin la necesidad de envolver, enmascarar u ocultar las excepciones.Por supuesto, no siempre es posible hacerlo de la manera que describí, pero a menudo lo es y conduce a un código más limpio por completo (en mi humilde opinión):
throws IOException
para nada más que para satisfacer al compiladorAccount
inmutables y beneficiarse de sus ventajas (por ejemplo, seguridad de hilos)Account
en lambdas (por ejemplo, en aStream
)fuente
Se puede resolver mediante el siguiente código simple con Stream y Try en AbacusUtil :
Divulgación: Soy el desarrollador de
AbacusUtil
.fuente
Extendiendo la solución @marcg, normalmente puede lanzar y capturar una excepción marcada en Streams; es decir, el compilador le pedirá que atrape / vuelva a lanzar como si estuviera fuera de las transmisiones.
Luego, su ejemplo se escribiría de la siguiente manera (agregando pruebas para mostrarlo más claramente):
fuente
Para agregar correctamente el código de manejo IOException (a RuntimeException), su método se verá así:
El problema ahora es que
IOException
tendrá que ser capturado como aRuntimeException
y convertido de nuevo en unIOException
, y eso agregará aún más código al método anterior.¿Por qué usarlo
Stream
cuando se puede hacer así? Y el método arrojaIOException
así que no se necesita código adicional para eso también:fuente
Teniendo en cuenta este problema, desarrollé una pequeña biblioteca para tratar excepciones comprobadas y lambdas. Los adaptadores personalizados le permiten integrarse con los tipos funcionales existentes:
https://github.com/TouK/ThrowingFunction/
fuente
Su ejemplo se puede escribir como:
La clase Unthrow se puede tomar aquí https://github.com/SeregaLBN/StreamUnthrower
fuente
Si no le importa usar bibliotecas de terceros, la biblioteca cyclops-react de AOL , revelación :: Soy colaborador, tiene una clase ExceptionSoftener que puede ayudarlo aquí.
fuente
Las interfaces funcionales en Java no declaran ninguna excepción marcada o no marcada. Necesitamos cambiar la firma de los métodos de:
A:
O manejarlo con el bloque try-catch:
Otra opción es escribir un contenedor personalizado o usar una biblioteca como ThrowingFunction. Con la biblioteca solo necesitamos agregar la dependencia a nuestro pom.xml:
Y use las clases específicas como ThrowingFunction, ThrowingConsumer, ThrowingPredicate, ThrowingRunnable, ThrowingSupplier.
Al final el código se ve así:
fuente
No veo ninguna forma de manejar la excepción marcada en la secuencia (Java -8), la única forma en que apliqué es capturar la excepción marcada en la secuencia y volver a lanzarla como excepción no marcada.
fuente
Si desea manejar la excepción dentro de la transmisión y continuar procesando las adicionales, hay un excelente artículo en DZone de Brian Vermeer que utiliza el concepto de Either. Muestra una excelente manera de manejar esta situación. Lo único que falta es el código de muestra. Esta es una muestra de mi exploración usando los conceptos de ese artículo.
fuente