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

49

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

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

No entanto, como o método get pode lançar uma exceção verificada, o que não concorda com o contrato de interface Consumer , 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, quero apenas que a exceção seja lançada como RuntimeException e deixe o programa tratar, ou não, a exceção sem erros de compilação.

Então, eu gostaria de ter sua opinião sobre a minha solução controversa para o aborrecimento das exceções verificadas. Para esse fim, criei uma interface auxiliar ConsumerCheckException<T> e uma função de utilitário rethrow ( atualizada de acordo com a sugestão de Comentário de Doval ) como segue:

  @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/[email protected]/msg05984.html
       */
      public static <T extends Throwable> T sneakyThrow(Throwable t) {
          throw (T) t;
      }
  }

E agora posso escrever:

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

Não tenho certeza se esse é o melhor idioma para reverter 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 eu encontrei para fazer isso.

    
por Fernando Miguel Carvalho 29.01.2014 / 14:56
fonte

6 respostas

16

Vantagens, desvantagens e limitações da 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 irá forçá-lo a adicioná-lo mais, então é 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á tratar a exceção verificada, o compilador LHE lembrará de adicionar a cláusula throws à declaração do método. que contém o fluxo (se você não diria: 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 dentro do 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, então você 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 lançamentos é um incômodo e qualquer solução para silenciá-lo com o 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 assim, e eu não sou um deles), então apenas não adicione a exceção verificada à cláusula throws do método que contém o fluxo. O verificado A exceção, então, 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 lançamentos, e ainda lançar uma exceção, totalmente apropriado, em seguida, envolvendo uma exceção apenas para obter o privilégio de lançá-lo resulta em um stacktrace com exceções espúrias que não contribuem com nenhuma informação sobre o que realmente deu errado. Um bom exemplo é o 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.

  • Em qualquer caso, 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 dessas 2 conseqüências de lançar exceções CHECKED:

    1. O código de chamada não será capaz de pegá-lo pelo nome (se você tentar, o compilador dirá: A exceção nunca é lançada no corpo da tentativa correspondente declaração). Ele irá borbulhar e provavelmente será capturado no loop principal do programa por alguma "captura Exception" ou "catch Throwable", que pode ser o que você quer de qualquer maneira.

    2. Ele viola o princípio da menor surpresa: não será mais suficiente capturar o RuntimeException para garantir a captura de todos possíveis exceções. Por esse motivo, acredito que isso não deva ser feito no código da estrutura, mas apenas no código comercial que você controla completamente.

Referências:

OBSERVAÇÃO: Se você decidir usar essa técnica, poderá copiar a classe LambdaExceptionUtil do StackOverflow: link . Ele fornece a implementação completa (Função, Consumidor, Fornecedor ...), com exemplos.

    
por 26.12.2014 / 20:29
fonte
6

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

Isso em uma prática controversa, então YMMV. Eu provavelmente vou conseguir alguns votos negativos.

    
por 30.01.2014 / 07:31
fonte
1

outra versão deste código onde a exceção verificada é 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 magia é que se você ligar:

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

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

    
por 06.10.2014 / 18:07
fonte
-1

As exceções verificadas são muito úteis e seu tratamento depende do tipo de produto do qual você faz parte do código:

  • uma biblioteca
  • um aplicativo de desktop
  • um servidor em execução dentro de alguma caixa
  • um exercício acadêmico

Normalmente, 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 em RuntimeExcepton simples é, por exemplo, criar dentro do código da biblioteca algum documento xml particular e analisá-lo, criando algum arquivo temporário e depois lendo isso.

Para aplicativos de área de trabalho, você deve iniciar o desenvolvimento do produto criando sua própria estrutura de tratamento de exceções. Utilizando sua própria estrutura, você geralmente envolve uma exceção verificada em alguns de dois a quatro wrappers (suas subclasses personalizadas de RuntimeExcepion) e manipula-os por um único manipulador de exceções.

Para servidores, geralmente seu código será executado dentro de algum framework. Se você não usar nenhum (um servidor vazio) crie sua própria estrutura semelhante (com base nos Runtimes) descrita acima.

Para exercícios acadêmicos, você sempre pode envolvê-los diretamente no RuntimeExcepton. Alguém pode criar um pequeno quadro também.

    
por 04.06.2015 / 08:19
fonte
-1

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

Com isso, 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));

Já que todas as interfaces do Throwing * estendem suas contrapartes do tipo não Throwing, você pode usá-las diretamente no stream:

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

Ou você pode simplesmente:

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.

    
por 29.12.2014 / 23:07
fonte
-1

sneakyThrow é legal! Eu sinto totalmente sua dor. O Paguro tem interfaces funcionais que envolvem as exceções verificadas para que você não tenha que pensar mais sobre isso. Ele inclui coleções imutáveis e transformações funcionais ao longo das linhas de transdutores Clojure, ou Java 8 Streams, que usam as interfaces funcionais e envolvem exceções verificadas.

    
por 02.12.2015 / 17:54
fonte