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

287

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

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

Este código não é compilado, pois o Class.forName()método acima é lançado ClassNotFoundException, o que é verificado.

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

MarcG
fonte
42
A resposta curta é: você não pode, sem violar as regras sobre exceções. Você pode trapacear, é claro, e outros terão prazer em mostrar como, mas lembre-se de que isso é trapaça e essa trapaça geralmente volta a mordê-lo. Você deve pegar a exceção e lidar com isso. Se você deseja agrupá-lo e, em seguida, refazer a exceção verificada depois, você pode fazer isso com segurança.
Brian Goetz
35
@Brian, não preciso que outros me digam como trapacear, sei como me enganar e postei minha maneira de trapacear na resposta abaixo, que você votou negativamente. Sei que você está envolvido na discussão sobre Java que decidiu que não havia uma boa maneira de lidar com exceções verificadas no Streams, por isso acho incrível que você tenha notado essa minha pergunta, mas estou desapontado com sua resposta, que apenas diz "isto é não é bom ", não dá nenhuma razão para isso e, em seguida, adicionamos try / catch novamente.
MarcG
22
@Brian, sinceramente, na prática, quando as pessoas tentam refatorar declarações legadas, metade delas é convertida em fluxos, mas a outra metade desiste da refatoração, porque ninguém deseja adicionar essas tentativas / capturas. Eles tornam o código muito mais difícil de ler, certamente mais do que as declarações for originais. No meu exemplo de código acima, desde que você mantenha o "throws ClassNotFoundException", não vejo diferença no código externo. Você poderia me dar alguns exemplos da vida real em que isso viole as regras em relação às exceções?
21968 Marc
10
Os métodos de wrapper de gravação que envolvem exceções não verificadas abordam a objeção de "desordem de código" e não quebram o sistema de tipos. A resposta aqui que recorre a um "arremesso furtivo" de uma exceção verificada quebra o sistema de tipos, porque o código de chamada não espera (nem pode capturar) a exceção verificada.
Brian Goetz
14
Ele não trata da objeção de desorganização do código, porque você precisa de uma segunda tentativa / captura pelo fluxo, para desembrulhar e refazer a exceção original. Pelo contrário, se você lançar a exceção verificada, basta manter a throws ClassNotFoundExceptiondeclaração no método que contém o fluxo, para que o código de chamada espere e seja permitido capturar a exceção verificada.
MarcG

Respostas:

250

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

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

O problema da 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 ( throwscláusula).

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:

cher@armor1:~/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 em que as interfaces funcionais são definidos impede atualmente o compilador de encaminhar a exceção - não há nenhuma declaração de que iria dizer Stream.map()que, se Function.apply() throws E, Stream.map() throws Etambé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 throwSomeMoregostaríamos de ver IOExceptiona falta, mas na verdade falta 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, Eprecisa ser resolvida para um comum superde ClassNotFoundExceptione IOException, o 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 ( throwscláusula). Em seguida, o tipo de exceção relatado pelo compilador seria tão específico quanto a throwsdeclaração original das exceções verificadas do método referenciado, 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 estou 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.

Christian Hujer
fonte
16
Interessante. Acredito que algumas pessoas apreciam fluxos por permitir código paralelo mais fácil, enquanto outras por permitir código mais limpo. Brian Goetz obviamente se preocupa mais com o paralelismo (desde que ele criou o Java Concurrency in Practice), enquanto Robert Martin se preocupa mais com o código limpo (desde que ele foi o autor do livro Clean Code). As tentativas / capturas de clichê são um preço menor a pagar pelo paralelismo, portanto, não é de admirar que Brian Goetz não fique horrorizado com os problemas de usar exceções verificadas dentro de fluxos. Também não admira que Robert Martin odeie exceções verificadas, uma vez que elas aumentam a confusão.
MarcG
5
Eu prevejo que, em alguns anos, a dificuldade de lidar com exceções verificadas dentro de fluxos levará a um destes dois resultados: As pessoas simplesmente pararão de usar exceções verificadas, OU todo mundo começará a usar algum tipo de hack muito semelhante ao que eu publiquei em minha resposta UtilException. Eu apostaria que os fluxos Java-8 são o último prego no caixão de exceções verificadas, não pelo fato de que as exceções verificadas fazem parte do JDK. Embora eu goste e use exceções verificadas no código comercial (para alguns casos de uso específicos), eu teria preferido todas as exceções comuns do JDK em tempo de execução estendido.
MarcG
9
@Unihedro O problema é que as interfaces funcionais não encaminham exceções. Eu precisaria do try-catchbloco dentro do lambda, e isso simplesmente não faz sentido. Assim que Class.forNameé usado de alguma forma no lambda, por exemplo names.forEach(Class::forName), o problema está lá. Basicamente, os métodos que lançam exceções verificadas foram excluídos da participação na programação funcional como interfaces funcionais diretamente, pelo design (ruim!).
Christian Hujer
26
@ChristianHujer A exploração "Exceção à transparência" foi exatamente isso - uma exploração (que se originou na proposta BGGA). Após uma análise mais aprofundada, descobrimos que ele oferece um equilíbrio pobre de valor e complexidade, e que apresenta alguns problemas sérios (levando a problemas de inferência indecidíveis, e "pegar X" era incorreto, entre outros). É extremamente comum que uma idéia de linguagem parece promissor - até "óbvio" - mas, após uma exploração mais profunda, acabou sendo defeituoso. Este foi um desses casos.
27615 Brian Goetz
13
@BrianGoetz Existe alguma informação pública disponível sobre os problemas de inferência indecidíveis que você mencionou? Estou curioso e gostaria de entender.
Christian Hujer
169

Essa LambdaExceptionUtilclasse auxiliar permite usar quaisquer exceções verificadas 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::forNamelança ClassNotFoundException, que está marcada . O próprio fluxo também lança ClassNotFoundException, e NÃO alguma 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 (após a importação estaticamente 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 rethrowmétodos da LambdaExceptionUtilclasse 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 uncheckmétodos da LambdaExceptionUtilclasse acima são métodos 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 uncheckmétodos 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 uncheckmétodos 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 uncheckmétodos, 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 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 RuntimeExceptionpara poder 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.

MarcG
fonte
4
Eu sinto que esta resposta foi injustamente rebaixada. O código funciona. As exceções verificadas devem ser lançadas ou tratadas. Se você quiser jogá-los, mantenha a "cláusula throws" no método que contém o fluxo. Mas se você quiser lidar com eles simplesmente encapsulando e relendo novamente, acho que prefiro usar o código acima para "desmarcar" as exceções e deixá-las borbulhar sozinhas. A única diferença que estou ciente é que a exceção de bolha não estenderá o RuntimeException. Eu sei que os puristas não vão gostar disso, mas isso "inevitavelmente voltará para morder alguém"? Não parece provável.
MarcG
4
@Christian Hujer, para ser sincero com o defensor, ele votou uma versão anterior antes de eu adicionar a explicação "vantagens, desvantagens e limitações". Então talvez tenha sido merecido na época. Você não pode ensinar alguém a violar as regras sem ao menos tentar entender e explicar as consequências. A principal razão pela qual eu postei essa pergunta foi obter feedback para as desvantagens da minha resposta. Acabei recebendo esse feedback não aqui, mas de outra pergunta em programmers.stackexchange. Então voltei aqui e atualizei minha resposta.
27614 MarcG
16
Eu diminuí a votação apenas porque isso incentiva código não sustentável . Este é um truque feio, embora inteligente, e nunca vou achar essa resposta útil. Este é, novamente, outro "não use" do idioma.
Unihedron
12
@Unihedro, mas por que se tornou impossível de manter? Não vejo o porquê. Algum exemplo?
28414 MarcGold
2
Na minha opinião, o @SuppressWarnings ("unchecked")truque do compilador é completamente inaceitável.
Thorbjørn Ravn Andersen
26

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.

Brian Goetz
fonte
18
Só uma pergunta; qual foi a decisão de design que levou os lambdas a não poder propagar exceções verificadas fora de seu contexto? Note que eu entendo que as interfaces funcionais como Functionetc não fazem throwsnada; Eu só estou curioso.
fge
4
Isso throw w.cause;não faria o compilador reclamar que o método não lança nem pega Throwable? Portanto, é provável que um elenco IOExceptionseja necessário lá. Além disso, se o lambda lança mais de um tipo de exceção verificada, o corpo da captura se torna um pouco feio com algumas instanceofverificações (ou algo com um objetivo semelhante) para verificar qual exceção verificada foi lançada.
Victor Stafusa
10
@schatten Uma razão é que você pode esquecer de pegar o WE, e então uma exceção estranha (com a qual ninguém sabe lidar) vaza. (Você pode dizer "mas captou a exceção, portanto é seguro". Neste exemplo de brinquedo. Mas toda vez que vejo uma base de código adotar essa abordagem, alguém se esquece. A tentação de ignorar as exceções não tem limites.) Outro risco é que usá-lo com segurança é específico para uma combinação específica (usar site, exceção). Ele não se adapta bem a várias exceções ou usos não-homogêneos.
27716 Brian Goetz
2
@hoodaticus Concordo com você. Dito isso, você prefere agrupar cada vez mais (como mostrado acima, aumentando o risco de "esquecer") ou apenas criar 4 interfaces inteligentes e usar lambdas sem o agrupamento, como mostrado em stackoverflow.com/a/30974991/2365724 ? Graças
PaoloC
10
Francamente, esta solução é completamente inviável. Eu pensei que o objetivo dos fluxos era reduzir o clichê, não aumentá-lo.
Wddz #
24

Você pode!

Estendendo @marcg 's UtilExceptione adicionando throw Eonde necessário: desta forma, o compilador solicitará que você adicione cláusulas throw e tudo mais como se você pudesse lançar exceções verificadas nativamente nos fluxos do java 8.

Instruções: copie / cole LambdaExceptionUtilno seu IDE e use-o conforme mostrado abaixo LambdaExceptionUtilTest.

public final class LambdaExceptionUtil {

    @FunctionalInterface
    public interface Consumer_WithExceptions<T, E extends Exception> {
        void accept(T t) throws E;
    }

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

    /**
     * .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name))));
     */
    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) {
                throwActualException(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) {
                throwActualException(exception);
                return null;
            }
        };
    }

    @SuppressWarnings("unchecked")
    private static <E extends Exception> void throwActualException(Exception exception) throws E {
        throw (E) exception;
    }

}

Alguns testes para mostrar uso e comportamento:

public class LambdaExceptionUtilTest {

    @Test(expected = MyTestException.class)
    public void testConsumer() throws MyTestException {
        Stream.of((String)null).forEach(rethrowConsumer(s -> checkValue(s)));
    }

    private void checkValue(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
    }

    private class MyTestException extends Exception { }

    @Test
    public void testConsumerRaisingExceptionInTheMiddle() {
        MyLongAccumulator accumulator = new MyLongAccumulator();
        try {
            Stream.of(2L, 3L, 4L, null, 5L).forEach(rethrowConsumer(s -> accumulator.add(s)));
            fail();
        } catch (MyTestException e) {
            assertEquals(9L, accumulator.acc);
        }
    }

    private class MyLongAccumulator {
        private long acc = 0;
        public void add(Long value) throws MyTestException {
            if(value==null) {
                throw new MyTestException();
            }
            acc += value;
        }
    }

    @Test
    public void testFunction() throws MyTestException {
        List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
        assertEquals(2, sizes.size());
        assertEquals(4, sizes.get(0).intValue());
        assertEquals(5, sizes.get(1).intValue());
    }

    private Integer transform(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
        return value.length();
    }

    @Test(expected = MyTestException.class)
    public void testFunctionRaisingException() throws MyTestException {
        Stream.of("ciao", null, "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
    }

}
PaoloC
fonte
1
Desculpe @setheron você está certo, basta adicionar <Integer>antes map. De fato, o compilador java não pode inferir o Integertipo de retorno. Tudo o resto deve estar correto.
PaoloC 7/08/15
1
Isso funcionou para mim. Tornou a resposta de MarcG perfeita, reforçando a manipulação da exceção.
Skychan
1
Solução para a questão acima: Declare uma variável como esta Consumer <ThingType> expression = rethrowConsumer ((ThingType thing) -> thing.clone ()); depois use essa expressão dentro do foreach interno.
Skychan 29/09/2015
1
@ Skychan: Como nesta nova versão modificada você não está suprimindo mais nenhuma exceção, provavelmente é um pouco mais difícil para o sistema de inferência. Em algum comentário abaixo, Brian Goetz fala sobre a "transparência das exceções" que leva a "problemas de inferência indecidíveis".
MarcG
3
Muito agradável. A única coisa lamentável é que isso não funciona perfeitamente com um método que gera várias exceções verificadas. Nesse caso, o compilador fará você pegar um supertipo comum, por exemplo Exception.
Wddz 17/07
12

Basta usar qualquer um dos NoException (meu projeto), Desmarcado de jOOλ , jogando-lambdas , interfaces de Throwable , ou Faux Pas .

// NoException
stream.map(Exceptions.sneak().function(Class::forName));

// jOOλ
stream.map(Unchecked.function(Class::forName));

// throwing-lambdas
stream.map(Throwing.function(Class::forName).sneakyThrow());

// Throwable interfaces
stream.map(FunctionWithThrowable.aFunctionThatUnsafelyThrowsUnchecked(Class::forName));

// Faux Pas
stream.map(FauxPas.throwingFunction(Class::forName));
Robert Važan
fonte
7

Eu escrevi uma biblioteca que estende a API do Stream para permitir que você lance exceções verificadas. Ele usa o truque de Brian Goetz.

Seu código se tornaria

public List<Class> getClasses() throws ClassNotFoundException {     
    Stream<String> classNames = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String");

    return ThrowingStream.of(classNames, ClassNotFoundException.class)
               .map(Class::forName)
               .collect(Collectors.toList());
}
Jeffrey
fonte
7

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
        }
Radoslav Stoyanov
fonte
1
Essa é uma solução simples e eficaz.
Lin W
2
Isto é exatamente o que Op não queria: tente blocos no lambda. Além disso, ele funciona apenas como esperado, desde que nenhum outro código fora do bloco try agrupe uma IOException em uma RuntimeException. Para evitar isso, um wrapper personalizado-RuntimeException (definido como uma classe interna privada) pode ser usado.
Malte Hartwig
5

Você não pode.

No entanto, você pode dar uma olhada em um dos meus projetos, o 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 pegar MyException.

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

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

fge
fonte
2
Mas, se você quiser lançar a exceção verificada original, precisará adicionar o try / catch ao redor do fluxo, para desembrulhá-lo, o que ainda é terrível! Eu gosto da ideia de que você PODE lançar uma exceção desmarcada, se quiser, e que PODE retornar um valor padrão ao fluxo, se quiser, mas também acho que você deve adicionar algum .orThrowChecked()método ao seu projeto que permita que a própria exceção verificada seja lançada . Dê uma olhada na minha UtilExceptionresposta nesta página e veja se você gosta da ideia de adicionar essa terceira possibilidade ao seu projeto.
MarcG
"Mas, se você quiser lançar a exceção verificada original, precisará adicionar o try / catch ao redor do fluxo, para desembrulhá-lo, o que ainda é terrível!" <- sim, mas você não tem escolha. Lambdas não pode propagam exceções verificadas fora do seu contexto, essa é uma "decisão" de design (I vê-lo como uma falha, pessoalmente, mas Ohwell)
fge
Quanto à sua ideia, não sigo muito bem o que faz, desculpe; afinal você ainda joga como desmarcada, então como isso é diferente do que eu faço? (exceto que eu tenho uma interface diferente para ele)
fge
De qualquer forma, você pode contribuir para o projeto! Além disso, você percebeu que Streamimplementa AutoCloseable?
fge
Deixe-me perguntar: o que você MyExceptionprecede precisa ser uma exceção desmarcada?
27614 MarcG
3

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);
    }
}
introspectado
fonte
3

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 tentativas / capturas feias 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 Methodrefatoraçã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 RuntimeExceptione sem precisar declará-la explicitamente.

sergut
fonte
4
O uso de lombok deve ser desencorajado.
Dragas
2

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:

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;
}
Piotr Niewinski
fonte
2

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.

    @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);
    }
OSGI Java
fonte
1

Concordo com os comentários acima, ao usar o Stream.map, você está limitado a implementar a Função que não gera Exceções.

No entanto, você pode criar seu próprio FunctionalInterface que é exibido como abaixo.

@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
  void accept(T instance) throws X;
}

em seguida, implemente-o usando Lambdas ou referências, como mostrado abaixo.

import java.io.FileWriter;
import java.io.IOException;

//lambda expressions and the execute around method (EAM) pattern to
//manage resources

public class FileWriterEAM  {
  private final FileWriter writer;

  private FileWriterEAM(final String fileName) throws IOException {
    writer = new FileWriter(fileName);
  }
  private void close() throws IOException {
    System.out.println("close called automatically...");
    writer.close();
  }
  public void writeStuff(final String message) throws IOException {
    writer.write(message);
  }
  //...

  public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {

    final FileWriterEAM writerEAM = new FileWriterEAM(fileName);    
    try {
      block.accept(writerEAM);
    } finally {
      writerEAM.close();
    }
  }

  public static void main(final String[] args) throws IOException {

    FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));

    FileWriterEAM.use("eam2.txt", writerEAM -> {
        writerEAM.writeStuff("how");
        writerEAM.writeStuff("sweet");      
      });

    FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);     

  }


 void writeIt() throws IOException{
     this.writeStuff("How ");
     this.writeStuff("sweet ");
     this.writeStuff("it is");

 }

}
JohnnyO
fonte
1

A única maneira mapinterna 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 têm como objetivo 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 é relativamente conciso, ao mesmo tempo que torna aparente que o resultado do fluxo depende da mapconclusã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 applyOrDiemétodo pega um Functionque lança uma exceção e o converte em um Functionque 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 mapoperação ilustra que agora você tem um em Stream<CompletableFuture<T>>vez de apenas um Stream<T>. CompletableFuturecuida 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 à collectfase, é 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 finisherque não precisa saber nada sobre CompletableFuture.

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

Matt McHenry
fonte
1

Provavelmente, uma maneira melhor e mais funcional é agrupar exceções e propagá-las ainda mais no fluxo. Dê uma olhada no tipo Try do Vavr, por exemplo.

Exemplo:

interface CheckedFunction<I, O> {
    O apply(I i) throws Exception; }

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return i -> {
        try {
            return f.apply(i);
        } catch(Exception ex) {

            throw new RuntimeException(ex);
        }
    } }

fileNamesToRead.map(unchecked(file -> Files.readAllLines(file)))

OU

@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwUnchecked(Exception e) throws E {
    throw (E) e;
}

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return arg -> {
        try {
            return f.apply(arg);
        } catch(Exception ex) {
            return throwUnchecked(ex);
        }
    };
}

A 2ª implementação evita agrupar a exceção em um arquivo RuntimeException. throwUncheckedfunciona porque quase sempre todas as exceções genéricas são tratadas como desmarcadas no java.

Mikhail Kholodkov
fonte
1

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 ...), desta maneira permite obter uma definição estática estrita de possíveis exceções (requer declará-las throws). E nada instanceofou algo é necessário.

Taras
fonte
-1

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 marcada dentro de Callableem 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.

Matthias Ronge
fonte
1
(1) Já existem várias respostas mostrando um exemplo como este; então, o que sua resposta adiciona às perguntas e respostas que ainda não foram abordadas? A publicação de respostas duplicadas como essa apenas adiciona confusão ao site. (2) O PO diz especificamente que não deseja fazer isso. "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."
Radiodef