この記事はJava学習シリーズ「課題2」です。まだ環境が整っていない方は Java環境構築ガイド を先に読んでください。課題1(パスワードチェッカー)でSpring Bootの基本(@RestController・@PostMapping・@RequestBody)を学んでいることを前提とします。
この記事で作るもの
ユーザーデータ(JSON配列)を受け取り、部門別集計・給与ランキング・全体統計を返すデータ分析REST APIです。
APIの使い方(curl):
curl -X POST http://localhost:8080/api/analyze \
-H "Content-Type: application/json" \
-d '[
{"name":"田中太郎","age":35,"department":"開発部","salary":650000},
{"name":"鈴木一郎","age":42,"department":"開発部","salary":780000},
{"name":"佐藤花子","age":28,"department":"マーケティング部","salary":450000}
]'
レスポンス:
{
"totalCount": 3,
"avgAge": 35.0,
"avgSalary": 626666.7,
"byDepartment": {
"開発部": {"count": 2, "avgSalary": 715000.0},
"マーケティング部": {"count": 1, "avgSalary": 450000.0}
},
"top3": [
{"rank": 1, "name": "鈴木一郎", "department": "開発部", "salary": 780000},
{"rank": 2, "name": "田中太郎", "department": "開発部", "salary": 650000},
{"rank": 3, "name": "佐藤花子", "department": "マーケティング部", "salary": 450000}
]
}
この記事で学べること
- クラス設計(データをオブジェクトとして表現する)
- Stream API(
filter,sorted,groupingBy):リストを変換・集計するJavaの機能 Map(キーと値のペアでデータを管理する)- 例外処理(
@ExceptionHandlerでAPIエラーを適切に返す)
1. 要件定義:何を作るかを整理する
| # | 要件(やること) | 理由 |
|---|---|---|
| 1 | POST /api/analyze でユーザーデータのJSON配列を受け取る | HTTPリクエストでデータを受け付けるWebサービスにするため |
| 2 | 全体統計(総人数・平均年齢・平均給与)を計算する | データの全体像を把握するため |
| 3 | 部門別に集計する(人数・平均給与) | 部門ごとの傾向を比較するため |
| 4 | 給与の高い順にTOP3を返す | ランキング形式で強調表示するため |
| 5 | 空のリストが送られた場合は400エラーを返す | 不正なリクエストを弾くため |
2. プロジェクト作成
start.spring.io でプロジェクトを生成します。
Artifact: data-analyzer
Java: 21
Dependencies:
✓ Spring Web ← HTTPリクエストとJSONの変換に必要
✓ Validation ← リクエストのバリデーションに使う
3. データモデルのクラスを作る(要件1の準備)
APIで受け取るユーザーデータの構造を先に定義します。「1人のユーザー = 1つのJavaオブジェクト」として扱うために、User クラスを作ります。
なぜクラスに分けるのか?
name・age・department・salaryの4つの値がバラバラでは「誰のデータか」がわかりにくくなります。1人分のデータをひとまとめにしたUserクラスを作ることで、user.getName()/user.getSalary()のように明確に扱えます。
src/main/java/com/example/dataanalyzer/ に User.java を作成します。
package com.example.dataanalyzer;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
// リクエストのJSON配列の1要素を表すクラス
public class User {
@NotBlank(message = "名前は必須です")
private String name;
@Min(value = 0, message = "年齢は0以上です")
private int age;
@NotBlank(message = "部門は必須です")
private String department;
@Min(value = 0, message = "給与は0以上です")
private int salary;
// デフォルトコンストラクタ(Jacksonが JSON→オブジェクト変換に必要)
public User() {}
// getter / setter
public String getName() { return name; }
public int getAge() { return age; }
public String getDepartment() { return department; }
public int getSalary() { return salary; }
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }
public void setDepartment(String department) { this.department = department; }
public void setSalary(int salary) { this.salary = salary; }
}
デフォルトコンストラクタが必要な理由 Spring Boot内蔵のJacksonライブラリが
{"name": "田中"}というJSONをUserオブジェクトに変換するとき、「引数なしのコンストラクタ」→「setterで値を設定」という手順を踏みます。デフォルトコンストラクタがないと変換に失敗します。
4. レスポンスのDTOクラスを作る
APIが返すJSONの構造を定義します。record を使うと、getter・コンストラクタ・equals・hashCodeが自動生成されます。
DeptSummary.java(部門ごとの集計結果):
package com.example.dataanalyzer;
public record DeptSummary(int count, double avgSalary) {}
TopUser.java(給与ランキング1件):
package com.example.dataanalyzer;
public record TopUser(int rank, String name, String department, int salary) {}
AnalysisResult.java(APIのレスポンス全体):
package com.example.dataanalyzer;
import java.util.List;
import java.util.Map;
public record AnalysisResult(
int totalCount,
double avgAge,
double avgSalary,
Map<String, DeptSummary> byDepartment,
List<TopUser> top3
) {}
5. Stream APIで集計する(要件2〜4の実装)
Stream APIはリストのデータを変換・絞り込み・集計するJavaの機能です。
Stream API(ストリームAPI)とは? Java 8で追加された、リストや配列のデータを変換・集計するための機能です。JavaScriptの
map()/filter()/reduce()に相当します。「データの流れ(ストリーム)」を途中で加工するイメージです。メソッドチェーン(.でメソッドをつなげて書く)が特徴です。
全体統計の計算(要件2)
平均値を計算するには mapToInt → average とメソッドをつなげます。
// 平均年齢
double avgAge = users.stream()
.mapToInt(User::getAge) // User::getAge は user -> user.getAge() の省略形
.average()
.orElse(0); // データが0件のとき0を返す
// 平均給与(同じ構造)
double avgSalary = users.stream()
.mapToInt(User::getSalary)
.average()
.orElse(0);
部門別集計(要件3)
部門ごとにユーザーをまとめるには Map を使います。
Map(マップ)とは? キーと値のペアでデータを管理するコレクションです。JavaScriptのObject({})に相当します。ここでは「部門名(String)をキー、そこに属するUserリストを値」として使います。
Collectors.groupingBy() を使うと、指定したキーでリストを自動的にグルーピングできます。
import java.util.*;
import java.util.stream.*;
// 部門名をキーとしてUserをグルーピング
Map<String, List<User>> grouped = users.stream()
.collect(Collectors.groupingBy(User::getDepartment));
// 各部門の人数と平均給与を計算
Map<String, DeptSummary> byDepartment = new LinkedHashMap<>();
grouped.forEach((dept, members) -> {
double avg = members.stream()
.mapToInt(User::getSalary)
.average()
.orElse(0);
byDepartment.put(dept, new DeptSummary(members.size(), Math.round(avg * 10.0) / 10.0));
});
給与TOP3の取得(要件4)
sorted() で降順ソートし、limit(3) で上位3件に絞ります。
List<User> sorted = users.stream()
.sorted(Comparator.comparingInt(User::getSalary).reversed())
.limit(3)
.collect(Collectors.toList());
// ランク番号(1位・2位・3位)をつけてTopUserに変換
List<TopUser> top3 = new ArrayList<>();
for (int i = 0; i < sorted.size(); i++) {
User u = sorted.get(i);
top3.add(new TopUser(i + 1, u.getName(), u.getDepartment(), u.getSalary()));
}
6. Controllerを作る(完成版)
これまでの要件1〜5をすべて組み合わせた AnalyzerController.java の完成版です。
package com.example.dataanalyzer;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.stream.*;
@RestController
@RequestMapping("/api")
public class AnalyzerController {
// 要件1:POST /api/analyze でユーザーリストを受け取り、分析結果を返す
@PostMapping("/analyze")
public ResponseEntity<AnalysisResult> analyze(@RequestBody List<@Valid User> users) {
// 要件5:空のリストは400エラー
if (users == null || users.isEmpty()) {
return ResponseEntity.badRequest().build();
}
// 要件2:全体統計
double avgAge = users.stream().mapToInt(User::getAge).average().orElse(0);
double avgSalary = users.stream().mapToInt(User::getSalary).average().orElse(0);
// 要件3:部門別集計
Map<String, List<User>> grouped = users.stream()
.collect(Collectors.groupingBy(User::getDepartment));
Map<String, DeptSummary> byDepartment = new LinkedHashMap<>();
grouped.forEach((dept, members) -> {
double avg = members.stream().mapToInt(User::getSalary).average().orElse(0);
byDepartment.put(dept, new DeptSummary(members.size(), Math.round(avg * 10.0) / 10.0));
});
// 要件4:給与TOP3
List<User> sorted = users.stream()
.sorted(Comparator.comparingInt(User::getSalary).reversed())
.limit(3)
.collect(Collectors.toList());
List<TopUser> top3 = new ArrayList<>();
for (int i = 0; i < sorted.size(); i++) {
User u = sorted.get(i);
top3.add(new TopUser(i + 1, u.getName(), u.getDepartment(), u.getSalary()));
}
return ResponseEntity.ok(new AnalysisResult(
users.size(),
Math.round(avgAge * 10.0) / 10.0,
Math.round(avgSalary * 10.0) / 10.0,
byDepartment,
top3
));
}
}
7. 実行してテストする
起動後、以下のcurlコマンドでテストします。
curl -X POST http://localhost:8080/api/analyze \
-H "Content-Type: application/json" \
-d '[
{"name":"田中太郎","age":35,"department":"開発部","salary":650000},
{"name":"佐藤花子","age":28,"department":"マーケティング部","salary":450000},
{"name":"鈴木一郎","age":42,"department":"開発部","salary":780000},
{"name":"高橋美咲","age":31,"department":"人事部","salary":500000},
{"name":"伊藤次郎","age":29,"department":"開発部","salary":520000},
{"name":"渡辺明","age":45,"department":"マーケティング部","salary":620000},
{"name":"小林由美","age":33,"department":"人事部","salary":480000},
{"name":"加藤健","age":38,"department":"開発部","salary":700000}
]'
エラーケースも確認しましょう:
# 空のリストを送る → 400 Bad Request
curl -X POST http://localhost:8080/api/analyze \
-H "Content-Type: application/json" \
-d '[]'
8. GitHubに公開する
git init
git add .
git commit -m "feat: データ分析REST API(Spring Boot + Stream API)"
git remote add origin https://github.com/YOUR_NAME/data-analyzer.git
git push -u origin main
README.md に記載する内容例:
# データ分析 REST API
Spring Boot で作成したユーザーデータ分析REST API。
Stream APIで部門別集計・給与ランキング・全体統計を返します。
## 技術スタック
- Java 21 / Spring Boot 3.3 / Stream API
## エンドポイント
| メソッド | パス | 説明 |
|--------|------|------|
| POST | /api/analyze | ユーザーリストを受け取り集計結果を返す |
まとめ:学んだこと
| 概念 | 内容 | どこで使ったか |
|---|---|---|
| クラス設計 | データをオブジェクトとして表現する | User クラス |
record | イミュータブルなDTOクラスを簡潔に書く | DeptSummary / TopUser / AnalysisResult |
Stream API | リストの変換・集計・ソートをメソッドチェーンで記述 | 全体統計・TOP3 |
Collectors.groupingBy() | 部門でグルーピング | 部門別集計 |
Map | キーと値のペアでデータ管理 | 部門名→集計結果 |
Comparator | ソート順の指定 | 給与の降順ソート |
ResponseEntity | ステータスコード付きレスポンス | 400エラー返却 |
Javaの学習シリーズ: 環境構築 | 課題1・パスワードチェッカー | 今ここ(課題2・データ分析API) | 課題3・為替変換API | 課題4・Spring Boot Todo API | 課題5・家計簿API
関連記事
CHECK IT OUT
サブスク加入者限定の
コンテンツを配信中
- 毎月のAI・自動化トレンドレポート
- クリエイター向け業務効率化テンプレート
- ツール選定・SaaS比較まとめ(限定公開)
「これ自動化できないかな?」
そのアイデア、ITで実現できます。
業務の自動化・お客様向けツール開発・AI活用まで、クリエイター・個人事業主専門のITコンサルタントが対応します。まずは気軽にご相談ください。