PR

Java:共変戻り値 (Covariant Return Type)を1分で

共変戻り値 (Covariant Return Type) は、Java 5 から導入された機能で、「子クラスでメソッドをオーバーライドするとき、戻り値の型を親クラスの戻り値のサブクラスに変更できる」というものです。

  • 例えば、親クラスのメソッド戻り値が Animal という型だった場合、オーバーライドする子クラスでは、その戻り値を Animal のサブクラスである CatDog 等に変更して定義することができます。
スポンサーリンク

【復習】オーバーライドとは

オーバーライド (Override)は、親クラスで宣言・定義したメソッドと同名・同シグニチャ(引数リスト)を持つメソッドを、子クラス側で再定義することです。これにより、親クラスの振る舞いを「子クラスならではの振る舞い」に差し替えることができます。

  • ポイント オーバーライドの条件
    1. メソッド名が親クラスと同一であること
    2. メソッドの引数リスト(パラメータの型や数など)が親クラスと同一であること
    3. アクセス修飾子は、親クラスのメソッドよりも厳しくしてはいけない
      • 例:親クラスで public だったメソッドを private にするのは不可
    4. 戻り値の型が親クラスと同じか、またはそのサブクラスであること
      • ここが共変戻り値のキーポイント

つまり、もともとは「戻り値の型が親と全く同じであること」が基本だったのですが、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;
    }
}

コード解説

  1. Animal クラスは、getAnimal() メソッドで Animal 型を返している。
  2. Cat クラスは Animal クラスを継承し、同名・同シグニチャの getAnimal() を定義している。ただし、戻り値の型を Cat に変更。
  3. 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 であっても、実行時には CatgetAnimal() が呼ばれます(動的ディスパッチ)。ただし、返ってくる型を実際に 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# のようにキーワードで明示的に共変・反変を指定するわけではない)。

いずれにしても、「子クラスでより限定的な型を返すことでコンパイル時に型安全を保ちやすくする」という思想は共通しています。

共変戻り値のまとめ

  1. 共変戻り値 (Covariant Return Type) は、オーバーライド時に「親クラスの戻り値よりも狭い型(サブクラス)を戻り値に指定できる」機能で、Java 5 から導入。
  2. メリット:
    • キャスト不要で、明確にサブクラスの機能を利用できる
    • コードが読みやすくなり、型安全も向上
  3. 使用例:
    • ビルダーやフルエントインタフェースでのメソッドチェーン
    • 抽象クラスや汎用クラスで定義されたメソッドを子クラスで具象的に返す場合
  4. 注意:
    • むやみに使うのではなく、設計上「実際にサブクラスを返す」ことが意味をなすケースで用いると効果的

共変戻り値は、Java におけるオブジェクト指向設計をより洗練させ、キャストなどの冗長な記述を減らせる便利な機能です。継承やポリモーフィズムの概念をしっかりと把握しつつ、共変戻り値の恩恵を受けられる場面を見極めることで、より保守性の高いコードを書くことができます。

タイトルとURLをコピーしました