「平均算出」は“バラバラな値を代表する一つの数字”を作る技
平均は、たくさんの値を「代表する一つの数字」にまとめるための道具です。
売上の平均金額、テストの平均点、作業時間の平均、単価の平均…。
業務では「なんとなく平均を出す」のではなく、
空データ・小数点・丸め・null の扱いなどをきちんと決めたうえで、
毎回同じルールで計算できるようにしておくことがとても大事です。
基本形:数値 List の平均を出す
Stream を使った最小限の書き方
まずは、List<Integer> の平均を出す一番シンプルな形から見てみます。
import java.util.List;
public class AverageBasic {
public static void main(String[] args) {
List<Integer> scores = List.of(70, 80, 90);
double avg = scores.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0); // 空なら 0.0 とする
System.out.println(avg); // 80.0
}
}
Javaここで重要なのは二つです。
一つ目は、mapToInt で「int 専用ストリーム」に変換してから average() を呼んでいることです。average() は OptionalDouble を返すので、「値がない(空)」という状態を表現できます。
二つ目は、orElse(0.0) で「空だったときの平均値」を決めていることです。
ここをどうするか(0.0 にするのか、NaN にするのか、例外にするのか)は、
プロジェクト全体でルールを決めておくとブレません。
平均算出をユーティリティメソッドにまとめる
「null や空 List の扱い」を一箇所に閉じ込める
同じような平均計算を何度も書くなら、
ユーティリティにしてしまうとコードがかなりスッキリします。
import java.util.List;
public final class Averages {
private Averages() {}
public static double averageInt(List<Integer> source) {
if (source == null || source.isEmpty()) {
return 0.0;
}
return source.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0);
}
}
Java使い方はこうなります。
List<Integer> scores = List.of(70, 80, 90);
double avg = Averages.averageInt(scores);
System.out.println(avg); // 80.0
Javaここでの重要ポイントは、「null と空 List の扱いをユーティリティ側で決めている」ことです。
呼び出し側は「平均が欲しい」とだけ考えればよく、
毎回 null チェックや OptionalDouble の扱いを書かなくて済みます。
オブジェクト一覧から「特定の項目の平均」を出す
例:商品一覧から「価格の平均」を出す
業務では、単なる List<Integer> よりも、
「オブジェクトの特定のフィールドの平均」が欲しいことがほとんどです。
class Item {
private final String name;
private final int price;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public int getPrice() { return price; }
}
Javaこれを平均するユーティリティは、こう書けます。
import java.util.List;
import java.util.function.ToIntFunction;
public final class Averages {
private Averages() {}
public static <T> double averageInt(
List<T> source,
ToIntFunction<? super T> mapper
) {
if (source == null || source.isEmpty()) {
return 0.0;
}
return source.stream()
.mapToInt(mapper)
.average()
.orElse(0.0);
}
}
Java使い方はこうです。
List<Item> items = List.of(
new Item("A", 100),
new Item("B", 200),
new Item("C", 300)
);
double avgPrice = Averages.averageInt(items, Item::getPrice);
System.out.println(avgPrice); // 200.0
Javaここで深掘りしたい重要ポイントは三つです。
一つ目は、「ToIntFunction<? super T> mapper が“どの項目を平均するか”を表している」ことです。Item::getPrice を渡すことで、「価格の平均が欲しい」という意図が明確になります。
二つ目は、「ユーティリティ側は“どう平均を計算するか”だけを知っていて、“何を平均するか”は呼び出し側が決める」構造になっていることです。
これにより、同じメソッドで「価格」「数量」「スコア」など、何でも平均を取れます。
三つ目は、「空や null の扱いを一箇所に閉じ込めている」ことです。
プロジェクト全体で「空なら 0.0」というルールを統一できます。
グルーピングと組み合わせた「グループごとの平均」
例:カテゴリごとの平均価格
平均算出が本領を発揮するのは、「グルーピング」と組み合わせたときです。
例えば、商品を「カテゴリごとの平均価格」にしたい場合。
class Item {
private final String category;
private final int price;
public Item(String category, int price) {
this.category = category;
this.price = price;
}
public String getCategory() { return category; }
public int getPrice() { return price; }
}
JavaStream だけで書くとこうなります。
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
List<Item> items = List.of(
new Item("食品", 100),
new Item("食品", 300),
new Item("雑貨", 400)
);
Map<String, Double> avgByCategory =
items.stream()
.collect(Collectors.groupingBy(
Item::getCategory,
Collectors.averagingInt(Item::getPrice)
));
System.out.println(avgByCategory); // {食品=200.0, 雑貨=400.0}
Javaここでの重要ポイントは、
「groupingBy(カテゴリ, averagingInt(価格)) という一行が、
“カテゴリごとに価格の平均を出す”という業務仕様をそのまま表している」ことです。
「まず Map に詰めてから for で回して合計と件数を…」という手作業より、
はるかに読みやすく、バグも入りにくくなります。
小数点と丸めの扱いをどうするか
表示用には BigDecimal や丸めルールを意識する
average() や averagingInt() は double を返します。
内部的には「合計 ÷ 件数」をしているだけなので、
表示するときに「小数第何位まで出すか」「四捨五入か切り捨てか」を決める必要があります。
例えば、「小数第2位で四捨五入して表示したい」場合。
double avg = Averages.averageInt(items, Item::getPrice);
double rounded = Math.round(avg * 100.0) / 100.0;
System.out.println(rounded);
Java金額などで厳密な丸めが必要な場合は、BigDecimal を使うこともあります。
import java.math.BigDecimal;
import java.math.RoundingMode;
BigDecimal avgPrice =
BigDecimal.valueOf(avg)
.setScale(2, RoundingMode.HALF_UP);
Javaここでの重要ポイントは、
「平均そのもの(double)と、“どう表示するか(丸めルール)”は別物として考える」ことです。
ユーティリティは「正しい平均値」を返すところまでにしておき、
丸めやフォーマットは表示層で行う、という分離を意識すると設計がきれいになります。
null を含むデータの平均算出
「null を 0 とみなすか」「そもそも除外するか」を決める
現実のデータには null が混ざります。
平均算出で大事なのは、「null をどう扱うか」を決めることです。
例えば、「null は 0 とみなして平均を出す」なら、こう書けます。
public static double averageIntTreatNullAsZero(List<Integer> source) {
if (source == null || source.isEmpty()) {
return 0.0;
}
return source.stream()
.mapToInt(v -> v == null ? 0 : v)
.average()
.orElse(0.0);
}
Java逆に、「null は集計対象から除外する」なら、こうです。
public static double averageIntIgnoreNull(List<Integer> source) {
if (source == null || source.isEmpty()) {
return 0.0;
}
return source.stream()
.filter(v -> v != null)
.mapToInt(Integer::intValue)
.average()
.orElse(0.0);
}
Javaここでの重要ポイントは、
「null の扱いも“平均算出のルール”の一部」だと意識することです。
ユーティリティに閉じ込めておけば、
呼び出し側は「このメソッドは null をどう扱うのか」を意識せずに済みます。
まとめ:平均算出ユーティリティで身につけてほしい感覚
平均算出は、
単に「合計 ÷ 件数をするテクニック」ではなく、
空・null・小数点・丸め・グルーピングなどを含めて、
「一覧から“代表値”を一貫したルールで取り出すための設計」です。
数値 List なら mapToInt(...).average().orElse(0.0) の形を基本として覚える。
オブジェクト一覧では、「どの項目を平均するか」を関数(Item::getPrice など)で渡す汎用ユーティリティにする。
グルーピングと組み合わせて、「カテゴリごとの平均」「ステータスごとの平均」を一行で書けるようにする。
null や空コレクションの扱いをユーティリティ側で統一し、呼び出し側の思考負荷を減らす。
丸めや表示形式は別レイヤーの責任にして、「平均値そのもの」と切り分けて考える。
あなたのコードのどこかに、
毎回「合計して件数で割る」を手書きしている箇所があれば、
それを一度「平均ユーティリティ+Stream」に置き換えられないか眺めてみてください。
その小さな整理が、
「一覧から“意味のある代表値”を迷いなく取り出せるエンジニア」への、
確かな一歩になります。
