Java Streams: A Beginner's Guide

こんにちは、将来のJavaの魔法使い们!今日は、Java Streamsの世界に興味深く飛び込んでみましょう。プログラミングが初めてであれば心配しないでください。私はあなたの親切なガイドとして、一歩一歩進んでいきます。このチュートリアルの終わりには、プロのようにデータをストリーミングできるようになるでしょう!

Java - Streams

JavaでのStreamとは?

Conveyor belt sushi restaurantを思い浮かべてください。寿司のプレートがあなたの前を絶えず流れていき、あなたは好みのものを選んで食べられます。これがJavaでのStreamの概念です。一時的にメモリに保存することなく、要素を一つずつ処理することができます。

Streamは、Java 8で導入され、コレクションデータをより簡単かつ効率的に操作するためのものです。これにより、データに対する操作をより宣言的な方法で行うことができ、Javaに何をしたいかを伝えるだけで済みます。

JavaでStreamを生成する

まず、最初のStreamを作成してみましょう。いくつかの方法がありますが、簡単な例から始めます:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {
public static void main(String[] args) {
// ListからStreamを作成
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
Stream<String> fruitStream = fruits.stream();

// 直接Streamを作成
Stream<Integer> numberStream = Stream.of(1, 2, 3, 4, 5);
}
}

この例では、2つのStreamを作成しています。まずは、フルーツのListから作成したfruitStream、次に直接作成したnumberStreamです。

一般的なStream操作

Streamを取得したので、それにどのような操作を行えるか見てみましょう。

forEachメソッド

forEachメソッドは、Streamの各要素を処理するロボットのようです。まずはフルーツを印刷してみます:

fruits.stream().forEach(fruit -> System.out.println(fruit));

これは以下のように印刷します:

apple
banana
cherry
date

ここでfruit -> System.out.println(fruit)はラムダ式と呼ばれ、各要素に対して何をすべきかをJavaに短く伝える方法です。

mapメソッド

mapメソッドは、Streamの各要素を変換する魔法の杖のようです。すべてのフルーツを大文字にしてみます:

List<String> upperCaseFruits = fruits.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(upperCaseFruits);

これは以下のように印刷します:

[APPLE, BANANA, CHERRY, DATE]

String::toUpperCaseはメソッド参照と呼ばれ、各Stringに対してtoUpperCaseメソッドを使用するという意味です。

filterメソッド

filterメソッドは、ナイトクラブのボーイのように特定の要素だけを通過させるものです。'A'で始まるフルーツだけを残してみます:

List<String> aFruits = fruits.stream()
.filter(fruit -> fruit.startsWith("a"))
.collect(Collectors.toList());
System.out.println(aFruits);

これは以下のように印刷します:

[apple]

limitメソッド

limitメソッドは「これだけありがとう!」という感じで、Streamを特定の要素数に制限します:

List<String> firstTwoFruits = fruits.stream()
.limit(2)
.collect(Collectors.toList());
System.out.println(firstTwoFruits);

これは以下のように印刷します:

[apple, banana]

sortedメソッド

sortedメソッドは、本棚を並べるようなもので、要素を並べます:

List<String> sortedFruits = fruits.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedFruits);

これは以下のように印刷します:

[apple, banana, cherry, date]

並列処理

Streamの素晴らしい機能の1つは、並列処理が簡単に行えることです。大きなデータセットでの操作を速める可能性があります。並列Streamを作成する方法は以下の通りです:

fruits.parallelStream().forEach(fruit -> System.out.println(fruit + " " + Thread.currentThread().getName()));

これは以下のように印刷される可能性があります:

banana ForkJoinPool.commonPool-worker-1
apple main
date ForkJoinPool.commonPool-worker-2
cherry ForkJoinPool.commonPool-worker-3

順序は実行するたびに異なる場合があります。なぜなら、フルーツは並列に処理されているからです!

Collectors

Collectorsは、Stream操作の結果を集める特別なツールです。私たちは例でcollect(Collectors.toList())を使用して、Streamの結果をListに戻しています。他にも多くの便利なCollectorsがあります:

// 要素を文字列に結合
String fruitString = fruits.stream().collect(Collectors.joining(", "));
System.out.println(fruitString);  // 印刷: apple, banana, cherry, date

// 要素数を数える
long fruitCount = fruits.stream().collect(Collectors.counting());
System.out.println(fruitCount);  // 印刷: 4

// 要素をグループ化
Map<Character, List<String>> fruitGroups = fruits.stream()
.collect(Collectors.groupingBy(fruit -> fruit.charAt(0)));
System.out.println(fruitGroups);  // 印刷: {a=[apple], b=[banana], c=[cherry], d=[date]}

統計

Streamを使用して数値データの統計を計算することもできます:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt(Integer::intValue).summaryStatistics();
System.out.println("Count: " + stats.getCount());
System.out.println("Sum: " + stats.getSum());
System.out.println("Min: " + stats.getMin());
System.out.println("Max: " + stats.getMax());
System.out.println("Average: " + stats.getAverage());

これは以下のように印刷します:

Count: 5
Sum: 15
Min: 1
Max: 5
Average: 3.0

Java Streamsの例

すべてを合わせて、より複雑な例を見てみましょう。年齢と成績を持つ学生のリストを考えてみましょう:

class Student {
String name;
int age;
double grade;

Student(String name, int age, double grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
}

public class StreamExample {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("Alice", 22, 90.5),
new Student("Bob", 20, 85.0),
new Student("Charlie", 21, 92.3),
new Student("David", 23, 88.7)
);

// 21歳以上の学生の平均成績を取得
double averageGrade = students.stream()
.filter(student -> student.age > 21)
.mapToDouble(student -> student.grade)
.average()
.orElse(0.0);

System.out.println("21歳以上の学生の平均成績: " + averageGrade);

// 成績上位2人の学生の名前を取得
List<String> topStudents = students.stream()
.sorted((s1, s2) -> Double.compare(s2.grade, s1.grade))
.limit(2)
.map(student -> student.name)
.collect(Collectors.toList());

System.out.println("成績上位2人の学生: " + topStudents);
}
}

この例では、複数のStream操作を連結してデータを処理する方法を示しています。

そして、ここまでがJava Streamsの初歩です!練習することが大事です。これらの概念を試してみてください。間もなくプロのようにデータをストリーミングできるようになるでしょう!お楽しみに!

Credits: Image by storyset