質問とシナリオ
Java 25ではFlexible Constructor Bodies(JEP 513)が正式機能となります。明示的なsuper(...)またはthis(...)の前に何を実行できるか、なぜ構築中のオブジェクトを読み取ることができないのか、そしてこの機能が継承や引数の前処理にどのように影響するかを説明してください。
面接官がテストしていること
- コンストラクタチェーンと、インスタンスがまだ初期化されていないという安全規則の理解。
- 許可される静的またはローカルなロジックと、
thisへの不正なアクセスの区別。 - プレビュー版と正式版の違い、およびソース/ターゲットの移行リスクの認識。
回答前の確認質問
対象がJDK 25の正式機能なのか、それとも以前のJEP 447/482/492のプレビュー機能なのか、加えてプロジェクトのsource、target、およびruntimeのバージョンを確認します。また、焦点が構文、JVM検証、あるいはsuper(...)の引数を準備するヘルパーのインライン化にあるのかを確認します。
30秒の回答フレームワーク
従来のJavaでは、コンストラクタの最初の文がsuper(...)またはthis(...)である必要がありました。Flexible Constructor Bodiesでは、その呼び出しの前にローカル変数の計算や現在のクラスのフィールドの初期化など、制限されたプロローグが許可されますが、コードが構築中のインスタンスを読み取ったり呼び出したりすること、および未初期化のthisを任意のコードに渡すことはできません。これにより、安全なスーパークラスの構築順序を維持しながら、引数の直接的な前処理が可能になります。
ステップバイステップの詳細な回答
- コンストラクタにはプロローグ、明示的なコンストラクタ呼び出し、そして残りの本体があります。
this(...)は現在のクラスのチェーンを継続し、super(...)は親クラスに入ります。 - プロローグでは、ローカル変数の宣言、インスタンス状態に依存しない式の評価、およびまだ初期化されていない現在のクラスのフィールドの初期化が可能です。これらの値をコンストラクタ引数に渡すことができます。
- インスタンスフィールドの読み取り、インスタンスメソッドの呼び出し、引数としての
thisの使用、または部分的に初期化されたオブジェクトを観察可能なコードに公開することはできません。 - スーパークラスのコンストラクタが無効な初期化順序を観察しないように、コンパイラとJVMはどのフィールドが未初期化のままであるかを追跡します。静的メソッドと定数は、規則が許可する範囲で使用可能です。
- 移行時には、
super(...)の引数を準備するためだけに存在していた場合にのみヘルパーを削除します。副作用があるもの、複数の呼び出し元があるもの、または独立したテスト価値があるヘルパーはそのまま維持します。 - JDK 25ツールチェーンでコンパイルおよび実行します。古いsource/targetを対象とするプロジェクトは、単にその構文を採用することはできません。
class Child extends Parent {
private final String normalized;
Child(String raw) {
var value = raw.trim(); // prologue: local computation
super(value); // explicit constructor invocation
this.normalized = value;
}
}高品質な回答サンプル
JEP 513は、super(...)またはthis(...)の前の制限されたプロローグを許可します。これは、インスタンスの状態に依存しない引数の前処理や現在のクラスのフィールド初期化に役立ちますが、フィールドの読み取り、インスタンスメソッドの呼び出し、または未初期化のthisの公開は依然として禁止されています。コンパイラとJVMは、スーパークラスが正当な初期化状態のみを認識するという保証を維持します。私であれば、JEP 447/482/492のプレビューとJDK 25の正式機能を区別し、source、target、runtimeのバージョンを確認します。この機能により、コンストラクタの順序や継承の安全性の制約を崩すことなく、使い捨ての静的ヘルパーを減らすことができます。
よくある間違い
super()の前に任意の文を記述できると述べること。thisが初期化されていないことを無視し、インスタンスの読み取りや呼び出しを許可すること。- プレビュー版のJEP番号をJDK 25の正式な番号と呼ぶこと。
- コンパイラターゲットとデプロイランタイムを確認せずにソースコードを編集すること。
- 都合の良い例を作るために、外部から観察可能な副作用をプロローグに追加すること。
フォローアップ質問と回答
なぜローカル計算は許可され、thisの読み取りは許可されないのですか?
ローカル計算は、部分的に初期化されたオブジェクトを観察することができないためです。フィールドの読み取りやメソッドの呼び出しは動的ディスパッチを引き起こしたり、不完全な状態を公開したり、スーパークラスのロジックに不変条件が崩れた状態を見せたりする可能性があります。
this(...)とsuper(...)の前のルールは同一ですか?
どちらも早期構築コンテキスト(early-construction context)によって制約されます。this(...)は現在のクラスチェーンを継続し、super(...)は親クラスに入りますが、どちらも正当な初期化の前にインスタンス状態を公開することはできません。
ヘルパーをインライン化せず、以前のまま残すべきなのはどのような場合ですか?
複数の呼び出し元がある場合、意味のある副作用がある場合、独立したテスト価値がある場合、または明確なモジュール境界がある場合はそのまま残します。この機能は言語の機能拡張であり、リファクタリングを強制するものではありません。
より古いJDKはどのようにサポートしますか?
古いsource/targetでは新しい構文を使用できません。コンパイラ、ランタイム、およびリリースマトリックスが一緒にアップグレードされるまで、既存の引数準備アプローチを維持します。