Java 8: Lambda-Streams, filtrar por método con excepción

Tengo problemas para probar las expresiones Lambda de Java 8. Por lo general, funciona bien, pero ahora tengo métodos que arrojan IOException . Lo mejor es que mires el siguiente código:

 class Bank{ .... public Set getActiveAccountNumbers() throws IOException { Stream s = accounts.values().stream(); s = s.filter(a -> a.isActive()); Stream 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 comstack, porque tengo que detectar las posibles excepciones de los métodos isActive y getNumber. Pero incluso si utilizo explícitamente un try-catch-Block como a continuación, todavía no se comstack porque no capturo la excepción. Entonces, o bien hay un error en JDK, o no sé cómo atrapar estas excepciones.

 class Bank{ .... //Doesn't compile either public Set getActiveAccountNumbers() throws IOException { try{ Stream s = accounts.values().stream(); s = s.filter(a -> a.isActive()); Stream ss = s.map(a -> a.getNumber()); return ss.collect(Collectors.toSet()); }catch(IOException ex){ } } .... } 

¿Cómo puedo hacer que funcione? ¿Puede alguien darme pistas sobre la solución correcta?

Debes atrapar la excepción antes de que escapa de la lambda:

 s = s.filter(a -> { try { return a.isActive(); } catch (IOException e) { throw new UncheckedIOException(e); }}}); 

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 en el que se arrojaría esa excepción marcada, y en ese lugar no se declara.

Puede manejarlo usando un contenedor de su lambda que traduce las excepciones comprobadas a las no marcadas:

 public static  T uncheckCall(Callable callable) { try { return callable.call(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } 

Tu ejemplo se escribirá como

 return s.filter(a -> uncheckCall(a::isActive)) .map(Account::getNumber) .collect(toSet()); 

En mis proyectos, trato este tema sin envolver; en su lugar, utilizo un método que desactiva eficazmente la verificación de excepciones por parte del comstackdor. Huelga decir que esto debe manejarse con cuidado y todos en el proyecto deben ser conscientes de que una excepción marcada puede aparecer donde no está declarada. Este es el código de fontanería:

 public static  T uncheckCall(Callable callable) { try { return callable.call(); } catch (Exception e) { return sneakyThrow(e); } } public static void uncheckRun(RunnableExc r) { try { r.run(); } catch (Exception e) { sneakyThrow(e); } } public interface RunnableExc { void run() throws Exception; } @SuppressWarnings("unchecked") private static  void sneakyThrow(Throwable t) throws T { throw (T) t; } 

y puede esperar obtener una IOException en su cara, aunque collect no la declare. En la mayoría, pero no en todos los casos de la vida real, le conviene 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 ten cuidado con esos otros casos, donde realmente querrías reactjsr a la excepción en el acto. El comstackdor no informará al desarrollador que hay una IOException que atrapará allí y, de hecho, el comstackdor se quejará si intenta atraparlo porque lo hemos engañado al creer que no se puede lanzar tal excepción.

También puede propagar su dolor estático con lambdas, por lo que todo parece legible:

 s.filter(a -> propagate(a::isActive)) 

propagate aquí recibe java.util.concurrent.Callable como un parámetro y convierte cualquier excepción capturada durante la llamada en RuntimeException . Hay un método de conversión similar Throwables # propagate (Throwable) en Guava.

Este método parece ser esencial para el encadenamiento de métodos lambda, así que espero que algún día se agregue a una de las bibliotecas populares o que este comportamiento de propagación sea por defecto.

 public class PropagateExceptionsSample { // a simplified version of Throwables#propagate public static RuntimeException runtime(Throwable e) { if (e instanceof RuntimeException) { return (RuntimeException)e; } return new RuntimeException(e); } // this is a new one, n/a in public libs // Callable just suits as a functional interface in JDK throwing Exception public static  V propagate(Callable callable){ try { return callable.call(); } catch (Exception e) { throw runtime(e); } } public static void main(String[] args) { class Account{ String name; Account(String name) { this.name = name;} public boolean isActive() throws IOException { return name.startsWith("a"); } } List accounts = new ArrayList<>(Arrays.asList(new Account("andrey"), new Account("angela"), new Account("pamela"))); Stream s = accounts.stream(); s .filter(a -> propagate(a::isActive)) .map(a -> a.name) .forEach(System.out::println); } } 

Esta clase UtilException helper le permite usar cualquier excepción marcada en las secuencias de Java, como esta:

 Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String") .map(rethrowFunction(Class::forName)) .collect(Collectors.toList()); 

Nota Class::forName arroja ClassNotFoundException , que está marcada . La secuencia en sí también arroja ClassNotFoundException , y NO una excepción que no se verifica.

 public final class UtilException { @FunctionalInterface public interface Consumer_WithExceptions { void accept(T t) throws E; } @FunctionalInterface public interface BiConsumer_WithExceptions { void accept(T t, U u) throws E; } @FunctionalInterface public interface Function_WithExceptions { R apply(T t) throws E; } @FunctionalInterface public interface Supplier_WithExceptions { T get() throws E; } @FunctionalInterface public interface Runnable_WithExceptions { void run() throws E; } /** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */ public static  Consumer rethrowConsumer(Consumer_WithExceptions consumer) throws E { return t -> { try { consumer.accept(t); } catch (Exception exception) { throwAsUnchecked(exception); } }; } public static  BiConsumer rethrowBiConsumer(BiConsumer_WithExceptions biConsumer) throws E { return (t, u) -> { try { biConsumer.accept(t, u); } catch (Exception exception) { throwAsUnchecked(exception); } }; } /** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */ public static  Function rethrowFunction(Function_WithExceptions function) throws E { return t -> { try { return function.apply(t); } catch (Exception exception) { throwAsUnchecked(exception); return null; } }; } /** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */ public static  Supplier rethrowSupplier(Supplier_WithExceptions function) throws E { return () -> { try { return function.get(); } catch (Exception exception) { throwAsUnchecked(exception); return null; } }; } /** uncheck(() -> Class.forName("xxx")); */ public static void uncheck(Runnable_WithExceptions t) { try { t.run(); } catch (Exception exception) { throwAsUnchecked(exception); } } /** uncheck(() -> Class.forName("xxx")); */ public static  R uncheck(Supplier_WithExceptions supplier) { try { return supplier.get(); } catch (Exception exception) { throwAsUnchecked(exception); return null; } } /** uncheck(Class::forName, "xxx"); */ public static  R uncheck(Function_WithExceptions function, T t) { try { return function.apply(t); } catch (Exception exception) { throwAsUnchecked(exception); return null; } } @SuppressWarnings ("unchecked") private static  void throwAsUnchecked(Exception exception) throws E { throw (E)exception; } } 

Muchos otros ejemplos sobre cómo usarlo (después de importar UtilException ):

 @Test public void test_Consumer_with_checked_exceptions() throws IllegalAccessException { Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String") .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className)))); Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String") .forEach(rethrowConsumer(System.out::println)); } @Test public void test_Function_with_checked_exceptions() throws ClassNotFoundException { List classes1 = Stream.of("Object", "Integer", "String") .map(rethrowFunction(className -> Class.forName("java.lang." + className))) .collect(Collectors.toList()); List classes2 = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String") .map(rethrowFunction(Class::forName)) .collect(Collectors.toList()); } @Test public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException { Collector.of( rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), StringJoiner::add, StringJoiner::merge, StringJoiner::toString); } @Test public void test_uncheck_exception_thrown_by_method() { Class clazz1 = uncheck(() -> Class.forName("java.lang.String")); Class clazz2 = uncheck(Class::forName, "java.lang.String"); } @Test (expected = ClassNotFoundException.class) public void test_if_correct_exception_is_still_thrown_by_method() { Class clazz3 = uncheck(Class::forName, "INVALID"); } 

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 agregarla a la cláusula throws del método que contiene la transmisión. El comstackdor no lo obligará a agregarlo más, por lo que es más fácil olvidarlo.

• Si el código de llamada ya maneja la excepción marcada, el comstackdor LE RECORDARÁ que agregue la cláusula throws a la statement del método que contiene la secuencia (si no lo hace dirá: Exception nunca se arroja en el cuerpo de la instrucción Try correspondiente )

• En cualquier caso, no podrá rodear la secuencia en sí para capturar la excepción marcada DENTRO del método que contiene la secuencia (si lo intenta, el comstackdor dirá: La excepción nunca se arroja en el cuerpo de la statement 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 throws. Por ejemplo: new String (byteArr, “UTF-8”) arroja UnsupportedEncodingException, pero UTF-8 está garantizado por las especificaciones de Java para estar siempre presente. Aquí, la statement de los lanzamientos es una molestia y cualquier solución para silenciarla con un mínimo es bienvenido.

• Si odias verificaciones de excepciones y crees que nunca deberían agregarse al lenguaje Java, para empezar (un número creciente de personas piensa de esta manera, y yo NO soy uno de ellos), simplemente no agregues la excepción marcada al arroja la cláusula del método que contiene la secuencia. La excepción marcada se comportará, entonces, como una excepción no seleccionada.

• Si está implementando una interfaz estricta donde no tiene la opción de agregar una statement de lanzamientos, y sin embargo, lanzar una excepción es completamente apropiado, entonces al envolver una excepción solo para obtener el privilegio de lanzarla se genera una stack con excepciones espúreas 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 olvida 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á atraparlo por su nombre (si lo intenta, el comstackdor dirá: La excepción nunca se arroja en el cuerpo de la statement try correspondiente). Burbujeará y probablemente quede atrapado en el bucle principal del progtwig por alguna “excepción de captura” o “captura Throwable”, que puede ser lo que quieras de todos modos.

2) Viola el principio de menor sorpresa: ya no será suficiente capturar RuntimeException para poder garantizar la captura de todas las excepciones posibles. Por esta razón, creo que esto no debería hacerse en el código de la estructura, sino solo en el código comercial que usted controla completamente.

En conclusión: creo que las limitaciones aquí no son serias, y la clase UtilException se puede usar sin miedo. Sin embargo, depende de usted!

Usted puede potencialmente lanzar su propia variante de Stream envolviendo su lambda para lanzar una excepción no verificada y luego desenvolver esa excepción no verificada en las operaciones de la terminal:

 @FunctionalInterface public interface ThrowingPredicate { public boolean test(T t) throws X; } @FunctionalInterface public interface ThrowingFunction { public R apply(T t) throws X; } @FunctionalInterface public interface ThrowingSupplier { public R get() throws X; } public interface ThrowingStream { public ThrowingStream filter( ThrowingPredicate< ? super T, ? extends X> predicate); public  ThrowingStream map( ThrowingFunction< ? super T, ? extends R, ? extends X> mapper); public  R collect(Collector< ? super T, A, R> collector) throws X; // etc } class StreamAdapter implements ThrowingStream { private static class AdapterException extends RuntimeException { public AdapterException(Throwable cause) { super(cause); } } private final Stream delegate; private final Class x; StreamAdapter(Stream delegate, Class x) { this.delegate = delegate; this.x = x; } private  R maskException(ThrowingSupplier method) { try { return method.get(); } catch (Throwable t) { if (x.isInstance(t)) { throw new AdapterException(t); } else { throw t; } } } @Override public ThrowingStream filter(ThrowingPredicate predicate) { return new StreamAdapter<>( delegate.filter(t -> maskException(() -> predicate.test(t))), x); } @Override public  ThrowingStream map(ThrowingFunction mapper) { return new StreamAdapter<>( delegate.map(t -> maskException(() -> mapper.apply(t))), x); } private  R unmaskException(Supplier method) throws X { try { return method.get(); } catch (AdapterException e) { throw x.cast(e.getCause()); } } @Override public  R collect(Collector collector) throws X { return unmaskException(() -> delegate.collect(collector)); } } 

Entonces podrías usar esto de la misma manera que un Stream :

 Stream s = accounts.values().stream(); ThrowingStream ts = new StreamAdapter<>(s, IOException.class); return ts.filter(Account::isActive).map(Account::getNumber).collect(toSet()); 

Esta solución requeriría bastante repetitivo, por lo que le sugiero que eche un vistazo a la biblioteca que ya hice, que hace exactamente lo que he descrito aquí para toda la clase Stream (¡y más!).

Use el método #propagate (). Ejemplo de implementación no Guava del blog de Java 8 por Sam Beran :

 public class Throwables { public interface ExceptionWrapper { E wrap(Exception e); } public static  T propagate(Callable callable) throws RuntimeException { return propagate(callable, RuntimeException::new); } public static  T propagate(Callable callable, ExceptionWrapper wrapper) throws E { try { return callable.call(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw wrapper.wrap(e); } } } 

Se puede resolver por debajo del código simple con Stream y prueba en AbacusUtil :

 Stream.of(accounts).filter(a -> Try.call(a::isActive)).map(a -> Try.call(a::getNumber)).toSet(); 

Divulgación: soy el desarrollador de AbacusUtil .

Para agregar correctamente el código de manejo IOException (a RuntimeException), su método se verá así:

 Stream s = accounts.values().stream(); s = s.filter(a -> { try { return a.isActive(); } catch (IOException e) { throw new RuntimeException(e); }}); Stream ss = s.map(a -> { try { return a.getNumber() } catch (IOException e) { throw new RuntimeException(e); }}); return ss.collect(Collectors.toSet()); 

El problema ahora es que la IOException tendrá que ser capturada como una RuntimeException y convertida nuevamente a una IOException , y eso agregará aún más código al método anterior.

¿Por qué usar Stream cuando se puede hacer así? Y el método arroja IOException para que no se necesite código adicional para eso también:

 Set set = new HashSet<>(); for(Account a: accounts.values()){ if(a.isActive()){ set.add(a.getNumber()); } } return set; 

Extendiendo la solución @marcg, normalmente puedes lanzar y atrapar una excepción marcada en Streams; es decir, el comstackdor le pedirá que capture / vuelva a tirar como estaba fuera de las transmisiones.

 @FunctionalInterface public interface Predicate_WithExceptions { boolean test(T t) throws E; } /** * .filter(rethrowPredicate(t -> t.isActive())) */ public static  Predicate rethrowPredicate(Predicate_WithExceptions predicate) throws E { return t -> { try { return predicate.test(t); } catch (Exception exception) { return throwActualException(exception); } }; } @SuppressWarnings("unchecked") private static  T throwActualException(Exception exception) throws E { throw (E) exception; } 

Luego, su ejemplo se escribiría de la siguiente manera (agregando pruebas para mostrarlo más claramente):

 @Test public void testPredicate() throws MyTestException { List nonEmptyStrings = Stream.of("ciao", "") .filter(rethrowPredicate(s -> notEmpty(s))) .collect(toList()); assertEquals(1, nonEmptyStrings.size()); assertEquals("ciao", nonEmptyStrings.get(0)); } private class MyTestException extends Exception { } private boolean notEmpty(String value) throws MyTestException { if(value==null) { throw new MyTestException(); } return !value.isEmpty(); } @Test public void testPredicateRaisingException() throws MyTestException { try { Stream.of("ciao", null) .filter(rethrowPredicate(s -> notEmpty(s))) .collect(toList()); fail(); } catch (MyTestException e) { //OK } } 

Esto no responde directamente a la pregunta (hay muchas otras respuestas que sí lo hacen), pero trata de evitar el problema en primer lugar:

En mi experiencia, la necesidad de manejar excepciones en un Stream (u otra expresión lambda) a menudo se debe al hecho de que las excepciones se declaran arrojadas por métodos donde no deberían arrojarse. Esto a menudo proviene de la mezcla de la lógica comercial con el ingreso y la salida. La interfaz de su Account es un ejemplo perfecto:

 interface Account { boolean isActive() throws IOException; String getNumber() throws IOException; } 

En lugar de arrojar una IOException en cada getter, considere este diseño:

 interface AccountReader { Account readAccount(…) throws IOException; } interface Account { boolean isActive(); String getNumber(); } 

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 un objeto Account que ya contiene todos los valores, listo para ser utilizado. Como los valores ya han sido cargados por readAccount(…) , los getters no generarán una excepción. Por lo tanto, puedes 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 totalmente limpio (en mi humilde opinión):

  • Mejor separación de preocupaciones y siguiendo el principio de responsabilidad única
  • Menos repetitivo: no tiene que saturar su código con throws IOException para no usarlo, sino para satisfacer al comstackdor
  • Manejo de errores: Usted maneja los errores donde ocurren, cuando lee de un archivo o base de datos, en lugar de en algún lugar en el medio de su lógica de negocio, solo porque quiere obtener un valor de campo
  • Puede hacer que la Account inmutable y se beneficie de las ventajas de la misma (por ejemplo, seguridad de la secuencia)
  • No necesita “trucos sucios” o soluciones para usar la Account en lambdas (por ejemplo, en un Stream )

Teniendo esto en cuenta, desarrollé una pequeña biblioteca para tratar con excepciones comprobadas y lambdas. Los adaptadores personalizados le permiten integrarse con los tipos funcionales existentes:

 stream().map(unchecked(URI::new)) //with a static import 

https://github.com/TouK/ThrowingFunction/

Tu ejemplo puede escribirse como:

 import utils.stream.Unthrow; class Bank{ .... public Set getActiveAccountNumbers() { return accounts.values().stream() .filter(a -> Unthrow.wrap(() -> a.isActive())) .map(a -> Unthrow.wrap(() -> a.getNumber())) .collect(Collectors.toSet()); } .... } 

La clase Unthrow se puede tomar aquí https://github.com/SeregaLBN/StreamUnthrower

Si no te importa utilizar bibliotecas de terceros, AOL’s cyclops-react lib, disclosure :: Soy colaborador, tiene una clase ExceptionSoftener que puede ayudar aquí.

  s.filter(softenPredicate(a->a.isActive()));