1. 要素のカウント: count()
数値やオブジェクトのコレクションを扱うときは、要素数、合計、平均、最大・最小などを数えたり求めたりする場面がほぼ必ずあります。たとえば、会社の従業員数、給与の合計、学生の平均年齢、最も高価な商品、最大の注文などです。Stream API により、これらはより簡潔かつ表現的に書けるようになりました。
仕組み
メソッド count() は Stream API の終端操作で、ストリーム内の要素数を返します。
long count = employees.stream().count();
条件に合う要素だけを数えたい場合は、filter(...) を使います:
long richCount = employees.stream()
.filter(e -> e.getSalary() > 100_000)
.count();
本アプリの例:
商品リストがあるとします:
List<Product> products = List.of(
new Product("牛乳", 80),
new Product("チーズ", 250),
new Product("パン", 40)
);
価格が 100 より高い商品はいくつあるか数えます:
long expensiveCount = products.stream()
.filter(p -> p.getPrice() > 100)
.count();
System.out.println("高価な商品の数: " + expensiveCount);
Optional とは
Java には特別なコンテナ Optional があります。値を保持するか、空であるかのどちらかを表すラッパーオブジェクトです。結果が存在しない可能性を明示したい場合に使います。たとえば、min()、max()、average() は、コレクションが空のときに空の結果を返す可能性があります。このとき null の代わりに Optional が返されます。
Optional の主なメソッド
- isPresent() / isEmpty() — 値の有無を確認。
- orElse(value) — 値があればそれを、空ならデフォルトを返す。
- orElseThrow() — 空なら例外を投げる。
- ifPresent(...) — 値がある場合のみ処理を実行。
例:
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
if (min.isPresent()) {
System.out.println("最小価格: " + min.getAsInt());
} else {
System.out.println("リストは空です");
}
あるいはもっと簡潔に:
int minValue = min.orElse(-1);
System.out.println("最小価格: " + minValue);
このアプローチにより NullPointerException を避け、コードの安全性が高まります。
2. 数値データの合計・平均・最小・最大
プリミティブ・ストリーム: IntStream、DoubleStream、LongStream
数値データに対して Stream API は特別なストリームである IntStream、DoubleStream、LongStream を提供します。これらを使うと、合計・平均・最小・最大を容易かつ高速に計算できます。
数値ストリームへの変換
オブジェクトのストリームから数値ストリームを得るには、mapToInt、mapToDouble、mapToLong を使います。
int sum = products.stream()
.mapToInt(Product::getPrice)
.sum();
System.out.println("価格の合計: " + sum);
メソッド
- sum() — すべての要素の合計。
- average() — 算術平均(OptionalDouble を返す)。
- min()、max() — 最小と最大(OptionalInt/OptionalDouble/OptionalLong を返す)。
例:
// すべての価格の合計
int total = products.stream()
.mapToInt(Product::getPrice)
.sum();
// 平均価格
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
// 最小価格と最大価格
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
OptionalInt max = products.stream()
.mapToInt(Product::getPrice)
.max();
Optional から値を取得する方法
double average = avg.orElse(0.0); // コレクションが空なら 0.0
int minValue = min.orElse(-1); // コレクションが空なら -1
int maxValue = max.orElse(-1); // コレクションが空なら -1
プログラマーのジョーク: 空のリストはエラーではありません。単にデータがない状況です。しかし空の Optional に対して getAsInt() を呼ぶと、NoSuchElementException を食らいます。常に orElse か isPresent() を使いましょう!
3. オブジェクトの集約: Collectors.summingInt、averagingInt、maxBy、minBy
オブジェクトのストリームで、あるプロパティに基づいて集約したい場合は、Collectors クラスの専用コレクターを使います。
summingInt、averagingInt
int totalSalary = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary));
double avgSalary = employees.stream()
.collect(Collectors.averagingInt(Employee::getSalary));
minBy、maxBy
フィールドの最大/最小値を持つオブジェクトを探すには:
Optional<Employee> richest = employees.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary)));
Optional<Employee> poorest = employees.stream()
.collect(Collectors.minBy(Comparator.comparingInt(Employee::getSalary)));
重要: 結果は Optional です。コレクションが空の場合があるためです。
例
Product クラスを作成します:
public class Product {
private final String name;
private final int price;
public Product(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public int getPrice() { return price; }
}
商品のリストを作成します:
List<Product> products = List.of(
new Product("牛乳", 80),
new Product("チーズ", 250),
new Product("パン", 40)
);
最も高価な商品を見つけます:
Optional<Product> maxProduct = products.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Product::getPrice)));
maxProduct.ifPresent(p -> System.out.println("最も高価な商品: " + p.getName()));
4. グルーピングとの組み合わせ
集約メソッドはグルーピングと組み合わせることがよくあります。たとえば、商品名の最初の文字ごとに平均価格を求められます:
Map<Character, Double> avgPriceByLetter = products.stream()
.collect(Collectors.groupingBy(
p -> p.getName().charAt(0),
Collectors.averagingInt(Product::getPrice)
));
System.out.println(avgPriceByLetter);
結果:
{牛=80.0, チ=250.0, パ=40.0}
5. 実例
例 1: 学生と得点
学生の一覧とそのスコア:
public class Student {
private final String name;
private final int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
}
List<Student> students = List.of(
new Student("アリサ", 90),
new Student("ボブ", 75),
new Student("ヴァーシャ", 100)
);
スコアが 80 を超える学生は何人?
long count = students.stream()
.filter(s -> s.getScore() > 80)
.count();
System.out.println("スコアが 80 を超える学生の数: " + count);
平均スコア:
double avg = students.stream()
.mapToInt(Student::getScore)
.average()
.orElse(0.0);
System.out.println("平均スコア: " + avg);
最優秀の学生:
students.stream()
.max(Comparator.comparingInt(Student::getScore))
.ifPresent(s -> System.out.println("最優秀の学生: " + s.getName()));
例 2: 一意な商品の数
long uniqueCount = products.stream()
.map(Product::getName)
.distinct()
.count();
System.out.println("ユニークな商品の数: " + uniqueCount);
6. 役に立つポイント
ビジュアル図解: 集約メソッドの流れ
オブジェクトのコレクション
│
▼
stream()
│
▼
必要なら mapToInt/Double/Long
│
▼
sum() / average() / min() / max() / count()
│
▼
結果 (int, double, long, Optional)
Optional の扱い: 安全に値を取り出すには
なぜメソッドは Optional を返すのか?
コレクションが空の場合(たとえば、空のリストで最大値を探すなど)、空の Optional が返されます。その状態で getAsInt()/get() を呼ぶと例外が投げられます。
正しいやり方:
- orElse(defaultValue) を使う
- 値がある場合のみ処理するために ifPresent(...) を使う
- 明示的に例外を投げるなら orElseThrow(...) を使う
例:
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
System.out.println("平均価格: " + avg.orElse(0.0));
プリミティブ・ストリームと Collectors の使い分け
- 数値フィールドの合計/平均/最小/最大を単純に求めたいだけなら、mapToInt と該当メソッド(sum、average、min、max)を使う。
- オブジェクト自体を集約したい(例: あるフィールドが最大のオブジェクトを見つける)なら、maxBy/minBy のコレクターを使う。
- グループごとに集約を計算したいなら、groupingBy と集約コレクターの組み合わせを使う。
7. 集約メソッドでの典型的なミス
エラー №1: Optional を処理していない。 初学者はしばしば、min、max、average が Optional を返すことを忘れます。その結果、空のコンテナから値を取得しようとして NoSuchElementException が発生します。常に orElse、orElseThrow、または ifPresent を使いましょう。
エラー №2: 通常の stream を使い、mapToInt/mapToDouble を使っていない。 stream().sum() と書くと、コンパイラは「そのようなメソッドはありません!」と言うでしょう。合計・平均・最小・最大にはプリミティブ・ストリームが必要です。
エラー №3: 比較時の型不一致。 オブジェクトの最大/最小を探すときは、正しいコンパレータを使いましょう: Comparator.comparingInt(Employee::getSalary)。そうしないとコンパイルエラーや不正確な結果を招く可能性があります。
エラー №4: オブジェクトを null と比較。 コレクションに null 要素が含まれる場合、集約時に NullPointerException が投げられる可能性があります。そのような要素は事前にフィルタするか、Comparator.nullsLast(...)/nullsFirst(...) を使いましょう。
エラー №5: 空のコレクションで sum/average/min/max を使う際の結果を考慮していない。 コレクションが空の場合、sum() は 0 を返し、average()/min()/max() は空の Optional を返します。このケースを忘れずに処理しましょう。
GO TO FULL VERSION