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
/ catches
ao fluxo.
throws ClassNotFoundException
declaraçã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.Respostas:
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 (
throws
cláusula).Nossa expectativa como programadores Java é que o seguinte código seja compilado:
No entanto, fornece:
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, seFunction.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.
No caso de
throwSomeMore
gostaríamos de verIOException
a falta, mas na verdade faltaException
.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 ser resolvida para um comumsuper
deClassNotFoundException
eIOException
, 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 (
throws
cláusula). Em seguida, o tipo de exceção relatado pelo compilador seria tão específico quanto athrows
declaraçã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.
fonte
try-catch
bloco dentro do lambda, e isso simplesmente não faz sentido. Assim queClass.forName
é usado de alguma forma no lambda, por exemplonames.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!).Essa
LambdaExceptionUtil
classe auxiliar permite usar quaisquer exceções verificadas nos fluxos Java, como este:Nota
Class::forName
lançaClassNotFoundException
, que está marcada . O próprio fluxo também lançaClassNotFoundException
, e NÃO alguma exceção desmarcada.Muitos outros exemplos de como usá-lo (após a importação estaticamente
LambdaExceptionUtil
):NOTA 1: Os
rethrow
métodos daLambdaExceptionUtil
classe 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
uncheck
métodos daLambdaExceptionUtil
classe 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
uncheck
mé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
uncheck
mé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
uncheck
mé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 capturarRuntimeException
para 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.fonte
@SuppressWarnings ("unchecked")
truque do compilador é completamente inaceitável.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).
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.
fonte
Function
etc não fazemthrows
nada; Eu só estou curioso.throw w.cause;
não faria o compilador reclamar que o método não lança nem pegaThrowable
? Portanto, é provável que um elencoIOException
seja 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 algumasinstanceof
verificações (ou algo com um objetivo semelhante) para verificar qual exceção verificada foi lançada.Você pode!
Estendendo @marcg 's
UtilException
e adicionandothrow E
onde 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
LambdaExceptionUtil
no seu IDE e use-o conforme mostrado abaixoLambdaExceptionUtilTest
.Alguns testes para mostrar uso e comportamento:
fonte
<Integer>
antesmap
. De fato, o compilador java não pode inferir oInteger
tipo de retorno. Tudo o resto deve estar correto.Exception
.Basta usar qualquer um dos NoException (meu projeto), Desmarcado de jOOλ , jogando-lambdas , interfaces de Throwable , ou Faux Pas .
fonte
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
fonte
Esta resposta é semelhante a 17, mas evitando a definição de exceção do wrapper:
fonte
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:
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.
fonte
.orThrowChecked()
método ao seu projeto que permita que a própria exceção verificada seja lançada . Dê uma olhada na minhaUtilException
resposta nesta página e veja se você gosta da ideia de adicionar essa terceira possibilidade ao seu projeto.Stream
implementaAutoCloseable
?MyException
precede precisa ser uma exceção desmarcada?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.
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.
fonte
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:
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á-laRuntimeException
e sem precisar declará-la explicitamente.fonte
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:
fonte
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.
fonte
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.
em seguida, implemente-o usando Lambdas ou referências, como mostrado abaixo.
fonte
A única maneira
map
interna de lidar com exceções verificadas que podem ser lançadas por uma operação é encapsulá-las dentro de aCompletableFuture
. (UmaOptional
é 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
map
conclusão da operação. Aqui está o que parece:Isso produz a seguinte saída:
O
applyOrDie
método pega umFunction
que lança uma exceção e o converte em umFunction
que retorna um já concluídoCompletableFuture
- 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 emStream<CompletableFuture<T>>
vez de apenas umStream<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" daCompletableFuture
-cfCollector()
nos permite fazer isso usando umsupplier
,accumulator
,combiner
, efinisher
que não precisa saber nada sobreCompletableFuture
.Os métodos auxiliares podem ser encontrados no GitHub na minha
MonadUtils
classe, que ainda é um trabalho em andamento.fonte
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:
OU
A 2ª implementação evita agrupar a exceção em um arquivo
RuntimeException
.throwUnchecked
funciona porque quase sempre todas as exceções genéricas são tratadas como desmarcadas no java.fonte
Eu uso esse tipo de exceção de quebra automática:
Isso exigirá o tratamento estatístico dessas exceções:
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á-lasthrows
). E nadainstanceof
ou algo é necessário.fonte
Eu acho que essa abordagem é a correta:
Quebrando a exceção marcada dentro de
Callable
em umUndeclaredThrowableException
(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.
fonte