JavaジェネリックスのPECS原則、extendsとsuperの勘所
タグ: java / 初版公開: 2014-02-12

はじめに

PECS(Producer extends and Consumer super)とは、Javaのジェネリックスプログラミングにおいて、ジェネリッククラスのメソッドに柔軟性を持たせるための原則である。基本は以下の通り。

  • メソッドが値を取得するコレクション(Producer)は型にextendsをつける
  • メソッドで値を設定するコレクション(Consumer)は型にsuperをつける

説明のためスタックの実装を考える。Stackはジェネリッククラスであり、任意のクラスのオブジェクトのスタックを表現する。Stackのソースコードを以下に示すが、実装は重要でないので、シグネチャに注目して欲しい。

public class Stack<T> {
	private List<T> stack = new ArrayList<T>();
	
	public void push(T elm){
		stack.add(elm);
	}
	
	public T pop(){
		return stack.remove(stack.size() - 1);
	}
	
	public boolean isEmpty(){
		return stack.size() > 0 ? false : true;
	}
}

このStackを拡張して、以下2つのメソッドを追加することにする。

  • pushAllメソッド – Iterableを受け取り、コレクションの全要素をスタックにプッシュするメソッド
  • popAllメソッド – Collectionを受け取り、スタックの全要素をポップしてコレクションに出力するメソッド

Producer extends

この時、もしStack<Number>であれば、pushAllメソッドはIterable<Number>だけでなく、Numberのサブクラスのコレクション、すなわちIterable<Double>Iterable<Integer>も受け取れればAPIの柔軟性が増す。

つまりこのメソッドは、型パラメータTに対して、TTのサブクラスのIterableを受け取らねばならない。これを指定するのが? extends Tというワイルドカード型だ。これが“Producer extends”の原則であり、この原則に従って実装したメソッドを以下に示す。

	public void pushAll(Iterable<? extends T> src){
		for(T elm : src){
			push(elm);
		}
	}

Consumer super

一方、Stack<Number>の時、popAllメソッドは、Collection<Number>だけでなく、Collection<Object>を受け取り、値を詰め込むことができれば、APIはより柔軟になる。

つまりこのメソッドは、型パラメータTに対して、TTのスーパークラスのCollectionを受け取り、値を詰め込めることが望ましい。これを指定するのが? super Tというワイルドカード型だ。これが“Consumer super”の原則であり、この原則に従って実装したメソッドを以下に示す。

	public void popAll(Collection<? super T> dst){
		while(!isEmpty()){
			dst.add(pop());
		}
	}

おわりに

本エントリーは、Effective Java 項目28の内容のうち、PECS原則に焦点を当てて、要点をまとめたものだ。境界ワイルドカード型の使い方については、詳しくは本書を参照。

Effective Java 第2版 (The Java Series)
Joshua Bloch
ピアソンエデュケーション
売り上げランキング: 88,031