Javaでは、メソッドのオーバーロード(overloading)とオーバーライド(overriding)は特定のルールに従って解決されます。オーバーロードの際、コンパイラは呼び出し元の引数の型に基づいて、コンパイル時に最も適切なメソッドを選択します。
重要なポイント:
例:
class Super { void print(Number n) { System.out.println("Super Number"); } } class Sub extends Super { void print(Integer i) { System.out.println("Sub Integer"); } } ... Sub s = new Sub(); s.print(5); // Sub Integer s.print(5.0); // Super Number
ここでは、print(Integer)がInteger型の引数に対して呼び出され、Double型に対しては親メソッドが呼び出されます。
サブクラスでメソッドをオーバーライド(override)し、オーバーロード(overload)した場合、ポリモーフィズムの際にどちらが呼び出されますか?
回答: ポリモーフィズムでは、オーバーライドされたメソッドの場合、常にオブジェクトの実際の型に基づいてメソッドのバージョンが選ばれます。オーバーロードされたメソッドの場合、大文字の型参照に基づいてコンパイル時に選択が行われます。
class A { void test(Number n) { System.out.println("A:Number"); } } class B extends A { void test(Integer n) { System.out.println("B:Integer"); } @Override void test(Number n) { System.out.println("B:Number"); } } ... A obj = new B(); obj.test(1); // B:Numberが呼び出される、test(Integer)が存在しても
物語
開発者はサブクラスにオーバーロードされたバージョンのメソッドを追加し、すべてのサブクラスに対して呼び出されることを期待していました。しかし、親クラスの参照を通じて呼び出すと、オブジェクトの型ではなく参照の型に基づいてメソッドが選択され、必要なロジックが実行されませんでした。
物語
大規模なプロジェクトでequals(Object)メソッドを誤ってequals(MyClass obj)というシグネチャでオーバーロードし、標準のequalsを置き換えると思っていました。HashSetのコレクションで比較すると、デフォルトのequals(Object)が動作し、ロジックの不一致とデータの損失を引き起こしました。
物語
varargsを持つメソッドの新しいバージョンをクラス階層に追加すると、配列に対して必要なバージョンが呼び出されると思われました。しかし、配列を明示的に渡すと、コンパイラが間違ったオーバーロードを選択し、結果として処理が不正確になり、バグが長い間見逃されていました。