Soluzione alternativa per Java controllate le eccezioni

49

Apprezzo molto le nuove funzionalità di Java 8 su lambdas e le interfacce dei metodi predefinite. Tuttavia, mi annoio ancora con le eccezioni controllate. Ad esempio, se voglio solo elencare tutti i campi visibili di un oggetto, vorrei semplicemente scrivere questo:

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

Tuttavia, poiché il metodo get potrebbe generare un'eccezione controllata, che non concorda con il contratto di interfaccia Consumer , allora devo rilevare quell'eccezione e scrivere il seguente codice:

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

Tuttavia nella maggior parte dei casi voglio solo che l'eccezione venga generata come RuntimeException e che il programma gestisca o meno l'eccezione senza errori di compilazione.

Quindi, vorrei avere la tua opinione sulla mia soluzione controverso per il fastidio delle eccezioni controllate. A tal fine, ho creato un'interfaccia ausiliaria ConsumerCheckException<T> e una funzione di utilità rethrow ( aggiornata in base alla supposizione di commento di Doval ) come 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 ora posso solo scrivere:

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

Non sono sicuro che questo sia l'idioma migliore per aggirare le eccezioni controllate, ma come ho spiegato, mi piacerebbe avere un modo più conveniente per raggiungere il mio primo esempio senza considerare le eccezioni controllate e questo è il modo più semplice che ho trovato di farlo.

    
posta Fernando Miguel Carvalho 29.01.2014 - 14:56
fonte

6 risposte

16

Vantaggi, svantaggi e limiti della tua tecnica:

  • Se il codice chiamante deve gestire l'eccezione verificata DEVI aggiungerlo alla clausola throws del metodo che contiene lo stream. Il compilatore non ti costringerà ad aggiungerlo più, quindi è più facile dimenticarlo. Ad esempio:

    public void test(Object p) throws IllegalAccessException {
        Arrays.asList(p.getClass().getFields()).forEach(rethrow(f -> System.out.println(f.get(p))));
    }
    
  • Se il codice chiamante gestisce già l'eccezione verificata, il compilatore ti ricorderà di aggiungere la clausola throws alla dichiarazione del metodo che contiene lo stream (se non lo fai, dirà: l'eccezione non viene mai generata nel corpo dell'istruzione try corrispondente).

  • In ogni caso, non sarai in grado di circondare il flusso stesso per catturare l'eccezione controllata INSIDE il metodo che contiene lo stream (se provi, il compilatore dirà: L'eccezione non viene mai generata nel corpo dell'istruzione try corrispondente).

  • Se si chiama un metodo che letteralmente non può mai generare l'eccezione che dichiara, non si deve includere la clausola throws. Ad esempio: new String (byteArr, "UTF-8") lancia UnsupportedEncodingException, ma UTF-8 è garantito dalle specifiche Java per essere sempre presente. Qui, la dichiarazione dei lanci è una seccatura e qualsiasi soluzione per zittirlo con un numero minimo di regole è benvenuta.

  • Se odi le eccezioni controllate e senti che non dovrebbero mai essere aggiunte al linguaggio Java per cominciare (un numero crescente di persone pensa in questo modo, e io NON sono uno di loro), quindi non aggiungere l'eccezione verificata alla clausola throws del metodo che contiene lo stream. Il controllato l'eccezione, quindi, si comporterà come un'eccezione UNkecked.

  • Se stai implementando un'interfaccia rigorosa in cui non hai la possibilità di aggiungere una dichiarazione di proiezione, e tuttavia lanciare un'eccezione è del tutto appropriato, quindi il wrapping di un'eccezione solo per ottenere il privilegio di lanciarlo risulta in uno stacktrace con eccezioni spurie che non fornire informazioni su cosa effettivamente è andato storto. Un buon esempio è Runnable.run (), che non lancia eccezioni controllate. In questo caso, puoi decidere di non aggiungere l'eccezione verificata alla clausola throws del metodo che contiene lo stream.

  • In ogni caso, se si decide di NON aggiungere (o dimenticare di aggiungere) l'eccezione controllata alla clausola throws del metodo che contiene lo stream, essere a conoscenza di queste 2 conseguenze del lancio delle eccezioni CONTROLLATE:

    1. Il codice chiamante non sarà in grado di catturarlo per nome (se ci provi, il compilatore dirà: l'eccezione non viene mai lanciata nel corpo del tentativo corrispondente dichiarazione). Scoppierà e probabilmente verrà catturato nel ciclo del programma principale da alcuni "catch Exception" o "catch Throwable", che potrebbe essere ciò che voglio comunque

    2. Violenta il principio di minima sorpresa: non sarà più sufficiente per catturare RuntimeException per essere in grado di garantire la cattura di tutti possibili eccezioni. Per questo motivo, credo che questo non dovrebbe essere fatto nel codice framework, ma solo nel codice aziendale che controlli completamente.

Riferimenti:

NOTA: se decidi di utilizzare questa tecnica, puoi copiare la classe helper LambdaExceptionUtil da StackOverflow: link . Ti dà l'implementazione completa (Funzione, Consumatore, Fornitore ...), con esempi.

    
risposta data 26.12.2014 - 20:29
fonte
6

In questo esempio, può davvero fallire? Non pensarlo, ma forse il tuo caso è speciale. Se davvero "non può fallire", ed è solo una noiosa cosa del compilatore, mi piace avvolgere l'eccezione e lanciare un Error con un commento "non può accadere". Rende le cose molto chiare per la manutenzione. Altrimenti si chiederanno "come può accadere questo" e "chi diavolo lo gestisce?"

Questo in una pratica controversa, quindi YMMV. Probabilmente avrò dei downvotes.

    
risposta data 30.01.2014 - 07:31
fonte
1

un'altra versione di questo codice in cui l'eccezione verificata è appena ritardata:

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
}

la magia è che se chiami:

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

allora il tuo codice sarà obbligato a rilevare l'eccezione

    
risposta data 06.10.2014 - 18:07
fonte
-1

Le eccezioni controllate sono molto utili e la loro gestione dipende dal tipo di prodotto di cui fai parte il codice:

  • una libreria
  • un'applicazione desktop
  • un server in esecuzione all'interno di una casella
  • un esercizio accademico

Di solito una libreria non deve gestire eccezioni controllate, ma dichiarare come generate da API pubbliche. Il caso raro in cui potrebbero essere racchiusi in un semplice RuntimeExcepton è, ad esempio, creare all'interno del codice della libreria un documento privato XML e analizzarlo, creare un file temporaneo e leggerlo.

Per le applicazioni desktop è necessario avviare lo sviluppo del prodotto creando il proprio framework di gestione delle eccezioni. Usando il proprio framework di solito si avvolge un'eccezione controllata in alcuni dei due o quattro wrapper (le sottoclassi personalizzate di RuntimeExcepion) e gestita da un singolo gestore di eccezioni.

Per i server solitamente il tuo codice verrà eseguito all'interno di qualche framework. Se non si utilizza alcun (un server nudo) creare il proprio framework simile (basato su Runtime) descritto in precedenza.

Per gli esercizi accademici puoi sempre raggrupparli direttamente in RuntimeExcepton. Qualcuno può anche creare un piccolo framework.

    
risposta data 04.06.2015 - 08:19
fonte
-1

Potresti dare un'occhiata a questo progetto ; L'ho creato appositamente a causa del problema delle eccezioni controllate e delle interfacce funzionali.

Con esso puoi farlo invece di scrivere il tuo codice personalizzato:

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

Poiché tutte le interfacce di Throwing * estendono le loro controparti non di lancio, puoi quindi usarle direttamente nello stream:

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

O puoi solo:

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

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

E altre cose.

    
risposta data 29.12.2014 - 23:07
fonte
-1

sneakyThrow è bello! Sono assolutamente sento il vostro dolore. Paguro ha interfacce funzionali che racchiudono le eccezioni controllate in modo da non dover più pensare a questo. Comprende collezioni e trasformazioni funzionali immutabili lungo le linee dei trasduttori Clojure, o Java 8 Stream che prendono le interfacce funzionali e avvolgono le eccezioni controllate.

    
risposta data 02.12.2015 - 17:54
fonte