Estou com um problema ao tentar as expressões Lambda do Java 8. Geralmente funciona bem, mas agora tenho métodos que lançam IOException
. É melhor se você observar o seguinte código:
class Bank{
....
public Set<String> getActiveAccountNumbers() throws IOException {
Stream<Account> s = accounts.values().stream();
s = s.filter(a -> a.isActive());
Stream<String> ss = s.map(a -> a.getNumber());
return ss.collect(Collectors.toSet());
}
....
}
interface Account{
....
boolean isActive() throws IOException;
String getNumber() throws IOException;
....
}
O problema é que ele não é compilado, porque eu tenho que capturar as possíveis exceções dos métodos isActive e getNumber. Mas, mesmo que eu use explicitamente um bloco try-catch-Block, como abaixo, ele ainda não será compilado porque não capto a exceção. Portanto, ou há um erro no JDK ou não sei como capturar essas exceções.
class Bank{
....
//Doesn't compile either
public Set<String> getActiveAccountNumbers() throws IOException {
try{
Stream<Account> s = accounts.values().stream();
s = s.filter(a -> a.isActive());
Stream<String> ss = s.map(a -> a.getNumber());
return ss.collect(Collectors.toSet());
}catch(IOException ex){
}
}
....
}
Como posso fazê-lo funcionar? Alguém pode me sugerir a solução certa?
java
exception-handling
lambda
java-8
Martin Weber
fonte
fonte
Respostas:
Você deve capturar a exceção antes que ela escape do lambda:
Considere o fato de que o lambda não é avaliado no local em que você o escreve, mas em algum local completamente não relacionado, dentro de uma classe JDK. Portanto, esse seria o ponto em que essa exceção verificada seria lançada e nesse local não será declarada.
Você pode lidar com isso usando um wrapper de seu lambda que converte exceções verificadas em desmarcadas:
Seu exemplo seria escrito como
Nos meus projetos, eu lido com esse problema sem embrulhar; em vez disso, uso um método que efetivamente desativa a verificação de exceções do compilador. Escusado será dizer que isso deve ser tratado com cuidado e todos no projeto devem estar cientes de que uma exceção verificada pode aparecer onde não for declarada. Este é o código do encanamento:
e você pode esperar um
IOException
golpe na sua cara, mesmo quecollect
não o declare. Na maioria dos casos, mas não em todos os casos da vida real, você deseja refazer a exceção, de qualquer maneira, e tratá-la como uma falha genérica. Em todos esses casos, nada se perde em clareza ou correção. Apenas tome cuidado com os outros casos em que você realmente deseja reagir à exceção no local. O desenvolvedor não será avisado pelo compilador de que há um problemaIOException
para capturar lá e o compilador de fato reclamará se você tentar capturá-lo, porque nós o enganamos, acreditando que nenhuma exceção pode ser lançada.fonte
Você também pode propagar sua dor estática com lambdas, para que tudo pareça legível:
propagate
aqui recebejava.util.concurrent.Callable
como parâmetro e converte qualquer exceção capturada durante a chamada emRuntimeException
. Existe um método de conversão semelhante Throwables # propagate (Throwable) na Goiaba.Esse método parece ser essencial para o encadeamento do método lambda, portanto, espero que um dia ele seja adicionado a uma das bibliotecas populares ou esse comportamento de propagação seja por padrão.
fonte
Essa
UtilException
classe auxiliar permite usar quaisquer exceções verificadas nos fluxos Java, como este:Nota
Class::forName
lançaClassNotFoundException
, que está marcado . 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
UtilException
):Mas não o use antes de entender as seguintes vantagens, desvantagens e limitações :
• Se o código de chamada for manipular a exceção verificada, você DEVE adicioná-lo à cláusula throws do método que contém o fluxo. O compilador não forçará você a adicioná-lo mais, por isso é mais fácil esquecê-lo.
• Se o código de chamada já manipular a exceção verificada, o compilador lembrará que você deve adicionar a cláusula throws à declaração do método que contém o fluxo (caso contrário, ele dirá: A exceção nunca é lançada no corpo da instrução try correspondente )
• Em qualquer caso, você não poderá cercar o próprio fluxo para capturar a exceção verificada INSIDE no método que contém o fluxo (se você tentar, o compilador dirá: A exceção nunca é lançada no corpo da instrução try correspondente).
• Se você está chamando um método que literalmente nunca pode lançar a exceção declarada, não deve incluir a cláusula throws. 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.
• Se você odeia exceções verificadas e acha que elas nunca devem ser adicionadas à linguagem Java (um número crescente de pessoas pensa dessa maneira, e eu NÃO SOU uma delas), simplesmente não adicione a exceção verificada ao lança a cláusula do método que contém o fluxo. A exceção verificada se comportará como uma exceção NÃO Verificada.
• Se você estiver implementando uma interface estrita na qual você não tem a opção de adicionar uma declaração de throws e ainda assim lançar uma exceção for inteiramente apropriado, agrupar uma exceção apenas para obter o privilégio de lançá-la resulta em um stacktrace 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. Nesse caso, você pode decidir não adicionar a exceção verificada à cláusula throws do método que contém o fluxo.
• De qualquer forma, se você decidir NÃO adicionar (ou esquecer de adicionar) a exceção verificada à cláusula throws do método que contém o fluxo, esteja ciente destas 2 consequências de lançar exceções CHECKED:
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.
Concluindo: acredito que as limitações aqui não são sérias e a
UtilException
classe pode ser usada sem medo. No entanto, cabe a você!fonte
Você pode potencialmente rolar sua própria
Stream
variante envolvendo sua lambda para lançar uma exceção não verificada e depois desembrulhando essa exceção não verificada nas operações do terminal:Então você pode usar isso da mesma maneira exata que
Stream
:Essa solução exigiria um pouco de clichê, então sugiro que você dê uma olhada na biblioteca que já criei, que faz exatamente o que descrevi aqui para toda a
Stream
classe (e mais!).fonte
new StreamBridge<>(ts, IOException.class);
->new StreamBridge<>(s, IOException.class);
StreamAdapter
.Use o método #propagate (). Exemplo de implementação não Guava do Java 8 Blog de Sam Beran :
fonte
Isso não responde diretamente à pergunta (existem muitas outras respostas), mas tenta evitar o problema em primeiro lugar:
Na minha experiência, a necessidade de lidar com exceções em uma
Stream
(ou outra expressão lambda) geralmente vem do fato de que as exceções são declaradas lançadas a partir de métodos nos quais elas não devem ser lançadas. Isso geralmente vem da mistura da lógica de negócios com entrada e saída. SuaAccount
interface é um exemplo perfeito:Em vez de jogar um
IOException
em cada getter, considere este design:O método
AccountReader.readAccount(…)
pode ler uma conta de um banco de dados ou arquivo ou qualquer outra coisa e lançar uma exceção se não for bem-sucedida. Ele constrói umAccount
objeto que já contém todos os valores, pronto para ser usado. Como os valores já foram carregadosreadAccount(…)
, os getters não lançariam uma exceção. Assim, você pode usá-los livremente em lambdas sem a necessidade de quebrar, mascarar ou ocultar as exceções.Claro que nem sempre é possível fazê-lo da maneira que descrevi, mas muitas vezes é e leva a um código mais limpo (IMHO):
throws IOException
para não usar, mas para satisfazer o compiladorAccount
imutável e lucrar com as vantagens (por exemplo, segurança da linha)Account
em lambdas (por exemplo, em aStream
)fonte
Pode ser resolvido abaixo do código simples com Stream e Try no AbacusUtil :
Divulgação: Eu sou o desenvolvedor do
AbacusUtil
.fonte
Estendendo a solução @marcg, você normalmente pode lançar e capturar uma exceção verificada no Streams; isto é, o compilador solicitará que você pegue / lance novamente como você estava fora dos fluxos !!
Em seguida, seu exemplo seria escrito da seguinte maneira (adicionando testes para mostrá-lo mais claramente):
fonte
Para adicionar corretamente o código de manipulação IOException (ao RuntimeException), seu método será semelhante a este:
O problema agora é que o
IOException
arquivo terá que ser capturado como umRuntimeException
e convertido novamente em umIOException
- e isso adicionará ainda mais código ao método acima.Por que usar
Stream
quando isso pode ser feito da seguinte maneira - e o método é executadoIOException
para que também não seja necessário código extra para isso:fonte
Mantendo essa questão em mente, desenvolvi uma pequena biblioteca para lidar com exceções e lambdas verificadas. Adaptadores personalizados permitem integrar com tipos funcionais existentes:
https://github.com/TouK/ThrowingFunction/
fonte
Seu exemplo pode ser escrito como:
A classe Unthrow pode ser obtida aqui https://github.com/SeregaLBN/StreamUnthrower
fonte
Se você não se importa em usar bibliotecas de terceiros, a cyclops-react lib da AOL , disclosure :: eu sou um colaborador, possui uma classe ExceptionSoftener que pode ajudar aqui.
fonte
As interfaces funcionais em Java não declaram nenhuma exceção marcada ou desmarcada. Precisamos alterar a assinatura dos métodos de:
Para:
Ou manipule-o com o bloco try-catch:
Outra opção é escrever um wrapper personalizado ou usar uma biblioteca como ThrowingFunction. Com a biblioteca, precisamos adicionar apenas a dependência ao nosso pom.xml:
E use as classes específicas como ThrowingFunction, ThrowingConsumer, ThrowingPredicate, ThrowingRunnable, ThrowingSupplier.
No final, o código fica assim:
fonte
Não vejo nenhuma maneira de lidar com a exceção verificada no fluxo (Java -8), a única maneira que apliquei é pegar a exceção verificada no fluxo e lançá-las novamente como exceção não verificada.
fonte
Se você deseja manipular a exceção no fluxo e continuar processando as adicionais, há um excelente artigo no DZone de Brian Vermeer, usando o conceito de Either. Ele mostra uma excelente maneira de lidar com essa situação. A única coisa que falta é o código de exemplo. Esta é uma amostra da minha exploração usando os conceitos desse artigo.
fonte