はじめに
レビューで「〇〇の箇所のfor文はStreamで書いてください」
って指摘が入った!そもそもStreamってなんだ!
StreamはListやMapなどのコレクションで
処理を行う時に記述量を少なくしたり、可読性を上げたりなどの
メリットがあるよ!今回はStreamについて学んでいこう!
Stream APIとは?
Stream APIについてですが、公式では以下のように書かれています。
コレクションに対する写像(map)・簡約(reduce)変換といった、関数型の操作を要素のストリームに対して提供するクラス群。
http://www.tatapa.org/~takuo/lambda_ja/javadoc/java/util/stream/package-summary.html#package.description
Stream APIは配列やList、Mapなどのコレクションを操作するためのもので、値の集計や合計、フィルターなどができる便利なAPIです。Stream APIを使用することで、
- 記述量を少なくできる
- 可読性が向上する
- 性能が向上する
などのメリットを享受することができます!
使いこなせるようになると、「for文」や「if文のネスト」等を減らすことができ、大変便利です。本記事を活用して、是非マスターしてください!
さっそく一例を見ていきます!
以下にfor文での記述をStreamに書き変えたものを掲載しています。
for文による記述
List<Integer> numberList = Arrays.asList(10, 20, 30, 40, 50);
for (Integer num : numberList) {
if (num >= 30) {
System.out.println(num);
}
}
Streamによる記述
List<Integer> numberList = Arrays.asList(10, 20, 30, 40, 50);
numberList.stream().filter(i -> i >= 30).forEach(System.out::println);
この例はどちらも30、40、50を表示します。
for文内で、if文で条件分岐している箇所は、filter(i -> i >= 30)で行っています。
初見でもなんとなく、処理のイメージは湧くのではないでしょうか..??
Streamの使い方
Streamの処理は大きく以下の3つに分かれます。
- Streamの生成
Streamを生成する処理です。ListやMap、配列からStreamを生成します。 - 中間操作
中間操作は生成したStreamに対して行う具体的な処理を指します。
中間操作にはデータの変換や加工を行うメソッドが多数用意されています。
例えば、filterによって要素を絞ったり、sortによて並び替えたり、mapによって型変換を行うことができます。 - 終端操作
終端処理はStream以外の型に変換して、Streamの処理を終了させます。
MapやListに変換したり、グルーピングした結果を返したりします。
中間操作は1つのStream内で何度でも使用できますが、終端操作は1度しか使用できません。また、終端操作は必ず行う必要があり、省略ができません。
Streamの生成
配列やListに対してStream()でStreamを生成することができます。
配列から生成
String[] fruitsArray = { "apple", "grape", "orange" };
Stream<String> fruitsStream = Arrays.stream(fruitsArray);
fruitsStream.forEach(System.out::println);
出力結果は下記になります。
apple
grape
orange
Listから生成
List<String> fruitsList = Arrays.asList("apple", "grape", "orange");
Stream<String> fruitsStream2 = fruitsList.stream();
fruitsStream2.forEach(System.out::println);
出力結果は下記になります。
apple
grape
orange
Mapから生成
MapからStream直接生成するStreamを生成するAPIが用意されていないため、MapのentrySetメソッドを用いて得られるSetからStreamを生成します。
Map<Integer, String> fruitsMap = Map.of(1, "apple", 2, "grape", 3, "orange");
Stream<Entry<Integer, String>> fruitsStream3 = fruitsMap.entrySet().stream();
fruitsStream3.map(f -> f.getKey() + " : " + f.getValue()).sorted().forEach(System.out::println);
出力結果は下記になります。
1 : apple
2 : grape
3 : orange
中間操作
filter
条件を指定することで、Stream内の要素を絞り込むことができます。
filterの引数にはラムダ式でboolean(Predicate)を与えます。
fruitsList.stream().filter(f -> f.startsWith("g")).forEach(System.out::println);
distinct
重複を排除することができます。
fruitsList.stream().distinct().forEach(System.out::println);
sorted
デフォルトだと昇順にソートされます。
fruitsList.stream().sorted().forEach(System.out::println);
引数にComparator.reverseOrder()を指定することで降順にソートできます。
(ラムダで与えるのであればComparator#comparing()を指定した方が可読性が上がる。)
fruitsList.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);
あるクラスのキー、複合キーでソートする場合は下記のように記述できます。
userList.stream().sorted(Comparator.comparing(User::getId).thenComparing(User::getName)).forEach(System.out::println);
map
Stream内で変換処理を記述する際に使用します。
以下の例では、リスト内の数値を全て2倍にしています。
List<Integer> numberList = Arrays.asList(10, 20, 30, 40, 50);
numberList.stream().map(num -> num * 2).forEach(System.out::println);
終端操作
collect
streamを任意の型に変換します。実務でも最も使う処理だと思います。
List<User> userList = Arrays.asList(new User(1, "taro"), new User(2, "jiro"), new User(3, "saburo"));
// idを取り出しlistに変換
System.out.println(userList.stream().map(u -> u.getId()).collect(Collectors.toList()));
// mapに変換
System.out.println(userList.stream().collect(Collectors.toMap(User::getId, User::getName)));
findFirst
findFirst()は初めの要素をOptional型で返します。
userList.stream().findFirst().get().id;
allMatch/anyMatch
allMatchは与えられた関数(Predicate)を条件にStreamの要素を検索し、結果をbooleanで返します。
allMatchは全ての要素で一致する場合にtrue、anyMatchはどれかの要素で一致する場合にtrueを返します。
userList.stream().allMatch(u -> Objects.nonNull(u.getName());
max
最大値を返します。
numberList.stream().max(Comparator.naturalOrder());
min
最小値を返します。
numberList.stream().min(Comparator.naturalOrder());
sum
合計を返します。
numberList.stream().mapToInt(num -> num).sum();
average
平均値を返します。
numberList.stream().mapToInt(num -> num).average();
count
要素数を返します。
numberList.stream().count();
終わりに
本記事はここまでとなります。続きは第2編に記載しておりますので是非、併せて参照ください!
ご覧いただきありがとうございました。ご指摘等がございましたら頂けますと嬉しいです。
引き続き、プログラミングについて定期的に発信していきますのでよろしくお願いします!
また、もしよろしければtwitterもフォローしていただけると嬉しいです!🐢
コメント