【Java】StreamAPIの機能を使いこなす
今回はラムダ式を使用する時に、よく利用するjava.util.streamやjava.util.functionパッケージのAPIの使い方を見ていきます。これらのAPIは開発工数の削減に役立つので是非とも動きを覚えておきたいですね。
StreamAPIの基本的な挙動に関してはこちらで紹介しています。
- 各要素に対し、特定処理を実行するforEach
- 特定要素に絞り込むfilter
- 要素を別の要素に変換するmap
- 要素をさらに細かく分割し変換するflatMap
- 最初の要素を取得するfindFirst
- 要素を取得するfindAny
- 要素の集合同士を連結するconcat
- 要素を並び替えるsorted
- 条件に一致する要素が存在するか判定するanyMatch
- すべての要素が条件に一致するか判定するallMatch
- 条件に一致する要素が存在しないか判定するnoneMatch
- 各要素の処理結果でtrueが返された要素のStreamを返すtakeWhile
- 各要素の処理結果でtrueが返された要素を削除したStreamを返すdropWhile
- 途中で特定処理を実行するpeek
- 要素の結合的累積操作を行えるreduce
- 要素を切り詰めるlimit
- 最初の要素を破棄するskip
- 重複要素を削除するdistinct
- 繰り返しStreamを構築するiterate
- 最大要素を取得するmax
- 最小要素を取得するmin
- 要素の個数を数えるcount
- 要素の合計を取得するsum
- 要素の平均を取得するaverage
- 配列化するtoArray
- List化するCollectors.toList
- Set化するCollectors.toSet
- Map化するCollectors.toMap
- 文字列を連結させるCollectors.joining
- true、falseでMap化するCollectors.partisioningBy
- 特定データでグループ化Mapを作成するCollectors.groupingBy
- close時に特定の処理を実行するonClose
- 使用頻度の高い、覚えておくと良いメソッドは?
事前にテストで使用するGameCharacterクラスを作成し、クラス内にはAPIで利用するためのListを作成するメソッドを定義しておきます。これを利用して動きをみていくことにしましょう。
import java.util.List; //名前、HP、MP、敵か仲間のフラグを持つキャラクタークラス public class GameCharacter { public String name; public int hp; public int mp; public boolean isEnemy; public GameCharacter(String name, int hp, int mp, boolean isEnemy){ this.name = name; this.hp = hp; this.mp = mp; this.isEnemy = isEnemy; } //内容を表示するメソッド public String toString() { return name+":{hp="+hp+",mp="+mp+","+(isEnemy ? "敵" : "仲間")+"}"; } //キャラクターリスト生成メソッド public static List<GameCharacter> createList(){ return List.of( new GameCharacter("勇者",100,50,false) ,new GameCharacter("戦士",150,30,false) ,new GameCharacter("魔王",300,200,true) ,new GameCharacter("格闘家",180,0,false) ,new GameCharacter("スライム",10,0,true) ,new GameCharacter("オーク",60,0,true) ,new GameCharacter("リビングデッド",50,150,true) ,new GameCharacter("ペット",10,10,false) ); } }
紹介する内容では、createListを呼び出してList取得、Stream化してからの操作で実行していく流れになります。基本的にはList.streamで実行していきますが、結果を変えるために並列ストリームを生成するparallelStreamを使用するパターンも含まれます。
※parallelやunorderedを使用すると結果が大きく変化する(説明とは異なる動きをする)メソッドもありますのでご注意ください。
各要素に対し、特定処理を実行するforEach
forEachメソッドを利用すると各要素に対して何らかの処理を実行できます。
次の処理はリスト内のキャラの情報を表示する処理です。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //各要素を表示する charaList.stream().forEach(System.out::println); } }
勇者:{hp=100,mp=50,仲間} 戦士:{hp=150,mp=30,仲間} 魔王:{hp=300,mp=200,敵} 格闘家:{hp=180,mp=0,仲間} スライム:{hp=10,mp=0,敵} オーク:{hp=60,mp=0,敵} リビングデッド:{hp=50,mp=150,敵} ペット:{hp=10,mp=10,仲間}
特定要素に絞り込むfilter
filterメソッドを利用すると要素の検索を実行できます。
次の処理はHP100以上のキャラ一覧を表示し、そのあとに敵キャラ一覧を表示します。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //HP100以上のキャラクターを表示する System.out.println("--------HP100以上--------"); charaList.stream() .filter(e -> e.hp > 100) .forEach(System.out::println); System.out.println("--------HP100以上--------"); System.out.println("--------敵キャラ--------"); //敵キャラクターを表示する charaList.stream() .filter(e ->e.isEnemy) .forEach(System.out::println); System.out.println("--------敵キャラ--------"); } }
--------HP100以上-------- 戦士:{hp=150,mp=30,仲間} 魔王:{hp=300,mp=200,敵} 格闘家:{hp=180,mp=0,仲間} --------HP100以上-------- --------敵キャラ-------- 魔王:{hp=300,mp=200,敵} スライム:{hp=10,mp=0,敵} オーク:{hp=60,mp=0,敵} リビングデッド:{hp=50,mp=150,敵} --------敵キャラ--------
要素を別の要素に変換するmap
mapメソッドを利用すると要素の変換を実行できます。
次の処理はキャラ一覧をキャラの名前一覧に変換してから内容を表示します。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //キャラクターの名前のみ表示する charaList.stream() .map(e -> e.name) .forEach(System.out::println); } }
勇者 戦士 魔王 格闘家 スライム オーク リビングデッド ペット
要素をさらに細かく分割し変換するflatMap
flatMapは特定の要素をさらに細かい要素に分離したい場合に使えます。
import java.util.List; import java.util.stream.Stream; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //キャラクターオブジェクトをname,hp,mpの要素に分離してstream化したものを表示 charaList.stream() .flatMap(e-> Stream.of(e.name,e.hp,e.mp)) .forEach(System.out::println); } }
勇者 100 50 戦士 150 30 魔王 300 200 格闘家 180 0 スライム 10 0 オーク 60 0 リビングデッド 50 150 ペット 10 10
最初の要素を取得するfindFirst
findFirstは最初の要素を取得できます。
import java.util.List; import java.util.Optional; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //最初の要素を取得 Optional<GameCharacter> data = charaList.parallelStream() .findFirst(); System.out.println(data); } }
Optional[勇者:{hp=100,mp=50,仲間}]
要素を取得するfindAny
findAnyはいずれかの要素を取得できます。
import java.util.List; import java.util.Optional; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //要素を取得 Optional<GameCharacter> data = charaList.parallelStream() .findAny(); System.out.println(data); } }
Optional[オーク:{hp=60,mp=0,敵}]
要素の集合同士を連結するconcat
concatメソッドはstream同士を結合できます。
import java.util.List; import java.util.stream.Stream; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //Streamの結合 Stream.concat(charaList.stream(), Stream.of(new GameCharacter("新人",1,1,false))) .forEach(System.out::println); } }
勇者:{hp=100,mp=50,仲間} 戦士:{hp=150,mp=30,仲間} 魔王:{hp=300,mp=200,敵} 格闘家:{hp=180,mp=0,仲間} スライム:{hp=10,mp=0,敵} オーク:{hp=60,mp=0,敵} リビングデッド:{hp=50,mp=150,敵} ペット:{hp=10,mp=10,仲間} 新人:{hp=1,mp=1,仲間}
要素を並び替えるsorted
要素の並び替えを行う場合はsortedを使用します。
サンプルプログラムはHPが大きい順に並べ替えたときの結果になります。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //HPの降順に並び替えて表示 charaList.stream() .sorted((c1,c2) -> - (c1.hp - c2.hp)) .forEach(System.out::println); } }
魔王:{hp=300,mp=200,敵} 格闘家:{hp=180,mp=0,仲間} 戦士:{hp=150,mp=30,仲間} 勇者:{hp=100,mp=50,仲間} オーク:{hp=60,mp=0,敵} リビングデッド:{hp=50,mp=150,敵} スライム:{hp=10,mp=0,敵} ペット:{hp=10,mp=10,仲間}
条件に一致する要素が存在するか判定するanyMatch
anyMatchは条件に一致する要素が存在する場合にtrueを返します。
また、要素がない場合はfalseを返します。
import java.util.List; import java.util.stream.Stream; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //mp100以上はいるか boolean isMp100 = charaList.stream() .anyMatch(e -> e.mp >= 100); System.out.println("mp100以上はいるか:"+isMp100); //mp0未満はいるか boolean isMpMinus = charaList.stream() .anyMatch(e -> e.mp < 0); System.out.println("mp0未満はいるか:"+isMpMinus); //空の場合 System.out.println("要素が空の場合:"+Stream.<GameCharacter>empty().anyMatch(e -> e.isEnemy)); } }
mp100以上はいるか:true mp0未満はいるか:false 要素が空の場合:false
すべての要素が条件に一致するか判定するallMatch
allMatchはすべての要素が条件に一致する場合にtrueを返します。
また、要素がない場合はtrueを返します。
import java.util.List; import java.util.stream.Stream; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //全員mp100以上か boolean isMp100 = charaList.stream() .allMatch(e -> e.mp >= 100); System.out.println("全員mp100以上か:"+isMp100); //全員hp10以上か boolean isHp10 = charaList.stream() .allMatch(e -> e.hp >= 10); System.out.println("全員hp10以上か:"+isHp10); //空の場合 System.out.println("要素が空の場合:"+Stream.<GameCharacter>empty().allMatch(e -> e.isEnemy)); } }
全員mp100以上か:false 全員hp10以上か:true 要素が空の場合:true
条件に一致する要素が存在しないか判定するnoneMatch
noneMatchは条件に一致する要素が存在しない場合にtrueを返します。
また、要素がない場合はtrueを返します。
import java.util.List; import java.util.stream.Stream; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //mp300以上が存在しないか boolean isMp300 = charaList.stream() .noneMatch(e -> e.mp >= 300); System.out.println("mp300以上が存在しないか:"+isMp300); //hp10以上が存在しないか boolean isHp10 = charaList.stream() .noneMatch(e -> e.hp >= 10); System.out.println("hp10以上が存在しないか:"+isHp10); //空の場合 System.out.println("要素が空の場合:"+Stream.<GameCharacter>empty().noneMatch(e -> e.isEnemy)); } }
mp300以上が存在しないか:true hp10以上が存在しないか:false 要素が空の場合:true
各要素の処理結果でtrueが返された要素のStreamを返すtakeWhile
通常の順次ストリームであれば最初の要素から順に処理し、Predicateの結果がtrueのデータを含むStreamを返します。途中でfalseとなった場合以降の要素はすべて削除されます。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //最初のfalseが見つかるまでの要素を表示 charaList.stream() .takeWhile(e -> e.mp < 100) .forEach(e -> System.out.println(e.name)); } }
勇者 戦士
要素の順はこんな感じでしたので魔王(MP200)の処理がfalseを返し魔王以降のデータが削除された結果になったようです。
new GameCharacter("勇者",100,50,false) ,new GameCharacter("戦士",150,30,false) ,new GameCharacter("魔王",300,200,true) ,new GameCharacter("格闘家",180,0,false) ,new GameCharacter("スライム",10,0,true) ,new GameCharacter("オーク",60,0,true) ,new GameCharacter("リビングデッド",50,150,true) ,new GameCharacter("ペット",10,10,false)
各要素の処理結果でtrueが返された要素を削除したStreamを返すdropWhile
通常の順次ストリームであれば最初の要素から順に処理し、Predicateの結果がtrueのデータを削除したStreamを返します。途中でfalseとなった場合以降の要素の削除は行いません。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //最初のfalseが見つかった以降の要素を表示 charaList.stream() .dropWhile(e -> e.mp < 100) .forEach(e -> System.out.println(e.name)); } }
魔王 格闘家 スライム オーク リビングデッド ペット
要素の順はこんな感じでしたので魔王(MP200)の処理がfalseを返し、勇者と戦士だけが削除された結果になりました。
new GameCharacter("勇者",100,50,false) ,new GameCharacter("戦士",150,30,false) ,new GameCharacter("魔王",300,200,true) ,new GameCharacter("格闘家",180,0,false) ,new GameCharacter("スライム",10,0,true) ,new GameCharacter("オーク",60,0,true) ,new GameCharacter("リビングデッド",50,150,true) ,new GameCharacter("ペット",10,10,false)
途中で特定処理を実行するpeek
peekは操作の途中で処理を実行することができます。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //途中で処理をねじ込む long cnt = charaList.stream() .peek(System.out::println) .count(); System.out.println(cnt); } }
8
あれ?内容が表示されない・・・
どうやらStreamの最適化にはまったようです。
countは処理を実行しなくても数の確定が可能である場合は中間処理を行わないようですね。
最適化がされないように少しコードを改変しましょうか。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //途中で処理をねじ込む long cnt = charaList.stream() .filter(e -> !e.isEnemy) .peek(System.out::println) .count(); System.out.println(cnt); } }
勇者:{hp=100,mp=50,仲間} 戦士:{hp=150,mp=30,仲間} 格闘家:{hp=180,mp=0,仲間} ペット:{hp=10,mp=10,仲間} 4
なぜこのような処理になったのかはStreamAPIの挙動についての記事をご覧ください。
要素の結合的累積操作を行えるreduce
reduceは各要素の連結処理や比較処理を行った結果を取得できます。
import java.util.List; import java.util.Optional; import java.util.function.BinaryOperator; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //文字列結合処理を行い表示 Optional<String> names = charaList.stream() .map(e -> e.name) .reduce(String::concat); System.out.println("--------名前連結--------"); System.out.println(names); System.out.println("--------名前連結--------"); //比較処理を行い表示 Optional<GameCharacter> result = charaList.stream() .reduce(BinaryOperator.maxBy((e1,e2)->e1.hp - e2.hp)); System.out.println("--------最大HPのキャラ--------"); System.out.println(result); System.out.println("--------最大HPのキャラ--------"); } }
--------名前連結-------- Optional[勇者戦士魔王格闘家スライムオークリビングデッドペット] --------名前連結-------- --------最大HPのキャラ-------- Optional[魔王:{hp=300,mp=200,敵}] --------最大HPのキャラ--------
要素を切り詰めるlimit
limitは指定した数だけの最初の要素数のみの状態にします。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //最初の3要素までにする charaList.stream() .limit(3) .forEach(System.out::println); } }
勇者:{hp=100,mp=50,仲間} 戦士:{hp=150,mp=30,仲間} 魔王:{hp=300,mp=200,敵}
最初の要素を破棄するskip
skipは指定された数だけ最初の要素から削除します。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //最初の3要素スキップ charaList.stream() .skip(3) .forEach(System.out::println); } }
格闘家:{hp=180,mp=0,仲間} スライム:{hp=10,mp=0,敵} オーク:{hp=60,mp=0,敵} リビングデッド:{hp=50,mp=150,敵} ペット:{hp=10,mp=10,仲間}
重複要素を削除するdistinct
distinctは重複データを排除します。
同じ要素かの判定はequalsとhashCodeで行われるので、必要であれば各種メソッドのオーバーライドで判定を定義しておきましょう。
GameCharacterクラスは、これらのメソッドをオーバーライドしていないので、全く同じ参照を要素に追加して試したいと思います。
import java.util.List; public class Test { public static void main(String[] args) { GameCharacter chara1 = new GameCharacter("勇者", 10,10, false); GameCharacter chara2 = new GameCharacter("魔王", 10,10, true); List<GameCharacter> disCharas = List.of(chara1,chara2,chara2,chara2); //重複データを表示 System.out.println("--------------重複データ--------------"); disCharas.stream().forEach(System.out::println); System.out.println("--------------重複データ--------------"); //distinct実行 System.out.println("--------------distinct実行--------------"); disCharas.stream() .distinct() .forEach(System.out::println); System.out.println("--------------distinct実行--------------"); } }
--------------重複データ-------------- 勇者:{hp=10,mp=10,仲間} 魔王:{hp=10,mp=10,敵} 魔王:{hp=10,mp=10,敵} 魔王:{hp=10,mp=10,敵} --------------重複データ-------------- --------------distinct実行-------------- 勇者:{hp=10,mp=10,仲間} 魔王:{hp=10,mp=10,敵} --------------重複データ--------------
equals、hashCodeが定義されているようなクラス(StringやInteger型など)の場合はしっかり値での判定になっていますのでご安心ください。
試しにString型で参照ではなく値で重複排除されるか見てみましょう。
import java.util.List; public class Test { public static void main(String[] args) { List<String> disCharas = List.of(new String("a"),new String("b"),new String("a")); //重複データを表示 System.out.println("--------------重複データ--------------"); disCharas.stream().forEach(System.out::println); System.out.println("--------------重複データ--------------"); //distinct実行 System.out.println("--------------distinct実行--------------"); disCharas.stream() .distinct() .forEach(System.out::println); System.out.println("--------------distinct実行--------------"); } }
--------------重複データ-------------- a b a --------------重複データ-------------- --------------distinct実行-------------- a b --------------distinct実行--------------
繰り返しStreamを構築するiterate
iterateを使用すると無限ループを行うStreamや条件指定でループを終了する時のような挙動をするStreamを作成できます。
まずは無限ループのStreamのサンプルを見てみましょう。
import java.util.stream.Stream; public class Test { public static void main(String[] args) { //無限ストリーム Stream.iterate(0,i->i+1) .forEach(i -> { try {Thread.sleep(100);} catch(Exception ex) {} System.out.println(i); }); } }
0 1 2 3 4 ...
※無限ループなので途中で強制停止してください。
無限ストリームはtakeWhileやlimitで止めることができます。
import java.util.List; import java.util.stream.Stream; public class Test { public static void main(String[] args) { System.out.println("--------limitで止める--------"); //limitによって止める Stream.iterate(0, i -> i + 1) .limit(3) .forEach(System.out::println); System.out.println("--------limitで止める--------"); System.out.println("--------takeWhileで止める--------"); List<GameCharacter> charaList = GameCharacter.createList(); //takeWhileによって止める Stream.iterate(0, i -> i + 1) .takeWhile(i -> i < 2) .peek(e-> System.out.print(e+"::")) .map(i -> charaList.get(i).name) .forEach(System.out::println); System.out.println("--------takeWhileで止める--------"); } }
--------limitで止める-------- 0 1 2 --------limitで止める-------- --------takeWhileで止める-------- 0::勇者 1::戦士 --------takeWhileで止める--------
条件によって終了させるようにiterateメソッドで指定することもできます。
import java.util.List; import java.util.stream.Stream; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //hasNextによって処理を続行するか判定する有限ストリーム Stream.iterate(0, i -> i < charaList.size(), i -> i + 1) .map(i -> charaList.get(i).name) .forEach(System.out::println); } }
勇者 戦士 魔王 格闘家 スライム オーク リビングデッド ペット
なんかfor文みたいに使えちゃいます。
最大要素を取得するmax
maxを利用すると何かしらの最大要素を取得できます。何を最大として判定するかはComparatorで実装します。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //一番文字数の長いキャラを表示 charaList.stream() .max((c1,c2)->c1.name.length() - c2.name.length()) .ifPresent(System.out::println); } }
リビングデッド:{hp=50,mp=150,敵}
最小要素を取得するmin
minを利用すると何かしらの最小要素を取得できます。何を最小として判定するかはComparatorで実装します。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //一番文字数の短いキャラを表示 charaList.stream() .min((c1,c2)->c1.name.length() - c2.name.length()) .ifPresent(System.out::println); } }
勇者:{hp=100,mp=50,仲間}
要素の個数を数えるcount
countは要素数を取得します。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //要素の個数表示 long cnt = charaList.stream() .count(); System.out.println(cnt); } }
8
要素の合計を取得するsum
mapTo系メソッドで数値ストリームに変換するとsumメソッドが使用できます。
sumメソッドは値の合計を返します。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //全キャラのHPの合計を表示 long sum = charaList.stream() .mapToInt(e -> e.hp) .sum(); System.out.println(sum); } }
860
要素の平均を取得するaverage
mapTo系メソッドで数値ストリームに変換するとaverageメソッドが使用できます。
averageメソッドは値の平均を返します。
import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //全キャラのHPの平均を表示 charaList.stream() .mapToInt(e -> e.hp) .average() .ifPresentOrElse(System.out::println, () -> System.out.println("平均演算を実行できない")); } }
107.5
配列化するtoArray
toArrayを利用してStreamを配列化します。
import java.util.Arrays; import java.util.List; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //キャラクターの名前のみを配列化する String[] names = charaList.stream() .map(e -> e.name) .toArray(String[]::new); //配列内の名前を表示する System.out.println(Arrays.toString(names)); } }
[勇者, 戦士, 魔王, 格闘家, スライム, オーク, リビングデッド, ペット]
List化するCollectors.toList
collectメソッドを使用してCollectors.toListを使用するとStreamをList化できます。
import java.util.List; import java.util.stream.Collectors; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //Listに変換 List<GameCharacter> data = charaList.stream() .collect(Collectors.toList()); //List内のキャラを表示する System.out.println(data); } }
[勇者:{hp=100,mp=50,仲間}, 戦士:{hp=150,mp=30,仲間}, 魔王:{hp=300,mp=200,敵}, 格闘家:{hp=180,mp=0,仲間}, スライム:{hp=10,mp=0,敵}, オーク:{hp=60,mp=0,敵}, リビングデッド:{hp=50,mp=150,敵}, ペット:{hp=10,mp=10,仲間}]
Set化するCollectors.toSet
collectメソッドを使用してCollectors.toSetを使用するとStreamをSet化できます。
import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //Setをに変換 Set<GameCharacter> data = charaList.stream() .collect(Collectors.toSet()); //Set内のキャラを表示する System.out.println(data); } }
[魔王:{hp=300,mp=200,敵}, 戦士:{hp=150,mp=30,仲間}, ペット:{hp=10,mp=10,仲間}, 勇者:{hp=100,mp=50,仲間}, リビングデッド:{hp=50,mp=150,敵}, 格闘家:{hp=180,mp=0,仲間}, スライム:{hp=10,mp=0,敵}, オーク:{hp=60,mp=0,敵}]
Map化するCollectors.toMap
collectメソッドを使用してCollectors.toMapを使用するとStreamをMap化できます。
import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //Mapに変換 Map<String, Integer> data = charaList.stream() .collect(Collectors.toMap(e -> e.name, e -> e.mp)); //名前=MPのマップ情報を表示する System.out.println("-----------各キャラのMP-----------"); data.forEach((k,v) -> System.out.println(k+"="+v)); System.out.println("-----------各キャラのMP-----------"); } }
-----------各キャラのMP----------- オーク=0 ペット=10 格闘家=0 勇者=50 魔王=200 リビングデッド=150 戦士=30 スライム=0 -----------各キャラのMP-----------
文字列を連結させるCollectors.joining
collectメソッドを使用してCollectors.joiningを使用すると文字列Streamを結合できます。
import java.util.List; import java.util.stream.Collectors; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //文字列をスラッシュ区切りで結合 String names = charaList.stream() .map(e -> e.name) .collect(Collectors.joining("/")); //スラッシュ区切りの名前を表示する System.out.println(names); } }
勇者/戦士/魔王/格闘家/スライム/オーク/リビングデッド/ペット
true、falseでMap化するCollectors.partisioningBy
collectメソッドとCollectors.partisioningByを利用することでtrueかfalseかのオブジェクトに分割したMapを生成できます。
import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //敵か仲間かで分割したMapを作成 Map<Boolean, List<GameCharacter>> partitioningMap = charaList.stream() .collect(Collectors.partitioningBy(e -> e.isEnemy)); //敵か仲間でそれぞれ名前を表示する System.out.println("-----------仲間-----------"); partitioningMap.get(false).stream().forEach(e -> System.out.println(e.name)); System.out.println("-----------仲間-----------"); System.out.println("-----------敵-----------"); partitioningMap.get(true).stream().forEach(e -> System.out.println(e.name)); System.out.println("-----------敵-----------"); } }
-----------仲間----------- 勇者 戦士 格闘家 ペット -----------仲間----------- -----------敵----------- 魔王 スライム オーク リビングデッド -----------敵-----------
特定データでグループ化Mapを作成するCollectors.groupingBy
collectメソッドとCollectors.groupingByを利用することで特定データをキーにグループ化したMapを作成できます。
import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //HP200以上、100以上、100未満で分割したMapを作成 Map<String, List<GameCharacter>> groupingMap = charaList.stream() .collect(Collectors.groupingBy(e -> { if ( e.hp >= 200 ) { return "HP200以上"; } else if ( e.hp >= 100 ){ return "HP100以上"; } else { return "HP100未満"; } })); //グループごとの情報を表示する groupingMap.forEach((k,v)->System.out.println(k+"="+v)); } }
HP100未満=[スライム:{hp=10,mp=0,敵}, オーク:{hp=60,mp=0,敵}, リビングデッド:{hp=50,mp=150,敵}, ペット:{hp=10,mp=10,仲間}] HP100以上=[勇者:{hp=100,mp=50,仲間}, 戦士:{hp=150,mp=30,仲間}, 格闘家:{hp=180,mp=0,仲間}] HP200以上=[魔王:{hp=300,mp=200,敵}]
さらに高度なグループ化を行った入れ子Mapの生成も有効利用できそうなため紹介しておきます。
import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //敵、仲間でグループ化したものをさらにHP100以上、100未満でグループ化したMapを作成 Map<String, Map<String, List<GameCharacter>>> groupingMap = charaList.stream() .collect( Collectors.groupingBy(e -> e.isEnemy ? "敵" : "仲間" , Collectors.groupingBy(e -> e.hp >= 100 ? "HP100以上" : "HP100未満") ) ); //グループごとの情報を表示する groupingMap.forEach((k,v)->v.forEach((k2,v2)->System.out.println(k+"で"+k2+"="+v2))); } }
敵でHP100未満=[スライム:{hp=10,mp=0,敵}, オーク:{hp=60,mp=0,敵}, リビングデッド:{hp=50,mp=150,敵}] 敵でHP100以上=[魔王:{hp=300,mp=200,敵}] 仲間でHP100未満=[ペット:{hp=10,mp=10,仲間}] 仲間でHP100以上=[勇者:{hp=100,mp=50,仲間}, 戦士:{hp=150,mp=30,仲間}, 格闘家:{hp=180,mp=0,仲間}]
groupingByの第二引数はCollector型を指定することができ、Collectorsのfiltering、countingなど多様な処理をするCollectorオブジェクトを引数にして処理ができます。
Collector型を指定できる機能は他にもいくつか用意されており、複雑な処理を行う場合に覚えておくと役に立つかもしれません。
close時に特定の処理を実行するonClose
streamにはcloseメソッドが存在します。closeを呼び出すと、onCloseで指定した処理が呼び出される仕組みになっています。
import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class Test { public static void main(String[] args) { List<GameCharacter> charaList = GameCharacter.createList(); //クローズ実行時にENDと表示 try(Stream<GameCharacter> stream = charaList.parallelStream()){ System.out.println( stream .onClose(()->System.out.println("END")) .map(e -> e.name) .collect(Collectors.joining(",")) ); } } }
勇者,戦士,魔王,格闘家,スライム,オーク,リビングデッド,ペット END
closeがあるということはStreamを使用したら解放しなきゃいけないのかと思われてしまうかもしれませんが、この記事で紹介したような処理では実行する必要はありません。
ただしIO系の処理の時はリソース開放のために実行が必要です。(Files.linesなど)
使用頻度の高い、覚えておくと良いメソッドは?
まずは、forEach、filter、map、anyMatch、allMatch、toArrayだけ覚えとけば良いかと思います。
個人的によく使うのは上記に加えて、Collectors.toList、Collectors.groupingByなので、開発するアプリケーションにもよるでしょうが、この辺も覚えておくと役に立つかもしれません。
関連記事
ディスカッション
コメント一覧
まだ、コメントがありません