Frage

In Java muss die erste Zeile Ihres Konstruktors also ein Aufruf von super sein ...sei es der implizite Aufruf von super() oder der explizite Aufruf eines anderen Konstruktors.Ich möchte wissen, warum ich das nicht mit einem Try-Block versehen kann.

Mein konkreter Fall ist, dass ich eine Scheinklasse für einen Test habe.Es gibt keinen Standardkonstruktor, aber ich möchte einen, um die Tests einfacher lesbar zu machen.Ich möchte auch die vom Konstruktor ausgelösten Ausnahmen in eine RuntimeException einschließen.

Was ich also effektiv tun möchte, ist Folgendes:

public class MyClassMock extends MyClass {
    public MyClassMock() {
        try {
            super(0);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // Mocked methods
}

Aber Java beschwert sich, dass „super“ nicht die erste Aussage ist.

Mein Workaround:

public class MyClassMock extends MyClass {
    public static MyClassMock construct() {
        try {
            return new MyClassMock();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public MyClassMock() throws Exception {
        super(0);
    }

    // Mocked methods
}

Ist das die beste Problemumgehung?Warum lässt mich Java Ersteres nicht zu?


Meine beste Vermutung zum „Warum“ ist, dass Java nicht zulassen möchte, dass sich ein konstruiertes Objekt in einem potenziell inkonsistenten Zustand befindet ...Wenn ich jedoch einen Spott mache, ist mir das egal.Es scheint, dass ich in der Lage sein sollte, das oben Gesagte zu tun ...oder zumindest weiß ich, dass das oben Genannte für meinen Fall sicher ist ...oder es scheint, als ob es sowieso so sein sollte.

Ich überschreibe alle Methoden, die ich aus der getesteten Klasse verwende, sodass kein Risiko besteht, dass ich nicht initialisierte Variablen verwende.

War es hilfreich?

Lösung

Leider können Compiler nicht nach theoretischen Prinzipien arbeiten, und auch wenn Sie vielleicht wissen, dass es in Ihrem Fall sicher ist, müsste es in allen Fällen sicher sein, wenn sie es erlauben würden.

Mit anderen Worten: Der Compiler stoppt nicht nur Sie, sondern alle, auch alle, die nicht wissen, dass er unsicher ist und eine besondere Behandlung erfordert.Dafür gibt es wahrscheinlich auch andere Gründe, da alle Sprachen normalerweise Möglichkeiten haben, dies zu tun unsicher Dinge, wenn man weiß, wie man damit umgeht.

In C# .NET gibt es ähnliche Bestimmungen, und die einzige Möglichkeit, einen Konstruktor zu deklarieren, der einen Basiskonstruktor aufruft, ist folgende:

public ClassName(...) : base(...)

Dabei wird der Basiskonstruktor vor dem Hauptteil des Konstruktors aufgerufen und Sie können diese Reihenfolge nicht ändern.

Andere Tipps

Dies geschieht, um zu verhindern, dass jemand etwas Neues erstellt SecurityManager Objekt aus nicht vertrauenswürdigem Code.

public class Evil : SecurityManager {
  Evil()
  {
      try {
         super();
      } catch { Throwable t }
      {
      }
   }
}

Ich weiß, dass dies eine alte Frage ist, aber sie gefiel mir, und deshalb habe ich beschlossen, ihr eine eigene Antwort zu geben.Vielleicht trägt mein Verständnis, warum dies nicht möglich ist, zur Diskussion und für zukünftige Leser Ihrer interessanten Frage bei.

Lassen Sie mich mit einem Beispiel einer fehlgeschlagenen Objektkonstruktion beginnen.

Definieren wir eine Klasse A, so dass:

class A {
   private String a = "A";

   public A() throws Exception {
        throw new Exception();
   }
}

Nehmen wir nun an, wir möchten ein Objekt vom Typ A in a erstellen try...catch Block.

A a = null;
try{
  a = new A();
}catch(Exception e) {
  //...
}
System.out.println(a);

Offensichtlich wird die Ausgabe dieses Codes wie folgt aussehen: null.

Warum Java keine teilweise konstruierte Version von zurückgibt A?Schließlich ist das Objekt zu dem Zeitpunkt, an dem der Konstruktor ausfällt, bereits ausgefallen name Das Feld wurde bereits initialisiert, oder?

Nun, Java kann keine teilweise erstellte Version von zurückgeben A weil das Objekt nicht erfolgreich erstellt wurde.Das Objekt befindet sich in einem inkonsistenten Zustand und wird daher von Java verworfen.Ihre Variable A wird nicht einmal initialisiert, sondern auf Null gehalten.

Um nun ein neues Objekt vollständig zu erstellen, müssen, wie Sie wissen, zunächst alle seine Superklassen initialisiert werden.Wie wäre der Endzustand des Objekts, wenn die Ausführung einer der Superklassen fehlschlagen würde?Das lässt sich nicht feststellen.

Schauen Sie sich dieses ausführlichere Beispiel an

class A {
   private final int a;
   public A() throws Exception { 
      a = 10;
   }
}

class B extends A {
   private final int b;
   public B() throws Exception {
       methodThatThrowsException(); 
       b = 20;
   }
}

class C extends B {
   public C() throws Exception { super(); }
}

Wenn der Konstrukteur von C wird aufgerufen, wenn beim Initialisieren eine Ausnahme auftritt B, was wäre der Wert des Finales int Variable b?

Daher kann das Objekt C nicht erstellt werden, es ist gefälscht, es ist Müll und es ist nicht vollständig initialisiert.

Für mich erklärt dies, warum Ihr Code illegal ist.

Ich kann nicht davon ausgehen, dass ich ein tiefes Verständnis der Java-Interna habe, aber ich verstehe, dass ein Compiler, wenn er eine abgeleitete Klasse instanziieren muss, zuerst die Basis erstellen muss (und davor seine Basis (...)). und fügen Sie dann die in der Unterklasse vorgenommenen Erweiterungen hinzu.

Es besteht also nicht einmal die Gefahr von nicht initialisierten Variablen oder ähnlichem.Wenn Sie versuchen, etwas im Konstruktor der Unterklasse zu tun Vor die Basisklasse' Konstrukteur, fordern Sie den Compiler im Grunde auf, eine Basisobjektinstanz zu erweitern, die noch nicht existiert.

Bearbeiten: In Ihrem Fall, Meine Klasse wird zum Basisobjekt und MyClassMock ist eine Unterklasse.

Ich weiß nicht, wie Java intern implementiert ist, aber wenn der Konstruktor der Superklasse eine Ausnahme auslöst, gibt es keine Instanz der Klasse, die Sie erweitern.Es wäre unmöglich, das anzurufen toString() oder equals() Methoden zum Beispiel, da sie in den meisten Fällen vererbt werden.

Java erlaubt möglicherweise ein Try/Catch um den super()-Aufruf im Konstruktor herum, wenn 1.Sie überschreiben ALLE Methoden der Superklassen und 2.Sie verwenden nicht die super.XXX()-Klausel, aber das klingt für mich alles zu kompliziert.

Ich weiß, dass es auf diese Frage zahlreiche Antworten gibt, aber ich möchte einen kleinen Einblick geben, warum dies nicht zulässig ist, insbesondere um zu beantworten, warum Java Ihnen dies nicht erlaubt.Also los geht's...

Denken Sie daran super() muss vor allem anderen im Konstruktor einer Unterklasse aufgerufen werden, wenn Sie also verwendet haben try Und catch Blöcke um dich herum super() Aufruf müssten die Blöcke so aussehen:

try {
   super();
   ...
} catch (Exception e) {
   super(); //This line will throw the same error...
   ...
}

Wenn super()fails in theversuchenblock, it HAS to be executed first in thefangenblock, so thatsuperruns before anything in your subclasss Konstruktor.Damit stehen Sie vor dem gleichen Problem wie am Anfang:Wenn eine Ausnahme ausgelöst wird, wird sie nicht abgefangen.(In diesem Fall wird es einfach noch einmal in den Catch-Block geworfen.)

Nun ist der obige Code auch in Java in keiner Weise zulässig.Dieser Code führt möglicherweise die Hälfte des ersten Superaufrufs aus und ruft ihn dann erneut auf, was bei einigen Superklassen zu Problemen führen kann.

Nun der Grund, warum Sie in Java keine Ausnahme auslösen können stattdessen des Anrufens super() liegt daran, dass die Ausnahme woanders abgefangen werden könnte und das Programm fortgesetzt würde ohne anzurufen super() auf Ihrem Unterklassenobjekt, und möglicherweise, weil die Ausnahme Ihr Objekt als Parameter verwenden und versuchen könnte, den Wert geerbter Instanzvariablen zu ändern, die noch nicht initialisiert worden wären.

Eine Möglichkeit, dies zu umgehen, besteht darin, eine private statische Funktion aufzurufen.Der Try-Catch kann dann im Funktionskörper platziert werden.

public class Test  {
  public Test()  {
     this(Test.getObjectThatMightThrowException());
  }
  public Test(Object o)  {
     //...
  }
  private static final Object getObjectThatMightThrowException()  {
     try  {
        return  new ObjectThatMightThrowAnException();
     }  catch(RuntimeException rtx)  {
        throw  new RuntimeException("It threw an exception!!!", rtx);
     }
  }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top