なぜJava8のStreamAPIのmapのシグネチャはmap(Function<T,R>mapper)ではなくmap(Function<? super T,? extends R>mapper)なのでしょうか。
またそうだとしても、PECSでは入力となるオブジェクトのコンテナをColection extends T>として、出力をCollection super R>に格納するのでしたが、Stream APIの各メソッドに渡す関数の入力パラメータは?super Tになっており、出力が? extends Rになっています。PECSとは逆になっているような気がします。
具体的な型
例えばmapは
map(Function super T,? extends R> mapper)
forEachは
forEach(Consumer super T> action)
のようにワイルドカードが指定されています。
具体的なイメージを湧かせるために、Animalとその派生クラスで考えます。
class Animal(){
}
class Dog extends Animal(){
}
class Puppy extends Dog(){
}
class Cat extends Animal(){
}
class Kitty extends Cat(){
}
DogはAnimalのサブクラスとします PuppyはDogのサブクラスとします
CatはAnimalのサブクラスとします KittyはCatのサブクラスとします
シグネチャの解釈
map(Function<? super Dog,? extends Cat> mapper)
の解釈は
(誤)mapは、Dogの先祖を入力できてCatの子孫を出力する ではなく
(正)mapはDogを入力してCatを出力するのだが、mapに渡すmapper関数は、Dogの先祖を入力できてCatの子孫を出力するものであれば良い
と考えられます。
入力と出力の型が異なるいろいろな種類のmapper関数があるものとします。
すべての組み合わせを羅列すると冗長なので、入力だけをDogでないものにした組み合わせ、出力だけをCatでないものにした組み合わせ、および最後に入力も出力もDog,Catでないものの例を表にしてmapに渡せるかどうかを見てみます。
入力型 | 出力型 | mapメソッドに渡せるのか? |
---|---|---|
Dog | Cat | OK |
Animal | Cat | OK |
Puppy | Cat | NG |
Dog | Animal | NG |
Dog | Kitty | OK |
Animal | Kitty | OK |
map(Function<T,R>mapper)であればmapには一番上の入出力を取る関数しか渡せませんが、map(Function super T,? extends R> mapper)となっているので、表のOKの関数ならmapに渡せます。 | ||
例として表の一番下のFunction<Animal,Kitty>型関数をmapに渡してみます |
void test(){
//入力のList<Dog>を準備
List<Dog>l=new ArrayList<>();
l.add(new Dog());
//Function<Animal,Kitty>型の関数を作る
Function<? super Dog,? extends Cat> mapper=new Function<Animal,Kitty>(){
public Kitty apply(Animal a){
Kitty k=new Kitty();
k.name=a.name;
return k;
}
};
l.stream()
.map(mapper) //Function<Dog,Cat>ではなくFunction<Animal,Kitty>型を渡す
.forEach(p->p.mew());//pはCat型
}
mapメソッドとしてはDogを入力としてCatを出力しますが、mapperはAnimalを入力としてKittyを出力しています。これは、表中のOKのシグニチャの関数をmapperに取る限り、mapメソッド内では
map入力のDog-->アップキャスト-->mapper入力Kitty-->mapper処理-->Kitty出力-->アップキャスト-->map出力Cat
のようにアップキャストしか行われず型安全であることが保証されます。
実際にはDog入力Cat出力以外の関数を実装した匿名クラスを渡すことは稀で、ラムダ式を書くことが普通で、mapのシグネチャがmap(Function<T,R>mapper)となっていると思っていても何ら実用上問題はないのですが、改めてマニュアルを見てアレレとなってしまわないように。