ワイルドカード


型変数を特定しない操作

パラメータ型を扱う場合でも、しばしばパラメータ化された型変数に関係のない操作を行うことがあります。 型変数に関与しない操作のみを行うロジックにとって、型変数を特定する必要はありません。 このように型変数に興味のないロジックが、パラメータ化された型を受け取る場合はワイルドカードを使って汎用化することができます。

ワイルドカードは ? 記号で表し、パラメータ化された型の変数型宣言で利用することができます。 例えば、次のような変数宣言と初期化を考えます。

Value<String> obj1 = new Value<String>("Test");
Value<Integer> obj2 = new Value<Integer>(new Integer(10));

Value<String> と Value<Integer> は異なる型なので、型変数 T を操作するようなロジックでは統一した方法で管理することができません。 しかし、一時的に変数を保持するだけだったり、型変数から Object 型の値を得るだけのようなロジックであれば統一した方法で管理することができます。 Value<String> も Value<Integer> も保持することができる変数を宣言するには、ワイルドカードを使います。

Value<?> obj = new Value<String>("Test");
obj = new Value<Integer>(new Integer(10));

変数 obj から型変数に絡むような操作が行われない場合、上記のような記述は有効です。 Value<?> 型は、型変数が示されていないため型変数に対して代入するような行為は全て向こうとなり、コンパイルエラーとなります。

class Value<T> {
	private T value;
	public Value(T value) {
		this.value = value;
	}
	public void setValue(T value) {
		this.value = value;
	}
	public T getValue() {
		return value;
	}
}

class Test {
	static void printValue(Value<?> obj) {
		System.out.println("type = " + obj.getValue().getClass());
		System.out.println("value = " + obj.getValue() + "\n");
	}

	public static void main(String args[]) {
		printValue(new Value<String>("kitty on your lap"));
		printValue(new Value<Integer>(new Integer(10)));
	}
}

printValue() メソッドの仮パラメータリストに注目してください。 仮パラメータ obj はワイルドカードを用いた Value<?> 型として宣言されています。 この場合、Value クラスであれば型変数に指定されている型は問わないという意味が含まれることになります。

printValue() メソッドでは T 型の実装に依存するような行為がないということにも注目してください。 T 型の実体は少なくとも Object 型を継承していることは保障されているため、obj.getValue() メソッドを呼び出す操作は不正ではありません。 getValue() メソッドの戻り値は、事実上 Object 型として扱われます。

しかし、一方でワイルドカードの型変数に対して代入する行為はコンパイラによって禁止されます。 例えば、次のプログラムはコンパイルエラーとなります。

class Value<T> {
	private T value;
	public Value(T value) {
		this.value = value;
	}
	public void setValue(T value) {
		this.value = value;
	}
	public T getValue() {
		return value;
	}
}

class Test {
	public static void main(String args[]) {
		Value<?> obj = new Value<String>("kitty on your lap");
		obj.setValue("Neko mimi mode");
	}
}

この場合、Value<?> 型の変数 obj に Value<String> インスタンスを参照させています。 その後 setValue() メソッドから文字列を渡そうとしていますが、Value<?> はワイルドカードによって型変数が汎用化されているため、型変数を利用するメソッドに値を代入することは禁止されてます。 変数 obj が Value<String> 型であれば問題のない行為ですが、この場合は型を検査できないためエラーとなるのです。 ただし、例外的な処置として null 型の代入、すなわち null を代入することは許可されます。


上限境界と下限境界

ワイルドカード ? を用いた変数型に一定の制限を与える方法があります。 一定の範囲を与えられたワイルドカードを境界ワイルドカード (Bounded Wildcards) と呼び、特定のクラスのサブクラスであることを強要、またはスーパークラスであることを強要することができます。

境界ワイルドカードのうち、指定した型と同じかその型を継承するサブクラス(サブインタフェース)であることを強要するものを上限境界ワイルドカードと呼びます。 上限境界は extends キーワードを用いて、不明な型 ? が指定したクラス、またはインタフェースを実装していることを保障します。 例えば、上限型が Number クラスであることを強制するには次のように記述します。

Value <? extends Number>

この場合、Value クラスの型変数 T は不明な型であるが、Number クラスまたは Number クラスのサブクラスでなければならないという境界が設定されます。 そのため、型変数 T が String 型である場合はコンパイルエラーとなるでしょう。

Value <? extends Number> 型の変数は、型変数 T が Number クラスの機能をサポートしていることを保障することができます。 そのため、T 型のオブジェクトに対して Number クラスのメソッドを安全に要求することができるようになります。

class Value<T> {
	private T value;
	public Value(T value) {
		this.value = value;
	}
	public void setValue(T value) {
		this.value = value;
	}
	public T getValue() {
		return value;
	}
}

class Test {
	static void printValue(Value<? extends Number> obj) {
		System.out.println("type = " + obj.getValue().getClass());
		System.out.println("int value = " + obj.getValue().intValue());
		System.out.println("double value = " + obj.getValue().doubleValue() + "\n");
	}

	public static void main(String args[]) {
		printValue(new Value<Integer>(100));
		printValue(new Value<Double>(1.23456789));
		//printValue(new Value<String>("Kitty")); //エラー
	}
}
>java Test
type = class java.lang.Integer
int value = 100
double value = 100.0

type = class java.lang.Double
int value = 1
double value = 1.23456789

このプログラムの printValue() メソッドが受け取る Value クラスのオブジェクトは、T 型変数が Number を継承していなければならないという制約が付加されています。 この境界ワイルドカードの影響によって、printValue() メソッドの仮引数 obj の getValue() メソッドから得られる T 型オブジェクトは Number クラスのメソッドを保障することができます。

逆に、境界ワイルドカードでは指定した型を下限とする下限境界ワイルドカードとすることもできます。 下限境界は、そのクラス、またはそのクラスのスーパークラスであることを強制することができます。 例えば、下限境界に String クラスを指定した場合、ワイルドカード ? の型変数は String またはそのスーパークラスである Object 型でなければなりません。 下限境界ワイルドカードを指定するには extends キーワードの代わりに super キーワードを利用します。

Value <? super String>

これによって、ワイルドカードの不明な型の下限が String クラスであることを保障します。 下限境界は型変数が String 型であることを保障するものではなく、Object 型である可能性も含んでいます。 そのため、String 型のメンバを直接利用することはできません。

class Value<T> {
	private T value;
	public Value(T value) {
		this.value = value;
	}
	public void setValue(T value) {
		this.value = value;
	}
	public T getValue() {
		return value;
	}
}

class Test {
	static void printValue(Value<? super String> obj) {
		System.out.println("type = " + obj.getValue().getClass());
		System.out.println("int value = " + obj.getValue() + "\n");
	}

	public static void main(String args[]) {
		printValue(new Value<String>("Kitty"));
		printValue(new Value<Object>(new Integer(10))); //OK
		//printValue(new Value<Integer>(new Integer(10))); //エラー
	}
}

このプログラムの printValue() メソッドの引数型 Value<? super String> に注目してください。 これは、Value クラスの T 型変数の下限(最も深いサブクラス)が String 型であることを制限しています。 そのため T に String 型のサブクラスや、他の関係のないクラスを指定することはできません。

ただし、下限境界は Object 型を含めるため、Object 型にキャストしてしまえばあらゆる型のオブジェクトを T から受け取る可能性があります。



前のページへ戻る次のページへ