本稿について
ほかの方々の素晴らしい記事がありますが,個人の備忘としての記事のため,何番煎じなのか分からないような内容になっているものもあります.
for文やif文とStreamを並べたときにどのように対応されているかを観たいときにお手頃な資料が欲しいなと思い記事にしました.
Stream API
だいぶ古くなってきたJava 8で導入されたものの1つで,List/Set/Mapなどのコレクション型やFileStreamなどを対象に,各要素(ファイルであれば各行)に対して操作を行うことが出来るAPI群のこと.
Stream APIでコアとなるStreamクラスが提供する中間操作にあたるメソッド(filterやmap)は,メソッドチェーンによって連続して記述することが出来る.
Stream APIで提供しているメソッドの特徴
filterやmapなどのメソッドの引数は,FunctionやConsumerのFunctionalInterfaceになっている.そのためLambda式が使えて処理全体がスッキリとした見映えにできる.
Stream API & Lambdaで気にすること
Stream APIのメソッドに指定する処理(Lambda式)は例外を発生させるようなものを定義させない.
これは処理は「入力値に対する戻り値が常に同じであること」が前提となっているため.
できること
私が{よく使う|便利だと思う}メソッドやクラスを並べてあります.
streamメソッド
java 8で一緒に導入されたdefaultメソッドによって各コレクション型や既存のInterfaceにstreamメソッドが追加されている.
このメソッドを使えば,そのコレクション型を対象にStream APIを活用することが出来る.
List<String> strList = Arrays.asList("Alice", "Bob", "Charlie", "Dave");
// Stream APIを使って,List内で末尾がeで終わる文字列を大文字に変換して標準出力する
strList.stream()
.filter(str -> str.endsWith("e"))
.map(String::toUpperCase)
.forEach(str -> {
System.out.println(str); // -> ALICE, CHARLIE, DAVEが出力される
});
forEachメソッド
おそらくStream APIの活用により一番恩恵が受けられるものではないでしょうか?
コレクション型やBufferedReaderなどはdefaultメソッドにより,各クラスから直接利用できる.
List<String> strList = Arrays.asList("Alice", "Bob", "Charlie", "Dave");
// 通常for文
for(int index=0, len=strList.size() ; index<len ; index++) {
System.out.println("Name : " + strList.get(index));
}
// 拡張for文
for(String name : strList) {
System.out.println("Name : " + name);
}
// Stream
strList.forEach(name -> {
System.out.println("Name : " + name);
});
filterメソッド
各要素に対してフィルタリング処理(if文)を差し込みたいときに使うメソッド
List<String> strList = Arrays.asList("Alice", "Bob", "Charlie", "Dave");
// 通常for文
for(int index=0, len=strList.size() ; index<len ; index++) {
String name = strList.get(index);
if(name.endsWith("e")) {
System.out.println("Name : " + name);
}
}
// 拡張for文
for(String name : strList) {
if(name.endsWith("e")) {
System.out.println("Name : " + name);
}
}
// Stream
strList.stream()
.filter(name -> name.endsWith("e"))
.forEach(name -> {
System.out.println("Name : " + name);
});
mapメソッド
変換処理(型変換がメイン?)を差し込みたいときに使うメソッド
「IntStreamのint値を基にDTOを生成する」などに利用できる
class Item {
int id; // アイテムID
String name; // 名称
int price; // 単価
}
List<Item> itemList = new ArrayList<>();
// 通常for文
for(int id=1 ; id<=10 ; id++) {
Item item = new Item();
item.id = id;
item.name = "アイテム" + id;
item.price = id * 100;
itemList.add(item);
}
// Stream (forEach版)
IntStream.rangeClosed(1, 10)
.mapToObj(id -> {
Item item = new Item();
item.id = id;
item.name = "アイテム" + id;
item.price = id * 100;
return item;
})
.forEach(itemList::add);
// Stream (collect版)
itemList = IntStream.rangeClosed(1, 10)
.mapToObj(id -> {
Item item = new Item();
item.id = id;
item.name = "アイテム" + id;
item.price = id * 100;
return item;
})
.collect(Collectors.toList());
peekメソッド
各要素の中身を変更させたいときに使うメソッド.
これ以外にも,途中経過をトレースするときに使える.
collectメソッド
各種中間操作によって得られた結果をListやMapに集めるときに使うメソッド.
StringのStreamだった時,1つのStringに連結させる Collectors.joining()
が用意されている.
class Item {
int id; // アイテムID
String name; // 名称
int price; // 単価
}
// お客さんの買い物かご(key: 商品 / value: 商品数)
Map<Item, Integer> basket = getCustomerBasket();
/* ** 名称ごとに小計を算出 ** */
// for文
Map<String, Integer> subtotalList = new HashMap<>();
for(Map.Entry<Item, Integer> e : basket.entrySet()) {
Item item = e.getKey();
Integer amount = e.getValue();
subtotalList.merge(item.name, item.price * amount, Integer::sum);
}
// Stream (forEach版)
basket.forEach( (item, amount) -> {
subtotalList.merge(item.name, item.price * amount, Integer::sum);
});
// Stream (collect版)
subtotalList = basket.entrySet().stream()
.collect(Collectors.toMap(
/*key=*/ e -> e.getKey().name,
/*value=*/ e -> e.getKey().price * e.getValue(),
/*merge=*/ Integer::sum
));
最後に
気が向いたら追記するかも,です.