40 Stimmen

Warum muss in einem Java-Konstruktor zuerst die Delegation an einen anderen Konstruktor erfolgen?

Wenn Sie in einem Konstruktor in Java einen anderen Konstruktor (oder einen Superkonstruktor) aufrufen wollen, muss dies in der ersten Zeile des Konstruktors stehen. Ich nehme an, das liegt daran, dass man keine Instanzvariablen ändern darf, bevor der andere Konstruktor läuft. Aber warum kann man keine Anweisungen vor der Delegation des Konstruktors haben, um den komplexen Wert für die andere Funktion zu berechnen? Mir fällt kein guter Grund ein, und ich bin schon auf einige reale Fälle gestoßen, in denen ich hässlichen Code geschrieben habe, um diese Einschränkung zu umgehen.

Ich frage mich nur:

  1. Gibt es einen guten Grund für diese Einschränkung?
  2. Gibt es Pläne, dies in zukünftigen Java-Versionen zu ermöglichen? (Oder hat Sun definitiv gesagt, dass dies nicht passieren wird?)

Ein Beispiel dafür, wovon ich spreche, ist ein von mir geschriebener Code, den ich in diese StackOverflow-Antwort . In diesem Code habe ich eine BigFraction-Klasse, die einen BigInteger-Zähler und einen BigInteger-Nenner hat. Der "kanonische" Konstruktor ist der BigFraction(BigInteger numerator, BigInteger denominator) Form. Für alle anderen Konstruktoren konvertiere ich einfach die Eingabeparameter in BigInteger und rufe den "kanonischen" Konstruktor auf, weil ich die ganze Arbeit nicht duplizieren möchte.

In einigen Fällen ist dies einfach; zum Beispiel nimmt der Konstruktor, der zwei long s ist trivial:

  public BigFraction(long numerator, long denominator)
  {
    this(BigInteger.valueOf(numerator), BigInteger.valueOf(denominator));
  }

Aber in anderen Fällen ist es schwieriger. Betrachten wir den Konstruktor, der eine BigDecimal:

  public BigFraction(BigDecimal d)
  {
    this(d.scale() < 0 ? d.unscaledValue().multiply(BigInteger.TEN.pow(-d.scale())) : d.unscaledValue(),
         d.scale() < 0 ? BigInteger.ONE                                             : BigInteger.TEN.pow(d.scale()));
  }

Ich finde das ziemlich hässlich, aber es hilft mir, doppelten Code zu vermeiden. Das Folgende würde ich gerne tun, aber es ist in Java illegal:

  public BigFraction(BigDecimal d)
  {
    BigInteger numerator = null;
    BigInteger denominator = null;
    if(d.scale() < 0)
    {
      numerator = d.unscaledValue().multiply(BigInteger.TEN.pow(-d.scale()));
      denominator = BigInteger.ONE;
    }
    else
    {
      numerator = d.unscaledValue();
      denominator = BigInteger.TEN.pow(d.scale());
    }
    this(numerator, denominator);
  }

Update

Es gab gute Antworten, aber bis jetzt wurde noch keine Antwort gegeben, mit der ich vollkommen zufrieden bin, aber es ist mir nicht wichtig genug, um ein Kopfgeld auszusetzen, also beantworte ich meine eigene Frage (hauptsächlich, um die lästige Nachricht "Haben Sie daran gedacht, eine akzeptierte Antwort zu markieren" loszuwerden).

Folgende Abhilfemaßnahmen wurden vorgeschlagen:

  1. Statische Fabrik.
    • Ich habe die Klasse an vielen Stellen verwendet, so dass der Code kaputt gehen würde, wenn ich plötzlich die öffentlichen Konstruktoren loswerden und mit valueOf()-Funktionen arbeiten würde.
    • Es wirkt wie eine Umgehung einer Einschränkung. Ich würde keine weiteren Vorteile einer Fabrik erhalten, weil diese nicht unterklassifiziert werden kann und weil gemeinsame Werte nicht zwischengespeichert/verwendet werden.
  2. Private statische "Konstruktor-Helfer"-Methoden.
    • Dies führt zu einer starken Aufblähung des Codes.
    • Der Code wird hässlich, weil ich in einigen Fällen wirklich Zähler und Nenner gleichzeitig berechnen muss, und ich kann nicht mehrere Werte zurückgeben, es sei denn, ich gebe eine BigInteger[] oder eine Art private innere Klasse.

Das Hauptargument gegen diese Funktionalität ist, dass der Compiler vor dem Aufruf des Superkonstruktors prüfen müsste, dass Sie keine Instanzvariablen oder -methoden verwendet haben, da sich das Objekt in einem ungültigen Zustand befinden würde. Dem stimme ich zu, aber ich denke, dies wäre eine einfachere Prüfung als die, die sicherstellt, dass alle finalen Instanzvariablen immer in jedem Konstruktor initialisiert werden, egal welchen Weg man durch den Code nimmt. Das andere Argument ist, dass man einfach keinen Code vorher ausführen kann, aber das ist eindeutig falsch, weil der Code zum Berechnen der Parameter für den Superkonstruktor ausgeführt wird irgendwo also muss es auf Bytecode-Ebene erlaubt sein.

Was ich gerne sehen würde, sind einige gut Grund, warum der Compiler mich diesen Code nicht nehmen lassen konnte:

public MyClass(String s) {
  this(Integer.parseInt(s));
}
public MyClass(int i) {
  this.i = i;
}

Und schreiben Sie es so um (der Bytecode wäre im Grunde identisch, denke ich):

public MyClass(String s) {
  int tmp = Integer.parseInt(s);
  this(tmp);
}
public MyClass(int i) {
  this.i = i;
}

Der einzige wirkliche Unterschied, den ich zwischen diesen beiden Beispielen sehe, ist, dass die " tmp "Der Gültigkeitsbereich der Variablen ermöglicht den Zugriff auf sie nach dem Aufruf von this(tmp) im zweiten Beispiel. Daher könnte eine spezielle Syntax (ähnlich wie bei static{} Blöcke für die Klasseninitialisierung) müssten eingeführt werden:

public MyClass(String s) {
  //"init{}" is a hypothetical syntax where there is no access to instance
  //variables/methods, and which must end with a call to another constructor
  //(using either "this(...)" or "super(...)")
  init {
    int tmp = Integer.parseInt(s);
    this(tmp);
  }
}
public MyClass(int i) {
  this.i = i;
}

0voto

Meine erraten ist, dass sich das Objekt in einem ungültigen Zustand befindet, bis ein Konstruktor für jede Ebene der Hierarchie aufgerufen wurde. Es ist für die JVM unsicher, irgendetwas darauf auszuführen, solange es nicht vollständig konstruiert ist.

0voto

amit Punkte 10341

Nun, das Problem ist, dass Java nicht erkennen kann, welche "Anweisungen" Sie vor dem Super-Aufruf setzen werden. Zum Beispiel könnten Sie sich auf Mitgliedsvariablen beziehen, die noch nicht initialisiert sind. Ich glaube also nicht, dass Java dies jemals unterstützen wird. Es gibt viele Möglichkeiten, dieses Problem zu umgehen, z. B. durch die Verwendung von Factory- oder Template-Methoden.

-3voto

OscarRyz Punkte 189898

Sehen Sie es so.

Nehmen wir an, dass ein Objekt aus 10 Teilen zusammengesetzt ist.

1,2,3,4,5,6,7,8,9,10

Ist das in Ordnung?

Von 1 bis 9 sind in der Superklasse, Teil #10 ist dein Zusatz.

Simple kann den 10. Teil erst hinzufügen, wenn die vorherigen 9 Teile abgeschlossen sind.

Das war's.

Wenn 1-6 von einer anderen Superklasse stammen, ist das in Ordnung, die Sache ist, dass ein einzelnes Objekt in einer bestimmten Reihenfolge erstellt wird, Das ist die Art und Weise, wie sie konzipiert wurde. .

Natürlich ist der wahre Grund weitaus komplexer, aber ich denke, damit wäre die Frage so gut wie beantwortet.

Was die Alternativen anbelangt, so wurden hier bereits zahlreiche genannt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X