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:
- Gibt es einen guten Grund für diese Einschränkung?
- 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:
- 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.
- 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;
}