405 Stimmen

Was ist falsch an überschreibbaren Methodenaufrufen in Konstruktoren?

Ich habe eine Wicket-Seitenklasse, die den Seitentitel in Abhängigkeit vom Ergebnis einer abstrakten Methode festlegt.

public abstract class BasicPage extends WebPage {

    public BasicPage() {
        add(new Label("title", getTitle()));
    }

    protected abstract String getTitle();

}

NetBeans warnt mich mit der Meldung "Overridable method call in constructor", aber was sollte daran falsch sein? Die einzige Alternative, die ich mir vorstellen kann, ist, die Ergebnisse von ansonsten abstrakten Methoden in Unterklassen an den Superkonstruktor zu übergeben. Aber das könnte bei vielen Parametern schwer zu lesen sein.

513voto

polygenelubricants Punkte 362173

Beim Aufrufen einer überschreibbaren Methode von Konstruktoren aus

Einfach ausgedrückt ist dies falsch, weil es unnötigerweise die Möglichkeit eröffnet VIELE Ungeziefer. Wenn die @Override aufgerufen wird, kann der Zustand des Objekts inkonsistent und/oder unvollständig sein.

Ein Zitat aus Effective Java 2nd Edition, Punkt 17: Entwerfen und dokumentieren Sie für Vererbung, oder verbieten Sie sie :

Es gibt noch ein paar weitere Einschränkungen, die eine Klasse erfüllen muss, um Vererbung zu ermöglichen. Konstruktoren dürfen keine überschreibbaren Methoden aufrufen direkt oder indirekt. Wenn Sie gegen diese Regel verstoßen, wird das Programm scheitern. Der Konstruktor der Oberklasse wird vor dem Konstruktor der Unterklasse ausgeführt, so dass die überschreibende Methode in der Unterklasse aufgerufen wird, bevor der Konstruktor der Unterklasse ausgeführt wurde. Wenn die Überschreibungsmethode von einer Initialisierung abhängt, die vom Unterklassenkonstruktor durchgeführt wird, verhält sich die Methode nicht wie erwartet.

Hier ein Beispiel zur Veranschaulichung:

public class ConstructorCallsOverride {
    public static void main(String[] args) {

        abstract class Base {
            Base() {
                overrideMe();
            }
            abstract void overrideMe(); 
        }

        class Child extends Base {

            final int x;

            Child(int x) {
                this.x = x;
            }

            @Override
            void overrideMe() {
                System.out.println(x);
            }
        }
        new Child(42); // prints "0"
    }
}

Hier, wenn Base Konstruktoraufrufe overrideMe , Child noch nicht die Initialisierung der final int x und die Methode erhält den falschen Wert. Dies wird mit ziemlicher Sicherheit zu Bugs und Fehlern führen.

Verwandte Fragen

Siehe auch


Bei Objektkonstruktion mit vielen Parametern

Konstruktoren mit vielen Parametern können zu einer schlechten Lesbarkeit führen, und es gibt bessere Alternativen.

Hier ein Zitat aus Effective Java 2nd Edition, Punkt 2: Ziehen Sie ein Builder-Muster in Betracht, wenn Sie viele Konstruktorparameter haben. :

Traditionell haben die Programmierer die Teleskopkonstrukteur Muster, bei dem Sie einen Konstruktor mit nur den erforderlichen Parametern, einen weiteren mit einem einzigen optionalen Parameter, einen dritten mit zwei optionalen Parametern und so weiter bereitstellen...

Das Muster des Teleskopkonstruktors sieht im Wesentlichen wie folgt aus:

public class Telescope {
    final String name;
    final int levels;
    final boolean isAdjustable;

    public Telescope(String name) {
        this(name, 5);
    }
    public Telescope(String name, int levels) {
        this(name, levels, false);
    }
    public Telescope(String name, int levels, boolean isAdjustable) {       
        this.name = name;
        this.levels = levels;
        this.isAdjustable = isAdjustable;
    }
}

Und jetzt können Sie eine der folgenden Aktionen durchführen:

new Telescope("X/1999");
new Telescope("X/1999", 13);
new Telescope("X/1999", 13, true);

Sie können jedoch derzeit nicht nur die name y isAdjustable und verlassen levels in der Standardeinstellung. Sie können weitere Konstruktorüberladungen bereitstellen, aber natürlich würde die Anzahl mit der Anzahl der Parameter explodieren, und Sie könnten sogar mehrere boolean y int Argumente, die die Dinge wirklich durcheinander bringen würden.

Wie Sie sehen können, ist dieses Muster nicht angenehm zu schreiben und noch weniger angenehm zu benutzen (Was bedeutet "true" hier? Was ist 13?).

Bloch empfiehlt die Verwendung eines Bauherrenmusters, mit dem Sie stattdessen etwas wie dieses schreiben könnten:

Telescope telly = new Telescope.Builder("X/1999").setAdjustable(true).build();

Beachten Sie, dass die Parameter jetzt benannt sind und Sie sie in beliebiger Reihenfolge einstellen und diejenigen überspringen können, die Sie auf den Standardwerten belassen wollen. Dies ist sicherlich viel besser als das Teleskopieren von Konstruktoren, vor allem, wenn es eine große Anzahl von Parametern gibt, die zu vielen der gleichen Typen gehören.

Siehe auch

Verwandte Fragen

63voto

Nordic Mainframe Punkte 27006

Hier ist ein Beispiel, das zum Verständnis beiträgt:

public class Main {
    static abstract class A {
        abstract void foo();
        A() {
            System.out.println("Constructing A");
            foo();
        }
    }

    static class C extends A {
        C() { 
            System.out.println("Constructing C");
        }
        void foo() { 
            System.out.println("Using C"); 
        }
    }

    public static void main(String[] args) {
        C c = new C(); 
    }
}

Wenn Sie diesen Code ausführen, erhalten Sie die folgende Ausgabe:

Constructing A
Using C
Constructing C

Siehst du? foo() macht von C Gebrauch, bevor der Konstruktor von C ausgeführt wurde. Wenn foo() erfordert, dass C einen definierten Zustand hat (d. h. der Konstruktor ist beendet ), dann kommt es zu einem undefinierten Zustand in C und es kann zu Problemen kommen. Und da man in A nicht wissen kann, was der überschriebene foo() erwartet, erhalten Sie eine Warnung.

12voto

KeatsPeeks Punkte 18568

Der Aufruf einer überschreibbaren Methode im Konstruktor ermöglicht es Unterklassen, den Code zu unterwandern, so dass Sie nicht mehr garantieren können, dass er funktioniert. Deshalb erhalten Sie eine Warnung.

Was passiert in Ihrem Beispiel, wenn eine Unterklasse die getTitle() und gibt null zurück ?

Um dies zu "beheben", können Sie eine Fabrikmethode anstelle eines Konstruktors zu verwenden, ist ein gängiges Muster für die Instanziierung von Objekten.

9voto

kirilv Punkte 1345

Hier ein Beispiel, das die logische Probleme die beim Aufruf einer überschreibbaren Methode im Superkonstruktor auftreten können.

class A {

    protected int minWeeklySalary;
    protected int maxWeeklySalary;

    protected static final int MIN = 1000;
    protected static final int MAX = 2000;

    public A() {
        setSalaryRange();
    }

    protected void setSalaryRange() {
        throw new RuntimeException("not implemented");
    }

    public void pr() {
        System.out.println("minWeeklySalary: " + minWeeklySalary);
        System.out.println("maxWeeklySalary: " + maxWeeklySalary);
    }
}

class B extends A {

    private int factor = 1;

    public B(int _factor) {
        this.factor = _factor;
    }

    @Override
    protected void setSalaryRange() {
        this.minWeeklySalary = MIN * this.factor;
        this.maxWeeklySalary = MAX * this.factor;
    }
}

public static void main(String[] args) {
    B b = new B(2);
    b.pr();
}

Das Ergebnis wäre in der Tat so:

minWeeklySalary: 0

maxWeeklySalary: 0

Das liegt daran, dass der Konstruktor der Klasse B zuerst den Konstruktor der Klasse A aufruft, wo die überschreibbare Methode in B ausgeführt wird. Aber innerhalb der Methode verwenden wir die Instanzvariable Faktor die über noch nicht initialisiert worden (weil der Konstruktor von A noch nicht beendet ist), also ist der Faktor 0 und nicht 1 und schon gar nicht 2 (was der Programmierer denken könnte). Stellen Sie sich vor, wie schwierig es wäre, einen Fehler aufzuspüren, wenn die Berechnungslogik zehnmal so verworren wäre.

Ich hoffe, das hilft jemandem.

4voto

Manuel Selva Punkte 17616

Wenn Sie in Ihrem Konstruktor Methoden aufrufen, die von Unterklassen überschrieben werden, ist es weniger wahrscheinlich, dass Sie auf Variablen verweisen, die noch nicht existieren, wenn Sie Ihre Initialisierung logisch zwischen dem Konstruktor und der Methode aufteilen.

Werfen Sie einen Blick auf diesen Beispiel-Link http://www.javapractices.com/topic/TopicAction.do?Id=215

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