Co jest złego w nadpisywalnych wywołaniach metod w konstruktorach?

Mam klasę wicket page, która ustawia tytuł strony w zależności od wyniku metody abstrakcyjnej.

public abstract class BasicPage extends WebPage {

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

    protected abstract String getTitle();

}

NetBeans ostrzega mnie Komunikatem "Overridable method call in constructor", ale co w tym złego? Jedyną alternatywą, jaką mogę sobie wyobrazić, jest przekazanie wyników abstrakcyjnych metod super konstruktorowi w podklasach. Ale to może być trudne do odczytania z wieloma parametrami.

Author: Wrench, 2010-08-04

7 answers

Na wywołanie metody overridable z konstruktorów

Mówiąc najprościej, jest to złe, ponieważ niepotrzebnie otwiera możliwości dla wielu {18]} błędów. Po wywołaniu @Override Stan obiektu może być niespójny i / lub niekompletny.

Cytat z Effective Java 2nd Edition, poz. 17: projekt i dokument do dziedziczenia, albo go zakazać :

Jest jeszcze kilka ograniczeń, których klasa musi przestrzegać, aby umożliwić dziedziczenie. konstruktory nie mogą wywoływać metod nadpisywalnych, bezpośrednio lub pośrednio. Jeśli naruszysz tę zasadę, spowoduje to awarię programu. Konstruktor klasy nadrzędnej działa przed konstruktorem klasy podrzędnej, więc metoda nadrzędna w podklasie zostanie wywołana przed uruchomieniem konstruktora klasy podrzędnej. Jeśli metoda nadrzędna zależy od jakiejkolwiek inicjalizacji wykonanej przez konstruktor podklasy, metoda nie będzie zachowywać się zgodnie z oczekiwaniami.

Oto przykład do ilustracja:

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"
    }
}

Tutaj, gdy Base konstruktor wywoła overrideMe, Child nie zakończyła inicjalizacji final int x, a metoda pobiera błędną wartość. To prawie na pewno doprowadzi do błędów i błędów.

Podobne pytania

Zobacz też


O budowie obiektów o wielu parametrach

Konstruktory o wielu parametrach mogą prowadzić do słabej czytelności i istnieją lepsze alternatywy.

Oto cytat z Effective Java 2nd Edition, pkt 2: rozważ wzorzec Buildera w obliczu wielu konstruktorów parametry:

Tradycyjnie Programiści używali wzoru konstruktora teleskopowego , w którym podajesz konstruktorowi tylko wymagane parametry, inny z pojedynczymi opcjonalnymi parametrami, trzeci z dwoma opcjonalnymi parametrami i tak dalej...

Wzorzec konstruktora teleskopowego jest zasadniczo podobny do tego:

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;
    }
}

A teraz możesz wykonać jedną z następujących czynności:

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

Nie można jednak obecnie ustawić tylko name i isAdjustable, i pozostawiając levels domyślnie. Możesz podać więcej przeciążeń konstruktora, ale oczywiście liczba eksplodowałaby wraz ze wzrostem liczby parametrów i możesz mieć nawet wiele argumentów boolean i int, co naprawdę zrobiłoby bałagan.

Jak widzisz, nie jest to przyjemny wzorzec do pisania, a jeszcze mniej przyjemny w użyciu (co tu oznacza "prawda"? Co to jest 13?).

Bloch zaleca użycie wzoru Buildera, który pozwoli Ci aby napisać coś takiego zamiast:

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

Zauważ, że teraz parametry są nazwane i możesz ustawić je w dowolnej kolejności, i możesz pominąć te, które chcesz zachować przy wartościach domyślnych. Jest to z pewnością znacznie lepsze niż konstrukcje teleskopowe, zwłaszcza gdy istnieje ogromna liczba parametrów, które należą do wielu tych samych typów.

Zobacz też

Podobne pytania

 480
Author: polygenelubricants,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-26 11:26:01

Oto przykład, który pomaga to zrozumieć:

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(); 
    }
}

Jeśli uruchomisz ten kod, otrzymasz następujące wyjście:

Constructing A
Using C
Constructing C
Widzisz? foo() używa c przed uruchomieniem konstruktora C. Jeśli foo() wymaga, aby C miał zdefiniowany stan (tzn. konstruktor zakończył), wtedy w C napotkamy niezdefiniowany stan i może się to zepsuć. A ponieważ nie możesz wiedzieć, czego oczekuje nadpisany foo(), dostajesz Ostrzeżenie.
 59
Author: Nordic Mainframe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-13 04:42:23

Wywołanie metody nadpisywalnej w konstruktorze pozwala podklasom obalić kod, więc nie możesz zagwarantować, że działa. Dlatego dostajesz Ostrzeżenie.

W twoim przykładzie, co się stanie, jeśli podklasa nadpisuje {[0] } i zwróci null ?

Aby to "naprawić", możesz użyć metody fabrycznej zamiast konstruktora, jest to powszechny wzorzec instancjacji obiektów.

 11
Author: KeatsPeeks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-04 10:29:41

Oto przykład, który ujawnia problemy logiczne , które mogą wystąpić podczas wywoływania nadpisywalnej metody w super konstruktorze.

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();
}

W rzeczywistości wynik będzie:

Minweeklysalary: 0

MaxWeeklySalary: 0

Jest to spowodowane tym, że konstruktor klasy B najpierw wywołuje konstruktor klasy A, gdzie metoda nadpisywalna wewnątrz B jest wykonywana. Ale wewnątrz metody używamy zmiennej instancji factor, która nie ma jeszcze została zainicjalizowana (ponieważ konstruktor a nie został jeszcze ukończony), więc współczynnik wynosi 0, a nie 1, a na pewno nie 2 (coś, co programista może myśleć, że będzie). Wyobraź sobie, jak trudno byłoby wyśledzić błąd, gdyby logika obliczeń była dziesięć razy bardziej pokręcona.

Mam nadzieję, że to komuś pomoże.
 8
Author: kirilv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-21 19:12:35

Jeśli wywołujesz metody w konstruktorze, które zastępują podklasy, oznacza to, że mniej prawdopodobne jest odwoływanie się do zmiennych, które jeszcze nie istnieją, jeśli logicznie podzielisz inicjalizację między konstruktor i metodę.

Spójrz na ten przykładowy link http://www.javapractices.com/topic/TopicAction.do?Id=215

 4
Author: Manuel Selva,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-04 09:44:41

W konkretnym przypadku Wicket: właśnie dlatego zapytałem Wicket Programiści dodający wsparcie dla jawnego dwufazowego procesu inicjalizacji komponentu w cyklu życia frameworka konstruowania komponentu, tj.

  1. Budownictwo-przez konstruktora
  2. Inicjalizacja-poprzez onInitilize (po budowie, gdy działają metody wirtualne!)

Była dość aktywna dyskusja na temat tego, czy jest to konieczne, czy nie (w pełni jest to konieczne IMHO) , ponieważ ten link demonstruje http://apache-wicket.1842946.n4.nabble.com/VOTE-WICKET-3218-Component-onInitialize-is-broken-for-Pages-td3341090i20.html)

Dobrą wiadomością jest to, że doskonali deweloperzy w Wicket nie skończyło się wprowadzenie dwufazowej inicjalizacji (aby najbardziej aweseome Java UI framework jeszcze bardziej niesamowite!) więc z Wicket możesz zrobić całą inicjalizację budowy post w metodzie onInitialize, która jest wywoływana przez framework automatycznie, jeśli ją nadpiszesz - w tym momencie w cyklu życia komponentu jego konstruktor zakończył pracę, więc metody wirtualne działają zgodnie z oczekiwaniami.

 2
Author: Volksman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-05 22:42:12

Myślę, że dla Wicket lepiej jest wywołać metodę add w onInitialize() (patrz components lifecycle) :

public abstract class BasicPage extends WebPage {

    public BasicPage() {
    }

    @Override
    public void onInitialize() {
        add(new Label("title", getTitle()));
    }

    protected abstract String getTitle();
}
 0
Author: sanluck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-09 11:18:49