[解決済み] コンストラクタでオーバーライド可能なメソッド呼び出しの何が問題なのでしょうか?
質問
抽象的なメソッドの結果に応じてページタイトルを設定するWicketページクラスがあります。
public abstract class BasicPage extends WebPage {
public BasicPage() {
add(new Label("title", getTitle()));
}
protected abstract String getTitle();
}
NetBeansは、"Overridable method call in constructor"というメッセージで警告してきますが、何が問題なのでしょうか?私が想像できる唯一の代替案は、サブクラスでスーパーコンストラクタに他の抽象的なメソッドの結果を渡すことです。しかし、これではパラメータが多くて読みにくいかもしれません。
どうすればいい?
コンストラクタからオーバーライド可能なメソッドを呼び出す場合
簡単に言うと、これは間違っています。
MANY
バグが発生します。このような場合
@Override
を呼び出すと、オブジェクトの状態に矛盾が生じたり、不完全な状態になることがあります。
引用元 Effective Java 2nd Edition, Item 17: Design and document for inheritance, or else prohibit it. :
継承を可能にするために、クラスが従わなければならない制限がいくつかあります。 コンストラクタはオーバーライド可能なメソッドを呼び出してはならない。 直接、間接を問わず。このルールに違反した場合、プログラムは失敗します。スーパークラスのコンストラクタはサブクラスのコンストラクタの前に実行されるため、サブクラスのオーバーライド メソッドはサブクラスのコンストラクタが実行される前に呼び出されます。オーバーライドするメソッドがサブクラスのコンストラクタによって実行される初期化に依存する場合、そのメソッドは期待どおりに動作しません。
以下に例を挙げて説明します。
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
で、このメソッドは間違った値を取得します。これは、ほぼ間違いなくバグやエラーの原因となります。
関連する質問
- 親クラスのコンストラクタからオーバーライドされたメソッドを呼び出す
- Java でベースクラスのコンストラクタがオーバーライドされたメソッドを呼び出したときの派生クラスオブジェクトの状態
- 抽象クラスのコンストラクタで、抽象 init() 関数を使用する。
こちらもご覧ください
多くのパラメータを持つオブジェクトの構築について
多くのパラメータを持つコンストラクタは可読性を低下させる可能性があり、より良い代替案が存在します。
以下、引用します。 Effective Java 第2版、項目2:コンストラクタのパラメータが多い場合はビルダパターンを考慮する :
従来、プログラマは 伸縮自在のコンストラクタ このパターンでは、あるコンストラクタには必須のパラメータだけを与え、別のコンストラクタにはオプションのパラメータをひとつ与え、3番目のコンストラクタにはオプションのパラメータをふたつ与え......といった具合に指定します。
telescopingコンストラクタのパターンは、基本的にこのようなものです。
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はbuilderパターンを使うことを推奨しており、そうすれば代わりにこのようなことが書ける。
Telescope telly = new Telescope.Builder("X/1999").setAdjustable(true).build();
パラメータには名前がついていて、好きな順番で設定でき、デフォルト値のままにしておきたいパラメータはスキップできることに注意してください。特に、同じ型に属するパラメータが大量にあるような場合、コンストラクタを拡張するよりもずっと良い方法です。
こちらもご覧ください
- ウィキペディア/ビルダーパターン
- Effective Java 第2版 項目2:コンストラクタのパラメータが多い場合はビルダパターンを検討する ( オンライン抜粋 )
関連する質問
関連
-
Spring Boot による HTTPS アクセスの設定
-
SpringBootApplication を型解決できない。
-
Eclipseで "XXXX "の解決策を(型に)解決することができない
-
リソースの読み込みに失敗しました。サーバーはステータス500(内部サーバーエラー)で応答しました。
-
xxx:jarのアーティファクトディスクリプタの読み込みに失敗した問題は解決しました。
-
Javaエラーメッセージがenclosingクラスでない
-
javaでよく使われる英単語
-
Eclipse起動エラー:javaは起動したが、終了コード=1を返した(ネット上の様々な落とし穴)
-
JSPで「リストが型解決できない!」の解決方法
-
[解決済み] メソッドと関数の違いは何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
java.util.NoSuchElementException 原因解析と解決方法
-
Java のエラーです。未解決のコンパイル問題 解決方法
-
javaの実行中に「javaの例外が発生しました」と表示された場合はどうすればよいですか?
-
SpringBootApplication を型解決できない。
-
スキャナは、タイプに解決することはできません最もルーキー初心者の質問
-
JQuery DataTable 详解
-
eclipse の実行時に java 仮想マシンが見つからなかった
-
Java コンパイルエラー - スレッド "main" で例外 java.lang.Error: 未解決のコンパイル問題です。
-
スレッド "main" で例外発生 java.net.BindException: アドレスは既に使用中です。NET_Bind
-
Google Chromeのエラー「Not allowed to load local resource」の解決策について