usar una puede lambdas funciones expresiones explicacion ejemplos casos anonimas java exception-handling lambda java-8

una - java 8 lambdas pdf



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

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<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 compila, 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 compila 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<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? ¿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> T uncheckCall(Callable<T> 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 compilador. 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> T uncheckCall(Callable<T> 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 <T extends Throwable> 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 reaccionar a la excepción en el acto. El compilador no informará al desarrollador que hay una IOException que atrapará allí y, de hecho, el compilador se quejará si intenta atraparlo porque lo hemos engañado al creer que no se puede lanzar tal excepción.


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<T, E extends Exception> { void accept(T t) throws E; } @FunctionalInterface public interface BiConsumer_WithExceptions<T, U, E extends Exception> { void accept(T t, U u) throws E; } @FunctionalInterface public interface Function_WithExceptions<T, R, E extends Exception> { R apply(T t) throws E; } @FunctionalInterface public interface Supplier_WithExceptions<T, E extends Exception> { T get() throws E; } @FunctionalInterface public interface Runnable_WithExceptions<E extends Exception> { void run() throws E; } /** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */ public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E { return t -> { try { consumer.accept(t); } catch (Exception exception) { throwAsUnchecked(exception); } }; } public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> 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 <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> 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 <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> 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, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier) { try { return supplier.get(); } catch (Exception exception) { throwAsUnchecked(exception); return null; } } /** uncheck(Class::forName, "xxx"); */ public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) { try { return function.apply(t); } catch (Exception exception) { throwAsUnchecked(exception); return null; } } @SuppressWarnings ("unchecked") private static <E extends Throwable> 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<Class> classes1 = Stream.of("Object", "Integer", "String") .map(rethrowFunction(className -> Class.forName("java.lang." + className))) .collect(Collectors.toList()); List<Class> 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 compilador 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 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á: 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 compilador dirá: La excepción nunca se arroja en el cuerpo de la declaración 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 declaración 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 declaración 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 pila 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 compilador dirá: La excepción nunca se arroja en el cuerpo de la declaración try correspondiente). Burbujeará y probablemente quede atrapado en el bucle principal del programa 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!


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 compilador
  • 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 immutable 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 )

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

@FunctionalInterface public interface Predicate_WithExceptions<T, E extends Exception> { boolean test(T t) throws E; } /** * .filter(rethrowPredicate(t -> t.isActive())) */ public static <T, E extends Exception> Predicate<T> rethrowPredicate(Predicate_WithExceptions<T, E> predicate) throws E { return t -> { try { return predicate.test(t); } catch (Exception exception) { return throwActualException(exception); } }; } @SuppressWarnings("unchecked") private static <T, E extends Exception> 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<String> 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 } }


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

Stream<Account> s = accounts.values().stream(); s = s.filter(a -> { try { return a.isActive(); } catch (IOException e) { throw new RuntimeException(e); }}); Stream<String> 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<String> set = new HashSet<>(); for(Account a: accounts.values()){ if(a.isActive()){ set.add(a.getNumber()); } } return set;


Se puede resolver por debajo del código simple con Stream y Try 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 .


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()));


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> V propagate(Callable<V> 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<Account> accounts = new ArrayList<>(Arrays.asList(new Account("andrey"), new Account("angela"), new Account("pamela"))); Stream<Account> s = accounts.stream(); s .filter(a -> propagate(a::isActive)) .map(a -> a.name) .forEach(System.out::println); } }


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<String> 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


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> { E wrap(Exception e); } public static <T> T propagate(Callable<T> callable) throws RuntimeException { return propagate(callable, RuntimeException::new); } public static <T, E extends Throwable> T propagate(Callable<T> callable, ExceptionWrapper<E> wrapper) throws E { try { return callable.call(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw wrapper.wrap(e); } } }


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

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

Stream<Account> s = accounts.values().stream(); ThrowingStream<Account, IOException> 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!).