コンストラクタ内のオーバーライド可能なメソッド呼び出しの何が問題なのでしょうか? 質問する

コンストラクタ内のオーバーライド可能なメソッド呼び出しの何が問題なのでしょうか? 質問する

抽象メソッドの結果に応じてページ タイトルを設定する Wicket ページ クラスがあります。

public abstract class BasicPage extends WebPage {

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

    protected abstract String getTitle();

}

NetBeans は「コンストラクタ内のオーバーライド可能なメソッド呼び出し」というメッセージで警告しますが、何が問題なのでしょうか? 考えられる唯一の代替案は、抽象メソッドの結果をサブクラスのスーパー コンストラクタに渡すことです。ただし、パラメータが多いと読みにくくなる可能性があります。

ベストアンサー1

コンストラクタからオーバーライド可能なメソッドを呼び出す場合

簡単に言えば、これは多くのバグの可能性を不必要に開くため間違っています。@Overrideが呼び出されると、オブジェクトの状態が矛盾したり不完全になったりする可能性があります。

Effective Java 2nd Edition の項目 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コンストラクタが を呼び出すとoverrideMeChildの初期化が完了しておらずfinal int x、メソッドは間違った値を取得します。これにより、ほぼ確実にバグやエラーが発生します。

関連する質問

参照


多くのパラメータを持つオブジェクトの構築について

多くのパラメータを持つコンストラクターは読みにくくなる可能性があるため、より優れた代替手段が存在します。

以下は、Effective Java 2nd Edition の項目 2 からの引用です。コンストラクタ パラメータが多数ある場合は、ビルダー パターンを検討してください

従来、プログラマーはテレスコープ コンストラクターパターンを使用してきました。このパターンでは、必須パラメーターのみを持つコンストラクター、1 つのオプション パラメーターを持つ別のコンストラクター、2 つのオプション パラメーターを持つ 3 番目のコンストラクター、というようにコンストラクターを指定します。

テレスコープ コンストラクター パターンは、基本的に次のようになります。

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

nameただし、現在はとのみを設定してisAdjustable、 をデフォルトのままにすることはできませんlevels。より多くのコンストラクター オーバーロードを提供することはできますが、パラメーターの数が増えるにつれてその数は爆発的に増加し、複数のbooleanおよびint引数を持つ可能性もあり、状況が本当に混乱することになります。

ご覧のとおり、これは書きやすいパターンではなく、使用するのもあまり気持ちのよいものではありません (ここでの「true」とはどういう意味ですか? 13 とは何ですか?)。

Bloch はビルダー パターンの使用を推奨しており、代わりに次のようなコードを記述できるようになります。

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

パラメータに名前が付けられ、任意の順序で設定でき、デフォルト値のままにしておきたいパラメータはスキップできることに注意してください。これは、特に多数の同じ型に属するパラメータが多数ある場合には、テレスコープ コンストラクターよりもはるかに優れています。

参照

関連する質問

おすすめ記事