Javaに関する様々な情報をご紹介します。

3ワイルドカード

Javaに関する様々な情報をご紹介します。

ワイルドカード

Javaではジェネリクスでデータ型を指定する際、データ型に幅を持たせるためワイルドカードを使用することができます。ここでは、ワイルドカードの使用方法について説明します。

代入互換性

ジェネリクスのワイルドカードを理解するためには、ジェネリクスの代入互換性を理解する必要があります。Javaでは、代入する変数間にクラスの継承関係がある場合、変数の代入は可能です。しかし、ジェネリクス型の変数として使用する場合は、代入できません。

以下の代入は可能です。

String strIns = new String();
Object objIns = new Object();

//下位クラスの変数から上位クラスの変数へ代入可能
objIns = strIns;

 

しかし、ジェネリクス型の変数として使用する場合は、代入できません。

List<String> strList = new ArrayList<String>();
List<Object> objList  new ArrayList<Object>();

//コンパイルエラー
objList = strList;

 

もし、「objList = strList;」ができるとobjListを通して、strListにString型とは異なるデータ型の値が代入され(objeList.add(100)でInteger型のデータを代入してしまう等)、想定外の実行時エラーを引き起こす原因になります。

ワイルドカード

ジェネリクスでは、ワイルドカードを使用することで使用するデータ型に幅を持たせることができます。List<Number>と指定した場合、Number型のデータしか使用できません、List<? extends Number>と指定した場合、Number型及び、Number型を継承するクラスのデータ型が使用できます。これは、指定した型と同じかその型を継承するサブクラス(サブインターフェース)であることを保証する宣言で、上限境界ワイルドカードと言います。

一方、<? super Number>と指定した場合、Number型及び、Number型のスーパークラスのデータ型が使用できます。これは、指定した型と同じかその型のスーパークラスであることを保証する宣言で、下限境界ワイルドカードと言います。

その他、上限も下限も指定しない<?>という宣言も可能です。これは、変数のデータ型に依存しない処理を行う場合に使用します。

extends(境界)でも<T extends A>の形式で、データ型を指定することができますが、ワイルドカードとは異なる概念です。extends(境界)は、型パラメータを宣言する際、指定できるデータ型を制限するために使用するものです。

ワイルドカードの使い方

以下のサンプルクラスを元に、ワイルドカードの使い方について説明します。

public class Shelf<T> {
   private T value;

   public void setValue (T value) {
     this.value = value;
   }

   public T getValue() {
     return value;
   }
}

 

<? extends ○○>のデータ型の変数は、その変数から値を取り出す(get)際は、上限である○○型のデータ型を返すように動作し意図したとおりに動作しますが、値を設定する(put)際は、putされる値が○○のサブクラスということは保証できるが、実際にどのクラスか特定できないため、コンパイルエラーになります。

そのため、<? extends ○○>は、変数から値を取り出す(get)のみの場合に使用します。
以下に例を記載します。

Shelf<String> strValue = new Shelf<String>();
Shelf<Integer> intValue = new Shelf<Integer>();

Shelf<? extends Number> list;
list = strValue;  //コンパイルエラー
list = intValue;  //代入可能、listとintValueに継承関係があるため

Number value = list.getValue();  //値を取得する際は、Number型として取得される
list.setValue("test");  //コンパイルエラー
list.setValue(10);  //コンパイルエラー、○○のサブクラスということは保証できるが、?が実際にどのクラスか特定できないため

 

<? super ○○>のデータ型の変数は、その変数に値を設定する(put)際は、下限である○○のデータ型を設定するように動作し意図したとおりに動作しますが、値を取り出す(get)際は、どのデータ型か特定できないためObject型として取り出されます。

Object型として取り出されるとデータ型を指定した意味がなくなるため、<? super ○○>は、変数から値を設定する(put)のみの場合に使用します。
以下に例を記載します。

Shelf<String> strValue = new Shelf<String>();
Shelf<Number> numValue = new Shelf<Number>();

Shelf<? super Integer> list;
list = strValue;  //コンパイルエラー
list = numValue;  //代入可能、exListとnumListに継承関係があるため

Object value = list.getValue();  //値を取得する際は、Object型として取得される
list.setValue("test");  //コンパイルエラー
list.setValue(10); //下限であるInteger型の値を設定するように動作する

 

ワイルドカードを利用することで、プログラムに柔軟性を持たせることができます。ワイルドカードが使われていない場合、同一の型同士のコレクションでしかコピーできないものが、ワイルドカードを使うことで、srcのコレクションの要素型が、dstのコレクションのサブタイプであるような場合も含めてコピーすることが可能になり、柔軟性が高くなります。

ワイルドカードを使用しない場合

public statice <E> void copy(List<E> dat, List<E> src) {
	for (int i = 0; i < src.size(); i++) {
		dst.set(i, src.get(i)); 	
	}
}

ワイルドカードを使用する場合


public statice <E> void copy(List<? super E> dat, List<? extends E> src) {
	for (int i = 0; i < src.size(); i++) {
		dst.set(i, src.get(i));
	}
}

 

<参考URL>
Java総称型のワイルドカードを上手に使いこなすための勘所

3ワイルドカード