共変戻り値 (Covariant Return Type) は、Java 5 から導入された機能で、「子クラスでメソッドをオーバーライドするとき、戻り値の型を親クラスの戻り値のサブクラスに変更できる」というものです。
- 例えば、親クラスのメソッド戻り値が
Animal
という型だった場合、オーバーライドする子クラスでは、その戻り値をAnimal
のサブクラスであるCat
やDog
等に変更して定義することができます。
【復習】オーバーライドとは
オーバーライド (Override)は、親クラスで宣言・定義したメソッドと同名・同シグニチャ(引数リスト)を持つメソッドを、子クラス側で再定義することです。これにより、親クラスの振る舞いを「子クラスならではの振る舞い」に差し替えることができます。
- メソッド名が親クラスと同一であること
- メソッドの引数リスト(パラメータの型や数など)が親クラスと同一であること
- アクセス修飾子は、親クラスのメソッドよりも厳しくしてはいけない
- 例:親クラスで
public
だったメソッドをprivate
にするのは不可
- 例:親クラスで
- 戻り値の型が親クラスと同じか、またはそのサブクラスであること
- ここが共変戻り値のキーポイント
オーバーライドの条件
つまり、もともとは「戻り値の型が親と全く同じであること」が基本だったのですが、Java 5 以降は「戻り値の型が親よりもサブクラス(より限定的な型)ならOK」と認められています。
共変戻り値の実例:サンプルコード
以下のように、「動物」を表すクラス Animal
を親クラス、その「猫」を表すクラス Cat
を子クラスとします。
class Animal { private String name; public Animal(String name) { this.name = name; } // 返り値は "Animal" public Animal getAnimal() { System.out.println("Animal: getAnimal()"); return this; } } class Cat extends Animal { public Cat(String name) { super(name); } // オーバーライドで、戻り値を "Cat" (親である Animal のサブクラス) に変更 @Override public Cat getAnimal() { System.out.println("Cat: getAnimal()"); return this; } }
Animal
クラスは、getAnimal()
メソッドでAnimal
型を返している。Cat
クラスはAnimal
クラスを継承し、同名・同シグニチャのgetAnimal()
を定義している。ただし、戻り値の型をCat
に変更。- Java 5 以降の共変戻り値のおかげで、これがコンパイルエラーにならず、正しくオーバーライドとして認識される。
呼び出しの様子
public class Main { public static void main(String[] args) { Animal a1 = new Animal("Generic Animal"); Animal a2 = new Cat("Tama"); // 親クラスのインスタンスの場合 Animal result1 = a1.getAnimal(); // Animal: getAnimal() // 実体は Cat だが、型が Animal として扱われる Animal result2 = a2.getAnimal(); // Cat: getAnimal() が呼ばれるが, 変数型は Animal // Cat で変数を受け取ればキャスト不要で Cat のメソッドが呼べる Cat c = new Cat("Mike"); Cat result3 = c.getAnimal(); // Cat: getAnimal() // ここで return this; は Cat 型として返るので、result3 は明確に Cat 型 } } // 出力 // Animal: getAnimal() // Cat: getAnimal() // Cat: getAnimal()
a2.getAnimal()
のように、変数型は Animal
であっても、実行時には Cat
の getAnimal()
が呼ばれます(動的ディスパッチ)。ただし、返ってくる型を実際に Cat
として使いたい場合は、型を Cat
にしておけばキャストが不要です。
共変戻り値がないとどうなる?
Java 5 以前では、オーバーライドの戻り値を親クラスと同じ型に限定していました。そのため、「実装としては Cat
を返していても、定義上は Animal
を返す」としか書けなかったのです。すると、以下のようなキャストが必須になってしまうことが多々ありました。
public Animal getAnimal() { // 実は Cat インスタンスを返している return new Cat("Tama"); } public void someMethod() { Animal a = getAnimal(); // Cat のメソッドを呼びたいときはキャストが必要 if (a instanceof Cat) { ((Cat) a).meow(); } }
共変戻り値が使えるようになると、戻り値の宣言時点で Cat
を返すことが明示できるので、呼び出し側が無駄なキャストをしなくてよくなり、より型安全・読みやすいコードになります。
- Q戻り値がサブクラスに変わるだけで何が変わるのか?
- A
戻り値が「より具体的な型」になることで、後続の処理でキャストが不要になるのが一番大きな変化です。これにより、コンパイル時に型チェックが厳密に働くので、安全性も向上します。
- Q「共変戻り値」と「ジェネリクス」との関係は?
- A
ジェネリクス(Generics)を使ったクラス継承においても、「親クラスのジェネリック引数のサブタイプ」にする といったケースで、共変戻り値に似た考え方が使われることがあります。
ただし、Java のジェネリクス自体は共変・反変といった概念をそのままはサポートしていません(ワイルドカード
? extends
/? super
などでそれっぽく表現しますが、C# のようにキーワードで明示的に共変・反変を指定するわけではない)。いずれにしても、「子クラスでより限定的な型を返すことでコンパイル時に型安全を保ちやすくする」という思想は共通しています。
共変戻り値のまとめ
- 共変戻り値 (Covariant Return Type) は、オーバーライド時に「親クラスの戻り値よりも狭い型(サブクラス)を戻り値に指定できる」機能で、Java 5 から導入。
- メリット:
- キャスト不要で、明確にサブクラスの機能を利用できる
- コードが読みやすくなり、型安全も向上
- 使用例:
- ビルダーやフルエントインタフェースでのメソッドチェーン
- 抽象クラスや汎用クラスで定義されたメソッドを子クラスで具象的に返す場合
- 注意:
- むやみに使うのではなく、設計上「実際にサブクラスを返す」ことが意味をなすケースで用いると効果的
共変戻り値は、Java におけるオブジェクト指向設計をより洗練させ、キャストなどの冗長な記述を減らせる便利な機能です。継承やポリモーフィズムの概念をしっかりと把握しつつ、共変戻り値の恩恵を受けられる場面を見極めることで、より保守性の高いコードを書くことができます。