Javaのジェネリックコンストラクター

1. 概要

以前にlink:/java-generics[Java Generics]の基本について説明しました。 このチュートリアルでは、JavaのGeneric Constructorsを見ていきます。
*ジェネリックコンストラクターは、ジェネリック型のパラメーターを少なくとも1つ持つコンストラクターです*
ジェネリックコンストラクターはジェネリッククラスにある必要はなく、ジェネリッククラスのすべてのコンストラクターがジェネリックである必要はありません。

2. 非ジェネリッククラス

最初に、汎用クラスではない単純なクラス_Entry_があります。
public class Entry {
    private String data;
    private int rank;
}
このクラスでは、2つのコンストラクターを追加します。2つのパラメーターを持つ基本コンストラクターと、汎用コンストラクターです。

2.1. 基本コンストラクタ

最初の_Entry_コンストラクターは、2つのパラメーターを持つ単純なコンストラクターです。
public Entry(String data, int rank) {
    this.data = data;
    this.rank = rank;
}
それでは、この基本的なコンストラクタを使用して_Entry_オブジェクトを作成しましょう。
@Test
public void givenNonGenericConstructor_whenCreateNonGenericEntry_thenOK() {
    Entry entry = new Entry("sample", 1);

    assertEquals("sample", entry.getData());
    assertEquals(1, entry.getRank());
}

2.2. 汎用コンストラクター

次に、2番目のコンストラクターはジェネリックコンストラクターです。
public <E extends Rankable & Serializable> Entry(E element) {
    this.data = element.toString();
    this.rank = element.getRank();
}
  • _Entry_クラスはジェネリックではありませんが、タイプ_E_のパラメーター_element_があるため、ジェネリックコンストラクターがあります。*

    ジェネリック型_E_は制限されており、_Rankable_および_Serializable_インターフェイスの両方を実装する必要があります。
    それでは、1つのメソッドを持つ_Rankable_インターフェースを見てみましょう。
public interface Rankable {
    public int getRank();
}
そして、_Rankable_インターフェイスを実装するクラス_Product_があるとします:
public class Product implements Rankable, Serializable {
    private String name;
    private double price;
    private int sales;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public int getRank() {
        return sales;
    }
}
次に、汎用コンストラクターを使用して、_Product_を使用して_Entry_オブジェクトを作成できます。
@Test
public void givenGenericConstructor_whenCreateNonGenericEntry_thenOK() {
    Product product = new Product("milk", 2.5);
    product.setSales(30);

    Entry entry = new Entry(product);

    assertEquals(product.toString(), entry.getData());
    assertEquals(30, entry.getRank());
}

3. ジェネリッククラス

次に、_GenericEntry_と呼ばれるジェネリッククラスを見てみましょう。
public class GenericEntry<T> {
    private T data;
    private int rank;
}
このクラスの前のセクションと同じ2種類のコンストラクターも追加します。

3.1. 基本コンストラクタ

まず、_GenericEntry_クラス用の単純な非ジェネリックコンストラクターを作成しましょう。
public GenericEntry(int rank) {
    this.rank = rank;
}
  • _GenericEntry_はジェネリッククラスですが、これはジェネリック型のパラメーターを持たない単純なコンストラクターです*

    これで、このコンストラクターを使用して_GenericEntry <String> _を作成できます。
@Test
public void givenNonGenericConstructor_whenCreateGenericEntry_thenOK() {
    GenericEntry<String> entry = new GenericEntry<String>(1);

    assertNull(entry.getData());
    assertEquals(1, entry.getRank());
}

3.2. 汎用コンストラクター

次に、2番目のコンストラクターをクラスに追加します。
public GenericEntry(T data, int rank) {
    this.data = data;
    this.rank = rank;
}
*これは、ジェネリック型_T_の_data_パラメーターを持っているため、ジェネリックコンストラクターです。*暗黙的に存在するので、コンストラクター宣言に_ <T> _を追加する必要がないことに注意してください。
それでは、汎用コンストラクターをテストしましょう。
@Test
public void givenGenericConstructor_whenCreateGenericEntry_thenOK() {
    GenericEntry<String> entry = new GenericEntry<String>("sample", 1);

    assertEquals("sample", entry.getData());
    assertEquals(1, entry.getRank());
}

4. 異なるタイプのジェネリックコンストラクター

ジェネリッククラスには、クラスのジェネリック型とは異なるジェネリック型を持つコンストラクターを含めることもできます。
public <E extends Rankable & Serializable> GenericEntry(E element) {
    this.data = (T) element;
    this.rank = element.getRank();
}
この_GenericEntry_コンストラクターには、_T_タイプとは異なる_E_タイプのパラメーター_element_があります。 実際に見てみましょう:
@Test
public void givenGenericConstructorWithDifferentType_whenCreateGenericEntry_thenOK() {
    Product product = new Product("milk", 2.5);
    product.setSales(30);

    GenericEntry<Serializable> entry = new GenericEntry<Serializable>(product);

    assertEquals(product, entry.getData());
    assertEquals(30, entry.getRank());
}
ご了承ください:
  • この例では、ProductE)を使用して_GenericEntry_を作成しました
    タイプ_Serializable_(T

  • このコンストラクタは、_E_型のパラメータが
    _T_にキャスト

5. 複数のジェネリックタイプ

次に、2つのジェネリック型を持つジェネリッククラス_MapEntry_があります。
public class MapEntry<K, V> {
    private K key;
    private V value;

    public MapEntry(K key, V value) {
        this.key = key;
        this.value = value;
    }
}
  • _MapEntry_には、それぞれ異なる型の2つのパラメーターを持つ1つの汎用コンストラクターがあります。*単純な単体テストで使用してみましょう。

@Test
public void givenGenericConstructor_whenCreateGenericEntryWithTwoTypes_thenOK() {
    MapEntry<String,Integer> entry = new MapEntry<String,Integer>("sample", 1);

    assertEquals("sample", entry.getKey());
    assertEquals(1, entry.getValue().intValue());
}

6. ワイルドカード

最後に、ジェネリックコンストラクターでワイルドカードを使用できます。
public GenericEntry(Optional<? extends Rankable> optional) {
    if (optional.isPresent()) {
        this.data = (T) optional.get();
        this.rank = optional.get().getRank();
    }
}
ここでは、この_GenericEntry_コンストラクターでワイルドカードを使用して、_Optional_型をバインドしました。
@Test
public void givenGenericConstructorWithWildCard_whenCreateGenericEntry_thenOK() {
    Product product = new Product("milk", 2.5);
    product.setSales(30);
    Optional<Product> optional = Optional.of(product);

    GenericEntry<Serializable> entry = new GenericEntry<Serializable>(optional);

    assertEquals(product, entry.getData());
    assertEquals(30, entry.getRank());
}
オプションのパラメーター型(この場合は_Product_)を_GenericEntry_型(この場合は_Serializable_)にキャストできることに注意してください。

7. 結論

この記事では、ジェネリッククラスと非ジェネリッククラスの両方でジェネリックコンストラクターを定義および使用する方法を学びました。
完全なソースコードは、https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-lang-oop-2 [GitHub]で見つけることができます。