Problemumgehung für von Java überprüfte Ausnahmen

49

Ich schätze die neuen Java 8-Funktionen für Lambdas und Standardmethoden-Schnittstellen sehr. Trotzdem langweilen mich geprüfte Ausnahmen. Wenn ich zum Beispiel nur alle sichtbaren Felder eines Objekts auflisten möchte, möchte ich einfach Folgendes schreiben:

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

Da die getMethode jedoch möglicherweise eine aktivierte Ausnahme auslöst, die nicht mit dem ConsumerSchnittstellenvertrag übereinstimmt , muss ich diese Ausnahme abfangen und den folgenden Code schreiben:

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

In den meisten Fällen möchte ich jedoch nur, dass die Ausnahme als geworfen wird RuntimeExceptionund das Programm die Ausnahme ohne Kompilierungsfehler verarbeiten kann oder nicht.

Ich würde gerne Ihre Meinung zu meiner umstrittenen Problemumgehung für gestörte Ausnahmen erfahren. Zu diesem Zweck habe ich eine Hilfsschnittstelle ConsumerCheckException<T>und eine Utility-Funktion rethrow( aktualisiert gemäß dem Vorschlag von Dovals Kommentar ) wie folgt erstellt:

  @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;
      }
  }

Und jetzt kann ich einfach schreiben:

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

Ich bin mir nicht sicher, ob dies die beste Redewendung ist, um die überprüften Ausnahmen umzukehren, aber wie ich erklärte, hätte ich gerne einen bequemeren Weg, um mein erstes Beispiel zu erreichen, ohne mit überprüften Ausnahmen umzugehen, und dies ist der einfachere Weg, den ich gefunden habe es zu tun.

Fernando Miguel Carvalho
quelle
2
Schauen Sie sich neben Roberts Link auch Sneakily Throwing Checked Exceptions an . Wenn Sie möchten, können Sie die ursprüngliche, aktivierte Ausnahme mit sneakyThrowinside of rethrowauslösen, anstatt sie in a zu verpacken RuntimeException. Alternativ können Sie auch die @SneakyThrowsAnnotation von Project Lombok verwenden , die dasselbe tut.
Doval
1
Beachten Sie auch, dass das Consumers in forEachbei Verwendung von parallelen Streams parallel ausgeführt werden kann . Ein Throwable, der vom Withing des Verbrauchers ausgelöst wurde, wird dann an den aufrufenden Thread weitergegeben, was 1) die anderen gleichzeitig laufenden Verbraucher nicht anhält, was angemessen sein kann oder nicht, und 2) wenn mehr als einer der Verbraucher nur etwas auslöst Eines der Threads wird vom aufrufenden Thread gesehen.
Joonas Pulakka
2
Lambdas sind so hässlich.
Tulains Córdova

Antworten:

16

Vor- und Nachteile sowie Einschränkungen Ihrer Technik:

  • Wenn der aufrufende Code die aktivierte Ausnahme behandeln soll, MUSS er der throws-Klausel der Methode hinzugefügt werden, die den Stream enthält. Der Compiler wird Sie nicht mehr zum Hinzufügen zwingen, sodass Sie es leichter vergessen können. Zum Beispiel:

    public void test(Object p) throws IllegalAccessException {
        Arrays.asList(p.getClass().getFields()).forEach(rethrow(f -> System.out.println(f.get(p))));
    }
  • Wenn der aufrufende Code die geprüfte Ausnahme bereits behandelt, werden Sie vom Compiler daran erinnert, die throws-Klausel zur Methodendeklaration hinzuzufügen, die den Stream enthält. .

  • In jedem Fall können Sie den Stream selbst nicht umgeben, um die aktivierte Ausnahme INNERHALB der Methode abzufangen, die den Stream enthält (wenn Sie es versuchen, sagt der Compiler: Ausnahme wird niemals im Hauptteil der entsprechenden try-Anweisung ausgelöst).

  • Wenn Sie eine Methode aufrufen, die die deklarierte Ausnahme buchstäblich nie auslösen kann, sollten Sie die throws-Klausel nicht einschließen. Beispiel: Ein neuer String (byteArr, "UTF-8") löst eine UnsupportedEncodingException aus, aber die Java-Spezifikation garantiert, dass UTF-8 immer vorhanden ist. Hier ist die Deklaration der Würfe ein Ärgernis und jede Lösung, um sie mit minimalem Boilerplate zum Schweigen zu bringen, ist willkommen.

  • Wenn Sie geprüfte Ausnahmen hassen und der Meinung sind, dass sie zunächst nie zur Java-Sprache hinzugefügt werden sollten (eine wachsende Anzahl von Menschen denkt so, und ich bin NICHT einer von ihnen), fügen Sie die geprüfte Ausnahme einfach nicht zu den Würfen hinzu Klausel der Methode, die den Stream enthält. Die aktivierte Ausnahme verhält sich dann wie eine nicht aktivierte Ausnahme.

  • Wenn Sie eine strenge Schnittstelle implementieren, in der Sie keine Option zum Hinzufügen einer Throws-Deklaration haben und dennoch eine Ausnahme auslösen können, führt das Umschließen einer Ausnahme, um das Privileg des Auslösens zu erhalten, zu einem Stacktrace mit unechten Ausnahmen, die keine Angaben dazu machen, was tatsächlich schief gelaufen ist. Ein gutes Beispiel ist Runnable.run (), das keine aktivierten Ausnahmen auslöst. In diesem Fall können Sie entscheiden, die aktivierte Ausnahme nicht der Throws-Klausel der Methode hinzuzufügen, die den Stream enthält.

  • Wenn Sie die geprüfte Ausnahme NICHT zur throws-Klausel der Methode hinzufügen (oder vergessen), die den Stream enthält, müssen Sie auf jeden Fall die folgenden zwei Konsequenzen berücksichtigen, wenn Sie CHECKED-Ausnahmen auslösen:

    1. Der aufrufende Code kann ihn nicht anhand des Namens abfangen (wenn Sie es versuchen, sagt der Compiler: Ausnahme wird niemals in den Hauptteil der entsprechenden try-Anweisung geworfen). Es sprudelt und wird wahrscheinlich von einer "catch Exception" oder "catch Throwable" in der Hauptprogrammschleife abgefangen, was Sie vielleicht sowieso wollen.

    2. Dies verstößt gegen das Prinzip der geringsten Überraschung: Es reicht nicht mehr aus, RuntimeException abzufangen, um sicherzustellen, dass alle möglichen Ausnahmen abgefangen werden. Aus diesem Grund sollte dies meines Erachtens nicht im Framework-Code erfolgen, sondern nur im Business-Code, den Sie vollständig steuern.

Verweise:

HINWEIS: Wenn Sie sich für diese Technik entscheiden, können Sie die LambdaExceptionUtilHilfsklasse von StackOverflow kopieren : https://stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-java-8 -Flüsse . Sie erhalten die vollständige Implementierung (Funktion, Verbraucher, Lieferant ...) mit Beispielen.

MarcG
quelle
6

Kann es in diesem Beispiel jemals wirklich scheitern? Denken Sie nicht so, aber möglicherweise ist Ihr Fall speziell. Wenn es wirklich "nicht scheitern kann" und es nur eine nervige Compiler-Sache ist, möchte ich die Ausnahme einpacken und ein Errormit einem Kommentar "nicht passieren kann" auslösen. Macht die Dinge für die Wartung sehr klar . Andernfalls werden sie sich fragen: "Wie kann das passieren?" Und "Wer zum Teufel macht das?"

Dies ist in einer kontroversen Praxis so YMMV. Ich werde wahrscheinlich ein paar Abstimmungen bekommen.

user949300
quelle
3
+1, weil Sie einen Fehler auslösen. Wie oft bin ich nach stundenlangem Debuggen an einem Catch-Block gelandet, der nur einen einzigen Zeilenkommentar enthielt "kann nicht passieren" ...
Axel
3
-1, weil Sie einen Fehler auslösen. Fehler weisen darauf hin, dass in JVM etwas nicht stimmt und die höheren Ebenen möglicherweise entsprechend damit umgehen. Wenn Sie diesen Weg wählen, sollten Sie RuntimeException auslösen. Eine weitere mögliche Problemumgehung sind Asserts (Need-EA-Flag aktiviert) oder die Protokollierung.
Duros
3
@duros: Abhängig davon, warum die Ausnahme "nicht passieren kann", kann die Tatsache, dass sie ausgelöst wird, darauf hindeuten, dass etwas schwer falsch ist. Nehmen wir zum Beispiel an, man ruft cloneeinen versiegelten Typ an, von Foodem bekannt ist, dass er ihn unterstützt, und er wirft CloneNotSupportedException. Wie könnte das passieren, wenn der Code nicht mit einer anderen unerwarteten Art von Code verknüpft würde Foo? Und wenn das passiert, kann man irgendetwas vertrauen?
Supercat
1
@supercat ausgezeichnetes Beispiel. Andere lösen Ausnahmen von fehlenden Zeichenfolgencodierungen oder MessageDigests aus. Wenn UTF-8 oder SHA fehlen, ist Ihre Laufzeit wahrscheinlich beschädigt.
user949300
1
@duros Das ist ein völliger Irrtum. An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.(zitiert aus dem Javadoc von Error) Dies ist genau diese Art von Situation, die bei weitem nicht unzureichend ist. Das Werfen eines ErrorHere ist die am besten geeignete Option.
biziclop
1

Eine andere Version dieses Codes, bei der die aktivierte Ausnahme nur verzögert ist:

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
}

Die Magie ist, dass, wenn Sie anrufen:

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

dann ist Ihr Code verpflichtet, die Ausnahme zu fangen

Java-Bär
quelle
Was passiert, wenn dies in einem parallelen Stream ausgeführt wird, in dem Elemente in verschiedenen Threads verwendet werden?
Prunge
0

sneakyThrow ist cool! Ich fühle total deinen Schmerz.

Wenn Sie in Java bleiben müssen ...

Paguro hat funktionale Schnittstellen, die geprüfte Ausnahmen umbrechen, so dass Sie nicht mehr darüber nachdenken müssen. Es enthält unveränderliche Sammlungen und Funktionstransformationen in Anlehnung an Clojure-Transducer (oder Java 8-Streams), die die funktionalen Schnittstellen berücksichtigen und geprüfte Ausnahmen umbrechen.

Es gibt auch VAVr und The Eclipse Collections zum Ausprobieren.

Verwenden Sie andernfalls Kotlin

Kotlin ist 2-Wege-kompatibel mit Java, hat keine geprüften Ausnahmen, keine Grundelemente (na ja, über die der Programmierer nicht nachdenken muss), ein besseres Typsystem, setzt Unveränderlichkeit voraus usw. Auch wenn ich die Paguro-Bibliothek oben kuratiere, habe ich Ich konvertiere den gesamten Java-Code, den ich kann, nach Kotlin. Alles, was ich früher in Java gemacht habe, mache ich jetzt lieber in Kotlin.

GlenPeterson
quelle
Jemand hat dies abgelehnt, was in Ordnung ist, aber ich werde nichts lernen, wenn Sie mir nicht sagen, warum Sie es abgelehnt haben.
GlenPeterson
1
+1 für Ihre Bibliothek in Github. Sie können auch eclipse.org/collections oder project vavr überprüfen, die funktionale und unveränderliche Sammlungen bereitstellen. Was denkst du darüber?
Firephil
-1

Überprüfte Ausnahmen sind sehr nützlich und ihre Handhabung hängt von der Art des Produkts ab, zu dem Ihr Code gehört:

  • eine Bibliothek
  • eine Desktop-Anwendung
  • Ein Server, der in einer Box läuft
  • eine akademische Übung

Normalerweise darf eine Bibliothek keine geprüften Ausnahmen verarbeiten, sondern muss als von der öffentlichen API geworfen deklariert werden. Der seltene Fall, in dem sie in RuntimeExcepton eingebunden werden könnten, besteht darin, im Bibliothekscode ein privates XML-Dokument zu erstellen und es zu analysieren, eine temporäre Datei zu erstellen und diese dann zu lesen.

Für Desktop-Anwendungen müssen Sie die Produktentwicklung starten, indem Sie Ihr eigenes Framework für die Ausnahmebehandlung erstellen. Wenn Sie ein eigenes Framework verwenden, verpacken Sie normalerweise eine aktivierte Ausnahme in zwei bis vier Wrapper (Ihre benutzerdefinierten Unterklassen von RuntimeExcepion) und behandeln sie mit einem einzigen Exception-Handler.

Bei Servern wird Ihr Code normalerweise innerhalb eines Frameworks ausgeführt. Wenn Sie keinen (nackten) Server verwenden, erstellen Sie ein eigenes Framework (basierend auf den oben beschriebenen Laufzeiten).

Für akademische Übungen können Sie diese jederzeit direkt in RuntimeExcepton einbinden. Jemand kann auch einen winzigen Rahmen schaffen.

Razmik
quelle
-1

Vielleicht möchten Sie sich dieses Projekt ansehen . Ich habe es speziell wegen des Problems der überprüften Ausnahmen und funktionalen Schnittstellen erstellt.

Damit können Sie dies tun, anstatt Ihren benutzerdefinierten Code zu schreiben:

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

Da alle diese Throwing * -Schnittstellen ihre nicht-Throwing-Gegenstücke erweitern, können Sie sie direkt im Stream verwenden:

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

Oder Sie können einfach:

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

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

Und andere Dinge.

fge
quelle
Besser nochfields.forEach((ThrowingConsumer<Field>) f -> out.println(f.get(o)))
user2418306
Würden die Downvoter das bitte erklären?
25.