1. 概要

このチュートリアルでは、Javaの共変リターンタイプを詳しく見ていきます。 リターンタイプの観点から共分散を調べる前に、それが何を意味するかを見てみましょう。

2. 共分散

共分散は、スーパータイプのみが定義されている場合にサブタイプがどのように受け入れられるかについての契約と見なすことができます。

共分散のいくつかの基本的な例を考えてみましょう。

List<? extends Number> integerList = new ArrayList<Integer>();
List<? extends Number> doubleList = new ArrayList<Double>();

So 共分散とは、スーパータイプを介して定義された特定の要素にアクセスできることを意味します。 ただし、共変システムに要素を配置することは許可されていません。これは、コンパイラが汎用構造の実際のタイプを判別できないためです。

3. 共変リターンタイプ

共変リターンタイプは、メソッドをオーバーライドすると、リターンタイプをオーバーライドされたメソッドのタイプのサブタイプにすることができます。

これを実践するために、produce()methodを使用した単純なProducerクラスを取り上げましょう。デフォルトでは、Stringが返されます。 オブジェクトとして、子クラスに柔軟性を提供します。

public class Producer {
    public Object produce(String input) {
        Object result = input.toLowerCase();
        return result;
    }
}

戻り型としてのオブジェクトの結果として、子クラスでより具体的な戻り型を持つことができます。 これは共変の戻りタイプになり、文字シーケンスから数値を生成します。

public class IntegerProducer extends Producer {
    @Override
    public Integer produce(String input) {
        return Integer.parseInt(input);
    }
}

4. 構造の使用法

共変リターン型の背後にある主な考え方は、リスコフ置換をサポートすることです。

たとえば、次のプロデューサーシナリオを考えてみましょう。

@Test
public void whenInputIsArbitrary_thenProducerProducesString() {
    String arbitraryInput = "just a random text";
    Producer producer = new Producer();

    Object objectOutput = producer.produce(arbitraryInput);

    assertEquals(arbitraryInput, objectOutput);
    assertEquals(String.class, objectOutput.getClass());
}

IntegerProducer に変更した後、実際に結果を生成するビジネスロジックは同じままにすることができます。

@Test
public void whenInputIsSupported_thenProducerCreatesInteger() {
    String integerAsString = "42";
    Producer producer = new IntegerProducer();

    Object result = producer.produce(integerAsString);

    assertEquals(Integer.class, result.getClass());
    assertEquals(Integer.parseInt(integerAsString), result);
}

ただし、引き続きObjectを介して結果を参照しています。 IntegerProducerへの明示的な参照を使用し始めると、結果をIntegerとして取得できます。 ]ダウンキャストなし:

@Test
public void whenInputIsSupported_thenIntegerProducerCreatesIntegerWithoutCasting() {
    String integerAsString = "42";
    IntegerProducer producer = new IntegerProducer();

    Integer result = producer.produce(integerAsString);

    assertEquals(Integer.parseInt(integerAsString), result);
}

よく知られているシナリオは、 Object# clone メソッドで、デフォルトでObjectを返します。 clone()メソッドをオーバーライドするときはいつでも、共変リターン型の機能により、オブジェクト自体よりも具体的なリターンオブジェクトを持つことができます。

5. 結論

この記事では、共分散と共分散の戻り型が何であるか、そしてそれらがJavaでどのように動作するかを見ました。

いつものように、コードはGitHubから入手できます。