2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

なぜJava8のStreamAPIのmapのシグネチャはmap(Function<? super T,? extends R> mapper)なのか?

Last updated at Posted at 2016-08-20

なぜ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
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)となっていると思っていても何ら実用上問題はないのですが、改めてマニュアルを見てアレレとなってしまわないように。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?