handling - throw exception in stream java 8




Como posso lançar exceções CHECKED de dentro dos fluxos do Java 8? (12)

Como posso lançar exceções CHECKED de dentro do Java 8 streams / lambdas?

Em outras palavras, eu quero fazer um 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;
    }

Esse código não é compilado, pois o método Class.forName() acima gera ClassNotFoundException , que é verificado.

Observe que NÃO quero agrupar a exceção verificada dentro de uma exceção de tempo de execução e, em vez disso, lançar a exceção desmarcada. Eu quero lançar a exceção verificada em si e sem adicionar feias try / catches ao fluxo.


A única maneira map interna de lidar com exceções verificadas que podem ser lançadas por uma operação é encapsulá-las dentro de a CompletableFuture . (Uma Optional é uma alternativa mais simples se você não precisar preservar a exceção.) Essas classes visam permitir que você represente operações contingentes de uma maneira funcional.

São necessários alguns métodos auxiliares não triviais, mas você pode chegar a um código que seja relativamente conciso, ao mesmo tempo que torna aparente que o resultado do seu fluxo depende da map conclusão da operação. Aqui está o que parece:

    CompletableFuture<List<Class<?>>> classes =
            Stream.of("java.lang.String", "java.lang.Integer", "java.lang.Double")
                  .map(MonadUtils.applyOrDie(Class::forName))
                  .map(cfc -> cfc.thenApply(Class::getSuperclass))
                  .collect(MonadUtils.cfCollector(ArrayList::new,
                                                  List::add,
                                                  (List<Class<?>> l1, List<Class<?>> l2) -> { l1.addAll(l2); return l1; },
                                                  x -> x));
    classes.thenAccept(System.out::println)
           .exceptionally(t -> { System.out.println("unable to get class: " + t); return null; });

Isso produz a seguinte saída:

[class java.lang.Object, class java.lang.Number, class java.lang.Number]

O applyOrDie método pega um Function que lança uma exceção e o converte em um Function que retorna um já concluído CompletableFuture - concluído normalmente com o resultado da função original ou concluído excepcionalmente com a exceção lançada.

A segunda map operação ilustra que agora você tem um em Stream<CompletableFuture<T>> vez de apenas um Stream<T> . CompletableFuture cuida apenas de executar esta operação se a operação upstream for bem-sucedida. A API torna isso explícito, mas relativamente indolor.

Até você chegar à collect fase, é isso. É aqui que exigimos um método auxiliar bastante significativo. Queremos "levantar" uma operação de recolha normal (neste caso, toList() ) "dentro" da CompletableFuture - cfCollector() nos permite fazer isso usando um supplier , accumulator , combiner , e finisher que não precisa saber nada sobre CompletableFuture .

Os métodos auxiliares podem ser encontrados no GitHub na minha MonadUtils classe, que ainda é um trabalho em andamento.


Aqui está uma visão ou solução diferente para o problema original. Aqui, mostro que temos a opção de escrever um código que processará apenas um subconjunto de valores válido, com a opção de detectar e manipular casos quando a exceção foi lançada.

private void run() {
    List<String> list = Stream.of(1, 2, 3, 4).map(wrapper(i ->
            String.valueOf(++i / 0), i -> String.valueOf(++i))).collect(Collectors.toList());
    System.out.println(list.toString());
}

private <T, R, E extends Exception> Function<T, R> wrapper(ThrowingFunction<T, R, E> function, 
Function<T, R> onException) {
    return i -> {
        try {
            return function.apply(i);
        } catch (ArithmeticException e) {
            System.out.println("Exception: " + i);
            return onException.apply(i);
        } catch (Exception e) {
            System.out.println("Other: " + i);
            return onException.apply(i);
        }
    };
}

@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
    R apply(T t) throws E;
}

Eu acho que essa abordagem é a correta:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes;
    try {
        classes = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String").map(className -> {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new UndeclaredThrowableException(e);
            }
        }).collect(Collectors.toList());
    } catch (UndeclaredThrowableException e) {
        if (e.getCause() instanceof ClassNotFoundException) {
            throw (ClassNotFoundException) e.getCause();
        } else {
            // this should never happen
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
    return classes;
}

Quebrando a exceção verificada dentro de Callable em um UndeclaredThrowableException (esse é o caso de uso para essa exceção) e desembrulhando-a fora.

Sim, acho feio e eu desaconselharia o uso de lambdas nesse caso e voltaria a um bom e velho loop, a menos que você esteja trabalhando com um fluxo paralelo e a paralelização traga um benefício objetivo que justifique a ilegibilidade do código.

Como muitos outros apontaram, existem soluções para essa situação, e espero que uma delas seja uma versão futura do Java.


Eu uso esse tipo de exceção de quebra automática:

public class CheckedExceptionWrapper extends RuntimeException {
    ...
    public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
        throw (T) getCause();
    }
}

Isso exigirá o tratamento estatístico dessas exceções:

void method() throws IOException, ServletException {
    try { 
        list.stream().forEach(object -> {
            ...
            throw new CheckedExceptionWrapper(e);
            ...            
        });
    } catch (CheckedExceptionWrapper e){
        e.<IOException>rethrow();
        e.<ServletExcepion>rethrow();
    }
}

Experimente online!

Embora a exceção seja lançada de qualquer maneira durante a primeira rethrow() chamada (oh, Java genéricos ...), dessa maneira permite obter uma definição estática estrita de possíveis exceções (requer declará-las throws ). E nada instanceof ou algo é necessário.


Resumindo os comentários acima, a solução avançada é usar um wrapper especial para funções não verificadas com o construtor como API, que fornece recuperação, nova exibição e supressão.

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(Try.<String, Class<?>>safe(Class::forName)
                  .handle(System.out::println)
                  .unsafe())
          .collect(toList());

O código abaixo demonstra para interfaces de consumidor, fornecedor e função. Pode ser facilmente expandido. Algumas palavras-chave públicas foram removidas para este exemplo.

Classe Try é o ponto final para o código do cliente. Métodos seguros podem ter um nome exclusivo para cada tipo de função. CheckedConsumer , CheckedSupplier e CheckedFunction são análogos verificados das funções lib que podem ser usadas independentemente do Try

CheckedBuilder é a interface para lidar com exceções em alguma função marcada. ouTry permite executar outra função do mesmo tipo, se houver falha anterior. handle fornece tratamento de exceção, incluindo filtragem de tipo de exceção. A ordem dos manipuladores é importante. Reduzir métodos inseguros e relançar relança última exceção na cadeia de execução. Reduza os métodos orElse e orElseGet retornam valores alternativos, como opcionais, se todas as funções falharem. Também existe o método suprimir . CheckedWrapper é a implementação comum do CheckedBuilder.

final class Try {

    public static <T> CheckedBuilder<Supplier<T>, CheckedSupplier<T>, T> 
        safe(CheckedSupplier<T> supplier) {
        return new CheckedWrapper<>(supplier, 
                (current, next, handler, orResult) -> () -> {
            try { return current.get(); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().get() : orResult.apply(ex);
            }
        });
    }

    public static <T> Supplier<T> unsafe(CheckedSupplier<T> supplier) {
        return supplier;
    }

    public static <T> CheckedBuilder<Consumer<T>, CheckedConsumer<T>, Void> 
        safe(CheckedConsumer<T> consumer) {
        return new CheckedWrapper<>(consumer, 
                (current, next, handler, orResult) -> t -> {
            try { current.accept(t); } catch (Exception ex) {
                handler.accept(ex);
                if (next.isPresent()) {
                    next.get().accept(t);
                } else {
                    orResult.apply(ex);
                }
            }
        });
    }

    public static <T> Consumer<T> unsafe(CheckedConsumer<T> consumer) {
        return consumer;
    }

    public static <T, R> CheckedBuilder<Function<T, R>, CheckedFunction<T, R>, R> 
        safe(CheckedFunction<T, R> function) {
        return new CheckedWrapper<>(function, 
                (current, next, handler, orResult) -> t -> {
            try { return current.applyUnsafe(t); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().apply(t) : orResult.apply(ex);
            }
        });
    }

    public static <T, R> Function<T, R> unsafe(CheckedFunction<T, R> function) {
        return function;
    }

    @SuppressWarnings ("unchecked")
    static <T, E extends Throwable> T throwAsUnchecked(Throwable exception) throws E { 
        throw (E) exception; 
    }
}

@FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> {
    void acceptUnsafe(T t) throws Exception;
    @Override default void accept(T t) {
        try { acceptUnsafe(t); } catch (Exception ex) {
            Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> {
    R applyUnsafe(T t) throws Exception;
    @Override default R apply(T t) {
        try { return applyUnsafe(t); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> {
    T getUnsafe() throws Exception;
    @Override default T get() {
        try { return getUnsafe(); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

interface ReduceFunction<TSafe, TUnsafe, R> {
    TSafe wrap(TUnsafe current, Optional<TSafe> next, 
            Consumer<Throwable> handler, Function<Throwable, R> orResult);
}

interface CheckedBuilder<TSafe, TUnsafe, R> {
    CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next);

    CheckedBuilder<TSafe, TUnsafe, R> handle(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handle(
            Class<E> exceptionType, Consumer<E> handler);

    CheckedBuilder<TSafe, TUnsafe, R> handleLast(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Class<E> exceptionType, Consumer<? super E> handler);

    TSafe unsafe();
    TSafe rethrow(Function<Throwable, Exception> transformer);
    TSafe suppress();
    TSafe orElse(R value);
    TSafe orElseGet(Supplier<R> valueProvider);
}

final class CheckedWrapper<TSafe, TUnsafe, R> 
        implements CheckedBuilder<TSafe, TUnsafe, R> {

    private final TUnsafe function;
    private final ReduceFunction<TSafe, TUnsafe, R> reduceFunction;

    private final CheckedWrapper<TSafe, TUnsafe, R> root;
    private CheckedWrapper<TSafe, TUnsafe, R> next;

    private Consumer<Throwable> handlers = ex -> { };
    private Consumer<Throwable> lastHandlers = ex -> { };

    CheckedWrapper(TUnsafe function, 
            ReduceFunction<TSafe, TUnsafe, R> reduceFunction) {
        this.function = function;
        this.reduceFunction = reduceFunction;
        this.root = this;
    }

    private CheckedWrapper(TUnsafe function, 
            CheckedWrapper<TSafe, TUnsafe, R> prev) {
        this.function = function;
        this.reduceFunction = prev.reduceFunction;
        this.root = prev.root;
        prev.next = this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next) {
        return new CheckedWrapper<>(next, this);
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handle(
            Consumer<Throwable> handler) {
        handlers = handlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handle(Class<E> exceptionType, Consumer<E> handler) {
        handlers = handlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Consumer<Throwable> handler) {
        lastHandlers = lastHandlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handleLast(Class<E> exceptionType, Consumer<? super E> handler) {
        lastHandlers = lastHandlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public TSafe unsafe() {
        return root.reduce(ex -> Try.throwAsUnchecked(ex));
    }

    @Override
    public TSafe rethrow(Function<Throwable, Exception> transformer) {
        return root.reduce(ex -> Try.throwAsUnchecked(transformer.apply(ex)));
    }

    @Override public TSafe suppress() {
        return root.reduce(ex -> null);
    }

    @Override public TSafe orElse(R value) {
        return root.reduce(ex -> value);
    }

    @Override public TSafe orElseGet(Supplier<R> valueProvider) {
        Objects.requireNonNull(valueProvider);
        return root.reduce(ex -> valueProvider.get());
    }

    private TSafe reduce(Function<Throwable, R> orResult) {
        return reduceFunction.wrap(function, 
                Optional.ofNullable(next).map(p -> p.reduce(orResult)), 
                this::handle, orResult);
    }

    private void handle(Throwable ex) {
        for (CheckedWrapper<TSafe, TUnsafe, R> current = this; 
                current != null; 
                current = current.next) {
            current.handlers.accept(ex);
        }
        lastHandlers.accept(ex);
    }
}

Você também pode escrever um método de wrapper para quebrar exceções não verificadas e até aprimorar o wrapper com um parâmetro adicional que representa outra interface funcional (com o mesmo tipo de retorno R ). Nesse caso, você pode passar uma função que seria executada e retornada em caso de exceções. Veja o exemplo abaixo:

    @Test
    public void getClasses() {

        String[] classNames = {"java.lang.Object", "java.lang.Integer", "java.lang.Foo"};
        List<Class> classes =
                Stream.of(classNames)
                        .map(className -> {
                            try {
                                return Class.forName(className);
                            } catch (ClassNotFoundException e) {
                                // log the error
                                return null;
                            }
                        })
                        .filter(c -> c != null)
                        .collect(Collectors.toList());

        if (classes.size() != classNames.length) {
            // add your error handling here if needed or process only the resulting list
            System.out.println("Did not process all class names");
        }

        classes.forEach(System.out::println);
    }

Essa classe auxiliar LambdaExceptionUtil permite usar qualquer exceção verificada nos fluxos Java, como este:

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

Nota Class::forName lança ClassNotFoundException , que está marcado . O próprio fluxo também lança ClassNotFoundException e NÃO uma exceção desmarcada.

public final class LambdaExceptionUtil {

@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; }

}

Muitos outros exemplos de como usá-lo (depois de importar estaticamente o LambdaExceptionUtil ):

@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");
    }    

NOTA 1: Os métodos de LambdaExceptionUtil classe LambdaExceptionUtil acima podem ser usados ​​sem medo e podem ser usados em qualquer situação . Um grande obrigado ao usuário @PaoloC, que ajudou a resolver o último problema: Agora o compilador solicitará que você adicione cláusulas throw e tudo é como se você pudesse lançar exceções verificadas nativamente nos fluxos do Java 8.

NOTA 2: Os métodos de uncheck da classe LambdaExceptionUtil acima são métodos de bônus e podem ser removidos com segurança da classe, se você não quiser usá-los. Se você os usou, faça-o com cuidado e não antes de entender os seguintes casos de uso, vantagens / desvantagens e limitações:

• Você pode usar os métodos de uncheck se estiver chamando um método que literalmente nunca pode lançar a exceção declarada. Por exemplo: new String (byteArr, "UTF-8") lança UnsupportedEncodingException, mas UTF-8 é garantido pela especificação Java para sempre estar presente. Aqui, a declaração de lances é um incômodo e qualquer solução para silenciá-la com um mínimo de clichê é bem-vinda: String text = uncheck(() -> new String(byteArr, "UTF-8"));

• Você pode usar os métodos de uncheck se estiver implementando uma interface estrita em que não tenha a opção de adicionar uma declaração de arremessos, e ainda assim lançar uma exceção seja inteiramente apropriado. A quebra de uma exceção apenas para obter o privilégio de lançá-la resulta em um rastreamento de pilha com exceções falsas que não contribuem com informações sobre o que realmente deu errado. Um bom exemplo é Runnable.run (), que não lança nenhuma exceção verificada.

• De qualquer forma, se você decidir usar os métodos uncheck , esteja ciente destas 2 consequências de lançar exceções CHECKED sem uma cláusula throws: 1) O código de chamada não poderá identificá-lo pelo nome (se você tentar, o compilador dirá: A exceção nunca é lançada no corpo da instrução try correspondente). Ele irá borbulhar e provavelmente será capturado no loop principal do programa por alguns "catch Exception" ou "catch Throwable", que podem ser o que você deseja. 2) Ele viola o princípio da menor surpresa: não será mais suficiente capturar o RuntimeException para garantir a captura de todas as exceções possíveis. Por esse motivo, acredito que isso não deve ser feito no código da estrutura, mas apenas no código comercial que você controla completamente.


Esta resposta é semelhante a 17, mas evitando a definição de exceção do wrapper:

List test = new ArrayList();
        try {
            test.forEach(obj -> {

                //let say some functionality throws an exception
                try {
                    throw new IOException("test");
                }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        catch (RuntimeException re) {
            if(re.getCause() instanceof IOException) {
                //do your logic for catching checked
            }
            else 
                throw re; // it might be that there is real runtime exception
        }

Você não pode fazer isso com segurança. Você pode trapacear, mas seu programa está quebrado e isso inevitavelmente voltará a morder alguém (deveria ser você, mas muitas vezes nossa trapaça explode em outra pessoa).

Aqui está uma maneira um pouco mais segura de fazer isso (mas ainda não recomendo isso).

class WrappedException extends RuntimeException {
    Throwable cause;

    WrappedException(Throwable cause) { this.cause = cause; }
}

static WrappedException throwWrapped(Throwable t) {
    throw new WrappedException(t);
}

try 
    source.stream()
          .filter(e -> { ... try { ... } catch (IOException e) { throwWrapped(e); } ... })
          ...
}
catch (WrappedException w) {
    throw (IOException) w.cause;
}

Aqui, o que você está fazendo é capturar a exceção no lambda, emitir um sinal do pipeline de fluxo que indica que a computação falhou excepcionalmente, capturar o sinal e agir nesse sinal para gerar a exceção subjacente. A chave é que você está sempre capturando a exceção sintética, em vez de permitir que uma exceção verificada vaze sem declarar que a exceção é lançada.


Você não pode.

No entanto, você pode dar uma olhada em throwing-lambdas que lhe permite manipular mais facilmente tais "jogando lambdas".

No seu caso, você seria capaz de fazer isso:

import static com.github.fge.lambdas.functions.Functions.wrap;

final ThrowingFunction<String, Class<?>> f = wrap(Class::forName);

List<Class> classes =
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(f.orThrow(MyException.class))
          .collect(Collectors.toList());

e pegue MyException .

Esse é um exemplo. Outro exemplo é que você pode .orReturn() algum valor padrão.

Observe que este ainda é um trabalho em andamento, mais está por vir. Melhores nomes, mais recursos, etc.


TL; DR Basta usar o Lombok @SneakyThrows .

Christian Hujer já explicou em detalhes por que lançar exceções verificadas de um fluxo não é, estritamente falando, possível devido às limitações do Java.

Algumas outras respostas explicaram truques para contornar as limitações do idioma, mas ainda são capazes de cumprir o requisito de lançar "a própria exceção verificada e sem adicionar feias tentativas / capturas ao fluxo" , algumas delas exigindo dezenas de linhas adicionais de clichê.

Vou destacar outra opção para fazer isso: o IMHO é muito mais limpo do que todos os outros: o Lombok @SneakyThrows . Foi mencionado de passagem por outras respostas, mas foi um pouco enterrado sob muitos detalhes desnecessários.

O código resultante é tão simples quanto:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes =
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                .map(className -> getClass(className))
                .collect(Collectors.toList());
    return classes;
}

@SneakyThrows                                 // <= this is the only new code
private Class<?> getClass(String className) {
    return Class.forName(className);
}

Nós apenas precisávamos de uma Extract Method refatoração (feita pelo IDE) e uma linha adicional para @SneakyThrows . A anotação se encarrega de adicionar todo o padrão para garantir que você possa lançar sua exceção verificada sem agrupá-la RuntimeException e sem precisar declará-la explicitamente.


A resposta simples para sua pergunta é: você não pode, pelo menos não diretamente. E não é sua culpa. A Oracle estragou tudo. Eles se apegam ao conceito de exceções verificadas, mas inconsistentemente se esquecem de cuidar das exceções verificadas ao projetar interfaces funcionais, fluxos, lambda etc. Isso é essencial para a indústria de especialistas como Robert C. Martin, que chamam as exceções verificadas de um experimento falho.

Na minha opinião, este é um erro enorme na API e um erro menor na especificação da linguagem .

O erro na API é que ela não fornece facilidade para encaminhar exceções verificadas, onde isso realmente faria muito sentido para a programação funcional. Como demonstrarei abaixo, essa instalação seria facilmente possível.

O erro na especificação de idioma é que ele não permite que um parâmetro de tipo inferir uma lista de tipos em vez de um único tipo, desde que o parâmetro de tipo seja usado apenas em situações em que uma lista de tipos é permitida (cláusula throws ).

Nossa expectativa como programadores Java é que o seguinte código seja compilado:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class CheckedStream {
    // List variant to demonstrate what we actually had before refactoring.
    public List<Class> getClasses(final List<String> names) throws ClassNotFoundException {
        final List<Class> classes = new ArrayList<>();
        for (final String name : names)
            classes.add(Class.forName(name));
        return classes;
    }

    // The Stream function which we want to compile.
    public Stream<Class> getClasses(final Stream<String> names) throws ClassNotFoundException {
        return names.map(Class::forName);
    }
}

No entanto, fornece:

[email protected]:~/playground/Java/checkedStream$ javac CheckedStream.java 
CheckedStream.java:13: error: incompatible thrown types ClassNotFoundException in method reference
        return names.map(Class::forName);
                         ^
1 error

A maneira pela qual as interfaces funcionais são definidas atualmente impede que o Compilador encaminhe a exceção - não há declaração que diga ao Stream.map() que se Function.apply() throws E , Stream.map() throws E também.

O que está faltando é uma declaração de um parâmetro de tipo para passar por exceções verificadas. O código a seguir mostra como esse parâmetro do tipo de passagem realmente poderia ter sido declarado com a sintaxe atual. Exceto pelo caso especial na linha marcada, que é um limite discutido abaixo, esse código compila e se comporta conforme o esperado.

import java.io.IOException;
interface Function<T, R, E extends Throwable> {
    // Declare you throw E, whatever that is.
    R apply(T t) throws E;
}   

interface Stream<T> {
    // Pass through E, whatever mapper defined for E.
    <R, E extends Throwable> Stream<R> map(Function<? super T, ? extends R, E> mapper) throws E;
}   

class Main {
    public static void main(final String... args) throws ClassNotFoundException {
        final Stream<String> s = null;

        // Works: E is ClassNotFoundException.
        s.map(Class::forName);

        // Works: E is RuntimeException (probably).
        s.map(Main::convertClass);

        // Works: E is ClassNotFoundException.
        s.map(Main::throwSome);

        // Doesn't work: E is Exception.
        s.map(Main::throwSomeMore);  // error: unreported exception Exception; must be caught or declared to be thrown
    }   

    public static Class convertClass(final String s) {
        return Main.class;
    }   

    static class FooException extends ClassNotFoundException {}

    static class BarException extends ClassNotFoundException {}

    public static Class throwSome(final String s) throws FooException, BarException {
        throw new FooException();
    }   

    public static Class throwSomeMore(final String s) throws ClassNotFoundException, IOException  {
        throw new FooException();
    }   
}   

No caso de throwSomeMore , gostaríamos de ver a IOException perdida, mas ela realmente perde a Exception .

Isso não é perfeito porque a inferência de tipo parece estar procurando por um único tipo, mesmo no caso de exceções. Como a inferência de tipo precisa de um único tipo, E precisa resolver para um super comum de ClassNotFoundException e IOException , que é Exception .

É necessário um ajuste na definição de inferência de tipo para que o compilador procure vários tipos se o parâmetro type for usado onde uma lista de tipos é permitida (cláusula throws ). Em seguida, o tipo de exceção relatado pelo compilador seria tão específico quanto a declaração de throws original das exceções verificadas do método referenciado, e não um único tipo super abrangente.

A má notícia é que isso significa que a Oracle estragou tudo. Certamente eles não quebram o código de aterramento do usuário, mas a introdução de parâmetros de tipo de exceção nas interfaces funcionais existentes interromperia a compilação de todo o código de aterramento do usuário que usa essas interfaces explicitamente. Eles terão que inventar um novo açúcar de sintaxe para corrigir isso.

A pior notícia é que esse tópico já foi discutido por Brian Goetz em 2010 https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java (novo link: http://mail.openjdk.java.net/pipermail/lambda-dev/2010-June/001484.html ), mas sou informado de que essa investigação não deu certo e que não há nenhum trabalho atual na Oracle que eu conheça para mitigar as interações entre exceções verificadas e lambdas.







checked-exceptions