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

168

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?

Martin Weber
fuente
1
Relacionado: stackoverflow.com/questions/31637892/…
Marko Topolnik el
44
La respuesta simple y correcta: captura la excepción dentro de la lambda.
Brian Goetz

Respuestas:

211

Debe atrapar la excepción antes de que escape 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 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:

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ía como

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

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:

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 recibir un IOExceptiontiro en la cara, aunque collectno 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 le IOExceptioninformará 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.

Marko Topolnik
fuente
44
He visto a NettyIO hacer el "lanzamiento furtivo" antes, y quería tirar mi silla por la ventana. "¿Qué? ¿De dónde se filtró esa excepción comprobada?" Este es el primer caso de uso legítimo para el lanzamiento furtivo que he visto todavía. Como programador, debes estar atento para indicar que es posible un lanzamiento furtivo. ¿Quizás sea mejor crear otra interfaz de transmisión / impl que admita una excepción comprobada?
kevinarpe
8
Ninguna API sensata debe entregar excepciones comprobadas no declaradas al cliente, eso es seguro. Sin embargo, dentro de la API puede entenderse que la excepción verificada puede filtrarse. No causan daño siempre que sean solo otro signo de falla general y estén a punto de ser atrapados y manejados al por mayor.
Marko Topolnik el
55
@kevinarpe Esta es la razón exacta por la cual los lanzamientos furtivos son una mala idea. Cortocircuitar el compilador seguramente confundirá a los futuros mantenedores.
Thorbjørn Ravn Andersen
29
El hecho de que no le gusten las reglas no significa que sea una buena idea tomar la ley en sus propias manos. Su consejo es irresponsable porque coloca la conveniencia del escritor de código sobre las consideraciones mucho más importantes de transparencia y facilidad de mantenimiento del programa.
Brian Goetz
34
@brian El hecho de que algo sea una regla no significa que sea una buena idea. Pero me sorprende que se refiera a la segunda parte de mi respuesta como "consejo", ya que pensé que había hecho evidente lo que propongo como solución y lo que ofrezco como FYI al lector interesado, con profusas renuncias.
Marko Topolnik
29

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

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

propagateaquí recibe java.util.concurrent.Callablecomo parámetro y convierte cualquier excepción detectada 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 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.

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);
    }
}
Andrey Chaschev
fuente
22

Esta UtilExceptionclase auxiliar 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::forNametiros ClassNotFoundException, que está marcada . La secuencia en sí también se lanza ClassNotFoundException, y NO alguna excepción de ajuste sin marcar.

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 estáticamente 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 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 UtilExceptionclase se puede usar sin temor. Sin embargo, ¡depende de ti!

MarcG
fuente
8

Potencialmente, puede lanzar su propia Streamvariante envolviendo su lambda para lanzar una excepción no verificada y luego desenvolviendo esa excepción no verificada en las operaciones de 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ía usar esto de la misma manera que 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 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 Streamclase (¡y más!).

Jeffrey
fuente
Hola ... pequeño bicho? new StreamBridge<>(ts, IOException.class);->new StreamBridge<>(s, IOException.class);
kevinarpe
1
@kevinarpe Sí. También debería haber dicho StreamAdapter.
Jeffrey
5

Use el método #propagate (). Muestra de implementación no guayaba de Java 8 Blog 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);
        }
    }
}
n0mer
fuente
El enlace del blog Java 8 está muerto.
Spycho
4

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. Su Accountinterfaz es un ejemplo perfecto:

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

En lugar de lanzar un IOExceptionsobre cada captador, 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 Accountobjeto que ya contiene todos los valores, listo para ser utilizado. Como los valores ya han sido cargados por readAccount(…), 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):

  • Mejor separación de preocupaciones y siguiendo el principio de responsabilidad única
  • Menos repetitivo: no tiene que abarrotar su código throws IOExceptionpara nada más que para satisfacer al compilador
  • Manejo de errores: maneja los errores donde ocurren, al leer desde un archivo o base de datos, en lugar de en algún lugar en el medio de su lógica comercial solo porque desea obtener un valor de campo
  • Es posible que pueda hacer Account inmutables y beneficiarse de sus ventajas (por ejemplo, seguridad de hilos)
  • No necesita "trucos sucios" o soluciones alternativas para usar Accounten lambdas (por ejemplo, en a Stream)
siegi
fuente
4

Se puede resolver mediante el siguiente 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.

user_3380739
fuente
3

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.

@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
    }
}
PaoloC
fuente
Este ejemplo no se compila
Roman M
Hola @RomanM, gracias por señalar esto: he corregido el tipo de retorno faltante en el método "throwActualException". Estamos usando esto en producción, así que espero que también esté trabajando de tu lado.
PaoloC
3

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 IOExceptiontendrá que ser capturado como a RuntimeExceptiony convertido de nuevo en un IOException, y eso agregará aún más código al método anterior.

¿Por qué usarlo Streamcuando se puede hacer así? Y el método arroja IOExceptionasí que no se necesita 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;
El coordinador
fuente
1

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:

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

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

Grzegorz Piwowarek
fuente
1

Su ejemplo se puede escribir 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

SeregaLBN
fuente
0

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í.

 s.filter(softenPredicate(a->a.isActive()));
John McClean
fuente
0

Las interfaces funcionales en Java no declaran ninguna excepción marcada o no marcada. Necesitamos cambiar la firma de los métodos de:

boolean isActive() throws IOException; 
String getNumber() throwsIOException;

A:

boolean isActive();
String getNumber();

O manejarlo con el bloque try-catch:

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

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:

<dependency>
    <groupId>pl.touk</groupId>
    <artifactId>throwing-function</artifactId>
    <version>1.3</version>
</dependency>

Y use las clases específicas como ThrowingFunction, ThrowingConsumer, ThrowingPredicate, ThrowingRunnable, ThrowingSupplier.

Al final el código se ve así:

public Set<String> getActiveAccountNumbers() {
  return accounts.values().stream()
    .filter(ThrowingPredicate.unchecked(Account::isActive))
    .map(ThrowingFunction.unchecked(Account::getNumber))
    .collect(Collectors.toSet());
}
SHoko
fuente
0

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.

        Arrays.stream(VERSIONS)
        .map(version -> TemplateStore.class
                .getClassLoader().getResourceAsStream(String.format(TEMPLATE_FILE_MASK, version)))
        .map(inputStream -> {
            try {
                return ((EdiTemplates) JAXBContext.newInstance(EdiTemplates.class).createUnmarshaller()
                        .unmarshal(inputStream)).getMessageTemplate();
            } catch (JAXBException e) {
                throw new IllegalArgumentException(ERROR, e);
            }})
        .flatMap(Collection::stream)
        .collect(Collectors.toList());
atul sachan
fuente
¿Realmente estás tratando de responder la pregunta?
Nilambar Sharma
@Nilambar - Lo que estoy tratando de decir aquí es que no hay forma de manejar la excepción marcada mientras uso java stream ... al final de todo, necesitamos atrapar la marcada y lanzar una en tiempo de ejecución / sin marcar ... Ahora hay dos cosas, 1. si crees que no estoy correcto con mi comprensión, corrígeme O 2. si crees que mi publicación es irrelevante, me complace eliminarla. saludos, Atul
atul sachan
0

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.

@Test
public void whenValuePrinted_thenPrintValue() {

    List<Integer> intStream = Arrays.asList(0, 1, 2, 3, 4, 5, 6);
    intStream.stream().map(Either.liftWithValue(item -> doSomething(item)))
             .map(item -> item.isLeft() ? item.getLeft() : item.getRight())
             .flatMap(o -> {
                 System.out.println(o);
                 return o.isPresent() ? Stream.of(o.get()) : Stream.empty();
             })
             .forEach(System.out::println);
}

private Object doSomething(Integer item) throws Exception {

    if (item == 0) {
        throw new Exception("Zero ain't a number!");
    } else if (item == 4) {
        return Optional.empty();
    }

    return item;
}
Steve Gelman
fuente