Solução alternativa para exceções verificadas por Java


49

Aprecio muito os novos recursos do Java 8 sobre interfaces lambdas e métodos padrão. No entanto, ainda me aborreço com exceções verificadas. Por exemplo, se eu apenas quiser listar todos os campos visíveis de um objeto, gostaria de simplesmente escrever isto:

    Arrays.asList(p.getClass().getFields()).forEach(
        f -> System.out.println(f.get(p))
    );

No entanto, como o getmétodo pode lançar uma exceção verificada, o que não concorda com o Consumercontrato de interface, devo capturar essa exceção e escrever o seguinte código:

    Arrays.asList(p.getClass().getFields()).forEach(
            f -> {
                try {
                    System.out.println(f.get(p));
                } catch (IllegalArgumentException | IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }
            }
    );

No entanto, na maioria dos casos, eu só quero que a exceção seja lançada como um RuntimeExceptione deixe o programa manipular, ou não, a exceção sem erros de compilação.

Portanto, gostaria de ter sua opinião sobre minha solução controversa para aborrecimento de exceções verificadas. Para isso, criei uma interface auxiliar ConsumerCheckException<T>e uma função de utilitário rethrow( atualizada de acordo com a sugestão do comentário de Doval ) da seguinte forma:

  @FunctionalInterface
  public interface ConsumerCheckException<T>{
      void accept(T elem) throws Exception;
  }

  public class Wrappers {
      public static <T> Consumer<T> rethrow(ConsumerCheckException<T> c) {
        return elem -> {
          try {
            c.accept(elem);
          } catch (Exception ex) {
            /**
             * within sneakyThrow() we cast to the parameterized type T. 
             * In this case that type is RuntimeException. 
             * At runtime, however, the generic types have been erased, so 
             * that there is no T type anymore to cast to, so the cast
             * disappears.
             */
            Wrappers.<RuntimeException>sneakyThrow(ex);
          }
        };
      }

      /**
       * Reinier Zwitserloot who, as far as I know, had the first mention of this
       * technique in 2009 on the java posse mailing list.
       * http://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html
       */
      public static <T extends Throwable> T sneakyThrow(Throwable t) {
          throw (T) t;
      }
  }

E agora eu posso escrever:

    Arrays.asList(p.getClass().getFields()).forEach(
            rethrow(f -> System.out.println(f.get(p)))
    );

Não tenho certeza de que esse seja o melhor idioma para contornar as exceções verificadas, mas, como expliquei, gostaria de ter uma maneira mais conveniente de obter meu primeiro exemplo sem lidar com as exceções verificadas, e essa é a maneira mais simples que encontrei para fazer isso.



2
Além do link de Robert, também dê uma olhada em Sneakily Throwing Exceptions Checked . Se você quisesse, poderia usar sneakyThrowdentro de rethrowpara lançar a exceção verificada original em vez de agrupá-la em um RuntimeException. Como alternativa, você pode usar a @SneakyThrowsanotação do Projeto Lombok que faz a mesma coisa.
Doval

11
Observe também que os Consumers in forEachpodem ser executados de maneira paralela ao usar Streams paralelos . Um lançamento jogável de dentro do consumidor será propagado para o encadeamento de chamada, que 1) não interromperá os outros consumidores em execução simultânea, o que pode ou não ser apropriado, e 2) se mais de um consumidor lançar algo, apenas um dos lançamentos será visto pelo thread de chamada.
Joonas Pulakka


2
Lambdas são tão feias.
Tulains Córdova

Respostas:


16

Vantagens, desvantagens e limitações de sua técnica:

  • 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. Por exemplo:

    public void test(Object p) throws IllegalAccessException {
        Arrays.asList(p.getClass().getFields()).forEach(rethrow(f -> System.out.println(f.get(p))));
    }
    
  • 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) .

  • De qualquer forma, você não poderá cercar o próprio fluxo para capturar a exceção verificada INSIDE o 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), apenas não adicione a exceção verificada aos lançamentos 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 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 contribua 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.

Referências:

NOTA: Se você decidir usar esta técnica, poderá copiar a LambdaExceptionUtilclasse auxiliar do StackOverflow: https://stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-java-8 -streams . Fornece a implementação completa (Função, Consumidor, Fornecedor ...), com exemplos.


6

Neste exemplo, ele pode realmente falhar? Não pense assim, mas talvez o seu caso seja especial. Se realmente "não pode falhar", e é apenas uma coisa irritante para o compilador, eu gosto de quebrar a exceção e lançar um Errorcom um comentário "não pode acontecer". Torna as coisas muito claras para manutenção. Caso contrário, eles se perguntarão "como isso pode acontecer" e "quem diabos lida com isso?"

Isso em uma prática controversa para YMMV. Provavelmente vou receber alguns votos negativos.


3
+1 porque você lançará um erro. Quantas vezes eu acabei depois de horas de depuração em um bloco catch que contém apenas uma única linha de comentário "não pode acontecer" ...
Axel

3
-1 porque você lançará um erro. Os erros indicam que algo está errado na JVM e os níveis superiores podem tratá-los adequadamente. Se você escolher esse caminho, você deve lançar RuntimeException. Outras soluções possíveis são afirmações (precisa de um sinalizador habilitado) ou log.
duros 28/03

3
@duros: Dependendo do motivo pelo qual a exceção "não pode acontecer", o fato de ser lançada pode indicar que algo está gravemente errado. Suponha, por exemplo, se alguém chama cloneum tipo selado Fooque é conhecido por apoiá-lo e ele lança CloneNotSupportedException. Como isso poderia acontecer, a menos que o código fosse vinculado a algum outro tipo inesperado de Foo? E se isso acontecer, algo pode ser confiável?
Supercat

11
@supercat excelente exemplo. Outros estão lançando exceções por falta de codificações de string ou de MessageDigests Se UTF-8 ou SHA estiverem ausentes, o tempo de execução provavelmente está corrompido.
user949300

11
@duros Esse é um completo equívoco. An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.(citado no Javadoc de Error) Esse é exatamente esse tipo de situação, portanto, longe de ser inadequado, jogar um Erroraqui é a opção mais apropriada.
biziclop

1

outra versão deste código em que a exceção verificada está apenas atrasada:

public class Cocoon {
         static <T extends Throwable> T forgetThrowsClause(Throwable t) throws T{
            throw (T) t;
        }

        public static <X, T extends Throwable> Consumer<X> consumer(PeskyConsumer<X,T> touchyConsumer) throws T {
            return new Consumer<X>() {
                @Override
                public void accept(X t) {
                    try {
                        touchyConsumer.accept(t);
                    } catch (Throwable exc) {
                        Cocoon.<RuntimeException>forgetThrowsClause(exc) ;
                    }

                }
            } ;
        }
// and so on for Function, and other codes from java.util.function
}

a mágica é que se você ligar:

 myArrayList.forEach(Cocoon.consumer(MyClass::methodThatThrowsException)) ;

seu código será obrigado a capturar a exceção


O que acontece se isso for executado em um fluxo paralelo onde os elementos são consumidos em diferentes segmentos?
usar o seguinte comando

0

sneakyThrow é legal! Eu sinto totalmente sua dor.

Se você tiver que ficar em Java ...

O Paguro possui interfaces funcionais que agrupam exceções verificadas, para que você não precise mais pensar nisso. Ele inclui coleções imutáveis ​​e transformações funcionais ao longo das linhas dos transdutores Clojure (ou Java 8 Streams) que recebem as interfaces funcionais e agrupam as exceções verificadas.

Há também o VAVr e o The Eclipse Collections para experimentar.

Caso contrário, use Kotlin

O Kotlin é compatível bidirecionalmente com Java, não possui exceções verificadas, nem primitivas (bem, não é o que o programador precisa pensar), um sistema de tipos melhorado, assume imutabilidade, etc. estou convertendo todo o código Java que posso para o Kotlin. Qualquer coisa que eu costumava fazer em Java, agora prefiro fazer no Kotlin.


Alguém votou negativamente, o que é bom, mas eu não vou aprender nada, a menos que você me diga por que votou negativamente.
GlenPeterson

11
+1 para sua biblioteca no github. Você também pode verificar eclipse.org/collections ou project vavr, que fornece coleções funcionais e imutáveis. Quais são seus pensamentos sobre isso?
firephil 16/03

-1

As exceções verificadas são muito úteis e seu manuseio depende do tipo de produto que você codifica como parte:

  • uma biblioteca
  • um aplicativo de desktop
  • um servidor rodando dentro de alguma caixa
  • um exercício acadêmico

Geralmente, uma biblioteca não deve manipular exceções verificadas, mas declarar como lançada pela API pública. O caso raro em que eles poderiam ser agrupados no RuntimeExcepton comum é, por exemplo, criar dentro do código da biblioteca algum documento xml particular e analisá-lo, criar algum arquivo temporário e depois ler esse documento.

Para aplicativos de desktop, você deve iniciar o desenvolvimento do produto criando sua própria estrutura de tratamento de exceções. Usando sua própria estrutura, geralmente você agrupa uma exceção verificada em dois a quatro wrappers (suas subclasses personalizadas de RuntimeExcepion) e as manipula por um único Exception Handler.

Para servidores, geralmente seu código será executado dentro de alguma estrutura. Se você não usar nenhum servidor (vazio), crie sua própria estrutura semelhante (com base nos tempos de execução) descritos acima.

Para exercícios acadêmicos, você sempre pode envolvê-los diretamente no RuntimeExcepton. Alguém pode criar uma estrutura minúscula também.


-1

Você pode dar uma olhada neste projeto ; Eu o criei especificamente por causa do problema de exceções verificadas e interfaces funcionais.

Com ele, você pode fazer isso em vez de escrever seu código personalizado:

// I don't know the type of f, so it's Foo...
final ThrowingConsumer<Foo> consumer = f -> System.out.println(f.get(p));

Como todas as interfaces Throwing * estendem suas contrapartes não Throwing, você pode usá-las diretamente no fluxo:

Arrays.asList(p.getClass().getFields()).forEach(consumer);

Ou você pode apenas:

import static com.github.fge.lambdas.consumers.Consumers.wrap;

Arrays.asList(p.getClass().getFields())
    .forEach(wrap(f -> System.out.println(f.get(p)));

E outras coisas


Melhor aindafields.forEach((ThrowingConsumer<Field>) f -> out.println(f.get(o)))
user2418306

Os downvoters poderiam explicar?
FGE
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.