Java 8のStream APIは強力だが、覚えるべきメソッドが多く、チェーンが長くなると可読性が落ちる。そこで、SparkやPandasのDataFrameライクな操作をJVM上で実現する軽量ライブラリ「JDFrame」を紹介する。SQLに近い直感的なAPIで、集計・フィルタ・ソート・結合を数行で記述できる。
導入
<dependency>
<groupId>io.github.burukeyou</groupId>
<artifactId>jdframe</artifactId>
<version>0.1.7</version>
</dependency>
動作イメージ
以下のような学生リストを考える。
class Pupil {
int id;
String name;
String campus;
String grade;
Integer age;
BigDecimal point;
}
「年齢が9~16歳でポイントが存在する生徒をキャンパス別に合計し、上位2キャンパスを抽出」という処理は、
SDFrame<FI2<String, BigDecimal>> summary =
SDFrame.read(pupils)
.whereNotNull(Pupil::getAge)
.whereBetween(Pupil::getAge, 9, 16)
.groupBySum(Pupil::getCampus, Pupil::getPoint)
.sortDesc(FI2::getC2)
.head(2);
summary.show();
出力例
c1 c2 三中 10 二中 7
主なAPI一覧
行列情報
show(n):先頭n行をテーブル形式で表示columns():列名一覧を取得head()/tail():先頭/末尾の要素を取得
フィルタリング
SDFrame.read(pupils)
.whereBetween(Pupil::getAge, 3, 6) // 閉区間
.whereBetweenR(Pupil::getAge, 3, 6) // 左開区間
.whereNotNull(Pupil::getName) // null/空文字除外
.whereIn(Pupil::getAge, List.of(3,7,8))
.whereLike(Pupil::getName, "a"); // %a%
集約
JDFrame<Pupil> df = JDFrame.from(pupils);
Pupil eldest = df.max(Pupil::getAge); // 最大レコード
BigDecimal avg = df.avg(Pupil::getAge); // 平均年齢
MaxMin<Pupil> range = df.maxMin(Pupil::getAge);
重複除去
SDFrame.read(pupils)
.distinct(Pupil::getCampus) // キャンパス名で重複除去
.distinct(p -> p.getCampus() + p.getGrade());
グループ化集計
// select campus, sum(point) ...
List<FI2<String, BigDecimal>> sumByCampus =
df.groupBySum(Pupil::getCampus, Pupil::getPoint).toLists();
// select campus, grade, avg(age) ...
List<FI3<String, String, BigDecimal>> avgByCampusGrade =
df.groupByAvg(Pupil::getCampus, Pupil::getGrade, Pupil::getAge).toLists();
ソート
SDFrame.read(pupils)
.sortDesc(Pupil::getAge) // age DESC
.sortAsc(Pupil::getGrade); // grade ASC
結合
SDFrame<Pupil> left = SDFrame.read(pupils);
SDFrame<Reward> right = SDFrame.read(rewards);
SDFrame<Joined>> result = left.join(
right,
(l, r) -> l.getId() == r.getPupilId(), // ON条件
(l, r) -> new Joined(l, r.getReward()) // 射影
);
便利ユーティリティ
mapPercent:値をパーセント変換partition(n):n件ずつに分割addSortNoCol:連番を付与addRankingSameColDesc:同値同順のランキング付与replenish:不足ディメンションを埋める
補完例:欠損月の自動追加
List<String> allMonths = List.of("2023-01","2023-02","2023-03");
SDFrame.read(monthlySales)
.replenish(Sale::getMonth, allMonths,
m -> new Sale(m, BigDecimal.ZERO));
JDFrameは、冗長なStream記述をSQLライクに置き換え、メンテナンスしやすいコードへと導いてくれる。