it-swarm-eu.dev

Problemumgehung für Java überprüfte Ausnahmen

Ich schätze die neuen Java 8 -Funktionen zu Lambdas und Standardmethoden-Schnittstellen sehr. Trotzdem langweilen mich immer noch geprüfte Ausnahmen. Zum Beispiel, wenn ich nur alle sichtbaren Felder eines auflisten möchte Objekt Ich möchte dies einfach schreiben:

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

Da die Methode get möglicherweise eine aktivierte Ausnahme auslöst, die nicht mit dem Schnittstellenvertrag Consumer ü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 RuntimeException ausgelöst wird und das Programm die Ausnahme ohne Kompilierungsfehler behandelt oder nicht.

Daher möchte ich Ihre Meinung zu meiner kontroversen Problemumgehung für geprüfte Ausnahmen als störend empfinden. Zu diesem Zweck habe ich eine Hilfsschnittstelle erstellt ConsumerCheckException<T> und eine Utility-Funktion rethrow (, die gemäß dem Vorschlag von Dovals Kommentar) wie folgt aktualisiert wurde:

  @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]m/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 geprüften Ausnahmen umzukehren, aber wie ich erklärt habe, möchte ich mein erstes Beispiel bequemer erreichen, ohne mich mit geprüften Ausnahmen zu befassen, und dies ist der einfachere Weg, den ich gefunden habe es zu tun.

Vor- und Nachteile Ihrer Technik:

  • Wenn der aufrufende Code die aktivierte Ausnahme behandeln soll, MÜSSEN Sie ihn der throw-Klausel der Methode hinzufügen, die den Stream enthält. Der Compiler zwingt Sie nicht mehr zum Hinzufügen, 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 aktivierte Ausnahme bereits verarbeitet, erinnert Sie der Compiler daran, die Throws-Klausel zur Methodendeklaration hinzuzufügen, die den Stream enthält (wenn Sie dies nicht tun, heißt es: Die Ausnahme wird niemals im Hauptteil der entsprechenden try-Anweisung ausgelöst). .

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

  • Wenn Sie eine Methode aufrufen, die buchstäblich nie die von ihr deklarierte Ausnahme auslösen kann, sollten Sie die throw-Klausel nicht einschließen. Beispiel: new String (byteArr, "UTF-8") löst UnsupportedEncodingException aus, aber UTF-8 wird durch die Java - Spezifikation garantiert, um immer vorhanden zu sein. Hier ist die Throws-Deklaration ein Ärgernis und Jede Lösung, um es mit minimalem Boilerplate zum Schweigen zu bringen, ist willkommen.

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

  • Wenn Sie eine strikte Schnittstelle implementieren, in der Sie nicht die Möglichkeit haben, eine Throws-Deklaration hinzuzufügen, und dennoch das Auslösen einer Ausnahme völlig angemessen ist, führt das Umschließen einer Ausnahme, um das Privileg zu erhalten, sie auszulösen, zu einer Stapelverfolgung mit falschen Ausnahmen Geben Sie keine Informationen darüber ein, was tatsächlich schief gelaufen ist. Ein gutes Beispiel ist Runnable.run (), das keine überprüften Ausnahmen auslöst. In diesem Fall können Sie entscheiden, die aktivierte Ausnahme nicht zur throw-Klausel der Methode hinzuzufügen, die den Stream enthält.

  • Wenn Sie die aktivierte Ausnahme NICHT zur Throws-Klausel der Methode hinzufügen (oder vergessen, sie hinzuzufügen), die den Stream enthält, sollten Sie sich in jedem Fall dieser beiden Konsequenzen bewusst sein, wenn Sie CHECKED-Ausnahmen auslösen:

    1. Der aufrufende Code kann ihn nicht nach Namen abfangen (wenn Sie es versuchen, sagt der Compiler: Ausnahme wird niemals in den Text 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. Es verstößt gegen das Prinzip der geringsten Überraschung: Es reicht nicht mehr aus, RuntimeException abzufangen, um das Abfangen aller möglichen Ausnahmen zu gewährleisten. Aus diesem Grund glaube ich, dass dies nicht im Framework-Code erfolgen sollte, sondern nur im Business-Code, den Sie vollständig kontrollieren.

Verweise:

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

16
MarcG

Kann es in diesem Beispiel jemals wirklich scheitern? Denken Sie nicht, aber vielleicht ist Ihr Fall etwas Besonderes. Wenn es wirklich "nicht scheitern kann" und es nur eine nervige Compilersache ist, möchte ich die Ausnahme einschließen und ein Error mit dem Kommentar "kann nicht passieren" auslösen. Macht die Dinge sehr klar für die Wartung. Andernfalls werden sie sich fragen, "wie kann das passieren" und "wer zum Teufel kümmert sich darum?"

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

6
user949300

eine andere Version dieses Codes, bei der die aktivierte Ausnahme nur verzögert wird:

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, wenn Sie anrufen:

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

dann ist Ihr Code verpflichtet, die Ausnahme abzufangen

1
java bear

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

Wenn Sie in Java bleiben müssen ...

Paguro verfügt über funktionale Schnittstellen, die geprüfte Ausnahmen umschließen. Sie müssen also nicht mehr darüber nachdenken. Es enthält unveränderliche Sammlungen und funktionale Transformationen nach dem Vorbild von Clojure-Wandlern (oder Java 8 Streams)), die die funktionalen Schnittstellen verwenden und geprüfte Ausnahmen umschließen.

Es gibt auch VAVr und The Eclipse Collections zum Ausprobieren.

Verwenden Sie andernfalls Kotlin

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

0
GlenPeterson