Что не так с переопределенными вызовами методов в конструкторах?

У меня есть класс страницы Wicket, который устанавливает заголовок страницы в зависимости от результата абстрактного метода.

public abstract class BasicPage extends WebPage {

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

    protected abstract String getTitle();

}

NetBeans предупреждает меня с сообщением «Перезаписываемый вызов метода в конструкторе», но что должно быть не так с ним? Единственная альтернатива, которую я могу себе представить, - это передать результаты абстрактных методов в супер-конструктор в подклассах. Но это может быть трудно читать со многими параметры.

366
задан Wrench 21 March 2017 в 08:45
поделиться

4 ответа

При вызове переопределяемого метода из конструкторов

Проще говоря, это неверно, потому что это излишне открывает возможности для МНОЖЕСТВО ошибок. Когда вызывается @Override , состояние объекта может быть несовместимым и / или неполным.

Цитата из Эффективное 2-е издание Java, пункт 17: Дизайн и документация для наследования или запрета его :

Есть еще несколько ограничений, которым должен подчиняться класс, чтобы разрешить наследование. Конструкторы не должны вызывать заменяемые методы прямо или косвенно. Если вы нарушите это правило, произойдет сбой программы.Конструктор суперкласса запускается перед конструктором подкласса, поэтому метод переопределения в подклассе будет вызываться до запуска конструктора подкласса. Если метод переопределения зависит от любой инициализации, выполняемой конструктором подкласса, метод не будет вести себя должным образом.

Вот пример для иллюстрации:

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

Здесь, когда конструктор Base вызывает overrideMe , Child не завершил инициализацию final int x , и метод получает неправильное значение. Это почти наверняка приведет к ошибкам и ошибкам.

Связанные вопросы

См. Также


При построении объекта с большим количеством параметров

Конструкторы с большим количеством параметров могут привести к плохой читаемости, и существуют лучшие альтернативы.

Вот цитата из Effective Java 2nd Edition, пункт 2: Рассмотрите шаблон построителя, когда сталкиваетесь со многими параметрами конструктора :

Традиционно программисты использовали шаблон телескопический конструктор , в котором вы предоставляете конструктор только с обязательными параметрами, другой - с одним необязательным параметром, третий - с двумя необязательными параметрами и так далее ...

Шаблон телескопического конструктора, по сути, выглядит примерно так:

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

И теперь вы можете выполнить любое из следующих действий:

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

Однако в настоящее время вы не можете установить только имя и ] isAdjustable и оставив уровней по умолчанию. Вы можете предоставить больше перегрузок конструктора, но, очевидно, число будет резко увеличиваться по мере увеличения количества параметров, и у вас может быть даже несколько аргументов boolean и int , что действительно приведет к беспорядку вещей.

Как видите, писать этот шаблон не очень приятно и еще менее приятно использовать (что здесь означает «истина»? Что такое 13?).

Блох рекомендует использовать шаблон компоновщика, который позволил бы вам вместо этого написать что-то вроде этого:

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

Обратите внимание, что теперь параметры имеют имена, и вы можете устанавливать их в любом порядке, и вы можете пропустить те, которые вы хотите оставить значения по умолчанию. Это, безусловно, намного лучше, чем телескопические конструкторы, особенно когда существует огромное количество параметров, принадлежащих ко многим из одних и тех же типов.

См. Также

Соответствующий questions

461
ответ дан 23 November 2019 в 00:09
поделиться

Вот пример, который помогает понять это:

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

Если вы запустите этот код, вы получите следующий результат:

Constructing A
Using C
Constructing C

Понимаете? foo () использует C до запуска конструктора C. Если foo () требует, чтобы C имел определенное состояние (т.е. конструктор завершил ), то он обнаружит неопределенное состояние в C, и что-то может сломаться. А поскольку вы не можете знать в A, что ожидает перезаписанный foo () , вы получите предупреждение.

56
ответ дан 23 November 2019 в 00:09
поделиться

Вызов переопределяемого метода в конструкторе позволяет подклассам изменять код, поэтому вы больше не можете гарантировать, что он работает. Вот почему вы получаете предупреждение.

В вашем примере, что произойдет, если подкласс переопределит getTitle() и вернет null?

Чтобы "исправить" это, вы можете использовать фабричный метод вместо конструктора, это обычный паттерн инстанцирования объектов.

11
ответ дан 23 November 2019 в 00:09
поделиться

Если вы вызываете в конструкторе методы, которые переопределяют подклассы, это означает, что вы с меньшей вероятностью будете ссылаться на переменные, которые еще не существуют, если вы логически разделите свою инициализацию между конструктором и методом.

Взгляните на этот образец ссылки http://www.javapractices.com/topic/TopicAction.do?Id=215

4
ответ дан 23 November 2019 в 00:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: