CodeGym /コース /JAVA 25 SELF /Stream API における sum、count、average、max、min のメソッド

Stream API における sum、count、average、max、min のメソッド

JAVA 25 SELF
レベル 31 , レッスン 0
使用可能

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. 数値データの合計・平均・最小・最大

プリミティブ・ストリーム: IntStreamDoubleStreamLongStream

数値データに対して Stream API は特別なストリームである IntStreamDoubleStreamLongStream を提供します。これらを使うと、合計・平均・最小・最大を容易かつ高速に計算できます。

数値ストリームへの変換

オブジェクトのストリームから数値ストリームを得るには、mapToIntmapToDoublemapToLong を使います。

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 を食らいます。常に orElseisPresent() を使いましょう!

3. オブジェクトの集約: Collectors.summingIntaveragingIntmaxByminBy

オブジェクトのストリームで、あるプロパティに基づいて集約したい場合は、Collectors クラスの専用コレクターを使います。

summingIntaveragingInt

int totalSalary = employees.stream()
    .collect(Collectors.summingInt(Employee::getSalary));

double avgSalary = employees.stream()
    .collect(Collectors.averagingInt(Employee::getSalary));

minBymaxBy

フィールドの最大/最小値を持つオブジェクトを探すには:

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 と該当メソッド(sumaverageminmax)を使う。
  • オブジェクト自体を集約したい(例: あるフィールドが最大のオブジェクトを見つける)なら、maxBy/minBy のコレクターを使う。
  • グループごとに集約を計算したいなら、groupingBy と集約コレクターの組み合わせを使う。

7. 集約メソッドでの典型的なミス

エラー №1: Optional を処理していない。 初学者はしばしば、minmaxaverageOptional を返すことを忘れます。その結果、空のコンテナから値を取得しようとして NoSuchElementException が発生します。常に orElseorElseThrow、または 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 を返します。このケースを忘れずに処理しましょう。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION