Java / LESSON 16 / JV16

Java:ラムダ式とStream

1学習の目的

2基礎解説

JV12までは forif でデータを処理してきました。Stream API を使うと、「何をしたいか」だけを書いて、繰り返しの手続きを省略できます。

操作意味
filter条件で絞るp -> p.getStock() > 0
map別のものに変換p -> p.getName()
sorted並べ替えComparator.comparing…
collect結果を集めるCollectors.toList()
✅ 覚えるべき重要ポイント
  1. ラムダ式は「引数 -> 処理」p -> p.getStock() > 0 は「p を受け取って、在庫が0より大きいか判定する」という意味。短い関数をその場で書く記法
  2. Stream は stream() で始まり、collect などで終わる。途中の filtermapつなげて書ける(メソッドチェーン)。
  3. Product::getName はメソッド参照p -> p.getName() と同じ意味だが、より短く書ける。
  4. 数値の集計は mapToInt を挟む.mapToInt(p -> p.getPrice()).sum() のように書くと、合計・平均・最大が取れる。
  5. 元のリストは変更されない。Streamの操作は新しい結果を作るだけなので、元のデータは安全に保たれる。

現場使用例:検索結果の絞り込み、一覧の並べ替え、カテゴリ別集計、CSVの加工。現代のJavaコードでは Stream が当たり前に使われている

import java.util.*;
import java.util.stream.*;

List<Product> items = …;

// ① 絞って、変換して、集める
List<String> names = items.stream()
    .filter(p -> p.getStock() > 0)      // 在庫があるものだけ
    .map(p -> p.getName())              // 名前に変換
    .collect(Collectors.toList());      // Listにする

// ② メソッド参照で短く
.map(Product::getName)                  // p -> p.getName() と同じ

// ③ 数値の集計
int total = items.stream()
    .mapToInt(p -> p.getPrice() * p.getStock())
    .sum();

double avg = items.stream()
    .mapToInt(Product::getPrice)
    .average()
    .orElse(0);                         // 空なら0

long count = items.stream()
    .filter(p -> p.getPrice() >= 10000)
    .count();

// ④ 並べ替え(高い順)
items.stream()
    .sorted(Comparator.comparingInt(Product::getPrice).reversed())
    .map(Product::getName)
    .collect(Collectors.toList());

// ⑤ 最大・最小
String max = items.stream()
    .max(Comparator.comparingInt(Product::getPrice))
    .map(Product::getName)
    .orElse("なし");

// ⑥ グループ化(カテゴリ別の件数)
Map<String, Long> byCategory = items.stream()
    .collect(Collectors.groupingBy(
        Product::getCategory, TreeMap::new, Collectors.counting()));

// ⑦ 文字列の連結
String joined = items.stream()
    .map(Product::getName)
    .collect(Collectors.joining(", "));

// ⑧ 条件の判定
items.stream().anyMatch(p -> p.getStock() == 0);   // 1つでも該当するか
items.stream().allMatch(p -> p.getPrice() > 0);    // すべて該当するか

3基本ドリル(10問)

JV16-D01 数値リスト {10, 20, 30}合計を Stream で求めて出力せよ。 コーディング★☆☆無料
期待される結果

出力 == 60

ヒント

list.stream().mapToInt(n -> n).sum() と書く。

模範解答
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = List.of(10, 20, 30);

        int total = nums.stream().mapToInt(n -> n).sum();

        System.out.println(total);
    }
}
解説

for文を書かずに合計できるmapToInt で数値の流れに変換すると、sum() が使えるようになる。

JV16-D02 ラムダ式 n -> n * 2 の意味はどれか。
A. nと2を掛ける宣言 B. nを受け取って2倍を返す関数 C. 変数の宣言 D. 比較演算
選択★☆☆無料
期待される結果

解答 == B

ヒント

矢印の左が引数、右が処理。

模範解答
B
解説

「引数 → 処理」を表す短い関数。メソッドを定義するほどではない小さな処理を、その場に書ける。

JV16-D03 文字列リストから4文字以下のものだけを抽出して出力せよ(ノートPC・マウス・モニター)。 コーディング★☆☆無料
期待される結果

出力 == [マウス, モニター]

ヒント

filter(s -> s.length() <= 4) で絞り、collect(Collectors.toList()) で集める。

模範解答
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<String> items = List.of("ノートPC", "マウス", "モニター");

        List<String> result = items.stream()
                .filter(s -> s.length() <= 4)
                .collect(Collectors.toList());

        System.out.println(result);
    }
}
解説

filter は条件に合うものだけを通す。for文とifを書いていた処理が1行になる。

JV16-D04 条件で絞り込むメソッドを補ってコードを完成させよ。 穴埋め★☆☆無料
コード
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = List.of(1, 2, 3, 4, 5);

        List<Integer> even = nums.stream()
                .____(n -> n % 2 == 0)
                .collect(Collectors.toList());

        System.out.println(even);
    }
}
期待される結果

出力 == [2, 4]

ヒント

「ふるいにかける」を意味する6文字のメソッド。

模範解答
filter
解説

filter は条件が true のものだけを残す。ふるいにかけるイメージで覚えるとよい。

JV16-D05 Stream の操作は元のリストをどうするか。
A. 変更する B. 変更しない C. 削除する D. 並べ替える
選択★☆☆無料
期待される結果

解答 == B

ヒント

新しい結果を作るだけ。

模範解答
B
解説

元のデータは安全に保たれるCollections.sort が元を並べ替えるのとは対照的。

JV16-D06 map で変換せよ(数値リストを2倍にして出力)。 コーディング★★☆広告解放
期待される結果

出力 == [20, 40, 60]

ヒント

map(n -> n * 2) で各要素を変換する。

模範解答
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = List.of(10, 20, 30);

        List<Integer> doubled = nums.stream()
                .map(n -> n * 2)
                .collect(Collectors.toList());

        System.out.println(doubled);
    }
}
解説

map は各要素を別のものに変換する。件数は変わらず、中身だけが変わる——filter(件数が減る)との違いに注意。

JV16-D07 結果をListに集めるコードを完成させよ。 穴埋め★★☆広告解放
コード
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<String> result = List.of("A", "B").stream()
                .____(Collectors.toList());

        System.out.println(result);
    }
}
期待される結果

出力 == [A, B]

ヒント

「集める」を意味する7文字のメソッド。

模範解答
collect
解説

collect が Stream の終点。これを書かないと処理が実行されず、結果も取り出せない。

JV16-D08 Product::getName という書き方は何か。
A. 変数宣言 B. メソッド参照(p -> p.getName() と同じ) C. 継承 D. 型変換
選択★★☆広告解放
期待される結果

解答 == B

ヒント

ラムダ式の短縮形。

模範解答
B
解説

単にメソッドを呼ぶだけなら、この書き方が短い:: はコロン2つで、クラス名とメソッド名をつなぐ。

JV16-D09 数値リストの平均を求めよ(空の場合は0とする)。 コーディング★★☆広告解放
期待される結果

出力 == 20.0

ヒント

mapToInt して average() を呼び、orElse(0) で既定値を指定する。

模範解答
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = List.of(10, 20, 30);

        double avg = nums.stream()
                .mapToInt(n -> n)
                .average()
                .orElse(0);

        System.out.println(avg);
    }
}
解説

average は Optional を返す——空のリストでは平均が存在しないため。orElse で「無いときの値」を指定する。

JV16-D10 文字列リストをカンマ区切りで連結せよ(Collectors.joining を使う)。 コーディング★★☆広告解放
期待される結果

出力 == A, B, C

ヒント

collect(Collectors.joining(", ")) と書く。

模範解答
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<String> items = List.of("A", "B", "C");

        String joined = items.stream()
                .collect(Collectors.joining(", "));

        System.out.println(joined);
    }
}
解説

末尾に余計な区切りが付かない。for文で書くと「最後だけカンマを付けない」処理が必要になり、意外と面倒。

4実践シナリオ(5問)

JV16-S01 【在庫のある商品】Product リストから在庫がある商品の名前だけを抽出して出力せよ(4件中3件が該当)。 コーディング★★☆広告解放
期待される結果

出力 == [ノートPC, マウス, キーボード]

ヒント

filter で絞り、map で名前に変換する。

模範解答
import java.util.*;
import java.util.stream.*;

public class Main {

    static class Product {
        String name;
        int stock;

        Product(String name, int stock) {
            this.name = name;
            this.stock = stock;
        }

        String getName() { return name; }
        int getStock() { return stock; }
    }

    public static void main(String[] args) {
        List<Product> items = List.of(
                new Product("ノートPC", 5),
                new Product("マウス", 42),
                new Product("モニター", 0),
                new Product("キーボード", 18));

        List<String> result = items.stream()
                .filter(p -> p.getStock() > 0)
                .map(Product::getName)
                .collect(Collectors.toList());

        System.out.println(result);
    }
}
解説

「絞って、変換して、集める」が Stream の基本形。for文とifで書いていた処理が、3行の流れになる。

JV16-S02 【在庫総額】Product リストから価格×在庫の合計を Stream で求めよ(927400)。 コーディング★★☆広告解放
期待される結果

出力 == 927400

ヒント

mapToInt の中で掛け算し、sum() する。

模範解答
import java.util.*;

public class Main {

    static class Product {
        int price;
        int stock;

        Product(int price, int stock) {
            this.price = price;
            this.stock = stock;
        }

        int getPrice() { return price; }
        int getStock() { return stock; }
    }

    public static void main(String[] args) {
        List<Product> items = List.of(
                new Product(128000, 5),
                new Product(3200, 42),
                new Product(45000, 0),
                new Product(8500, 18));

        int total = items.stream()
                .mapToInt(p -> p.getPrice() * p.getStock())
                .sum();

        System.out.println(total);
    }
}
解説

合計用の変数を宣言する必要がない。JV05では int total = 0; から始めていたが、Streamなら不要。

JV16-S03 【価格順に並べ替え】商品を価格の高い順に並べて名前を出力せよ。 コーディング★★☆広告解放
期待される結果

出力 == [ノートPC, モニター, キーボード, マウス]

ヒント

Comparator.comparingInt(...).reversed() で降順になる。

模範解答
import java.util.*;
import java.util.stream.*;

public class Main {

    static class Product {
        String name;
        int price;

        Product(String name, int price) {
            this.name = name;
            this.price = price;
        }

        String getName() { return name; }
        int getPrice() { return price; }
    }

    public static void main(String[] args) {
        List<Product> items = List.of(
                new Product("ノートPC", 128000),
                new Product("マウス", 3200),
                new Product("モニター", 45000),
                new Product("キーボード", 8500));

        List<String> sorted = items.stream()
                .sorted(Comparator.comparingInt(Product::getPrice).reversed())
                .map(Product::getName)
                .collect(Collectors.toList());

        System.out.println(sorted);
    }
}
解説

reversed() を付けるだけで降順になる。JV06では「後ろからループする」という工夫が必要だった。

JV16-S04 【カテゴリ別の集計】商品をカテゴリごとに件数を集計せよ(TreeMap でキー順に)。 コーディング★★★広告解放
期待される結果

出力 == {PC=1, 周辺機器=3}

ヒント

Collectors.groupingBy(分類キー, TreeMap::new, Collectors.counting()) を使う。

模範解答
import java.util.*;
import java.util.stream.*;

public class Main {

    static class Product {
        String name;
        String category;

        Product(String name, String category) {
            this.name = name;
            this.category = category;
        }

        String getCategory() { return category; }
    }

    public static void main(String[] args) {
        List<Product> items = List.of(
                new Product("ノートPC", "PC"),
                new Product("マウス", "周辺機器"),
                new Product("モニター", "周辺機器"),
                new Product("キーボード", "周辺機器"));

        Map<String, Long> byCategory = items.stream()
                .collect(Collectors.groupingBy(
                        Product::getCategory,
                        TreeMap::new,
                        Collectors.counting()));

        System.out.println(byCategory);
    }
}
解説

JV12でMapを使って書いた集計が1文になるgetOrDefault でカウントを増やす処理が不要になった。

JV16-S05 【条件の判定】商品リストに品切れがあるかすべて有料かを判定して2行出力せよ。 コーディング★★★広告解放
期待される結果

出力2行。true / true

ヒント

anyMatch と allMatch を使う。

模範解答
import java.util.*;

public class Main {

    static class Product {
        int price;
        int stock;

        Product(int price, int stock) {
            this.price = price;
            this.stock = stock;
        }

        int getPrice() { return price; }
        int getStock() { return stock; }
    }

    public static void main(String[] args) {
        List<Product> items = List.of(
                new Product(128000, 5),
                new Product(45000, 0),
                new Product(8500, 18));

        System.out.println(items.stream().anyMatch(p -> p.getStock() == 0));
        System.out.println(items.stream().allMatch(p -> p.getPrice() > 0));
    }
}
解説

anyMatch は見つかった時点で終了する。1万件あっても、1件目で該当すれば残りは調べない——効率的に動く。

5仕上げ課題

JV16-FINAL 【Streamで書き直す在庫レポート】JV12の集計を、Stream API で書き直せ。

Product クラス:name・category・price・stock(getterを持つ)

データ
・ノートPC / PC / 128000 / 5
・マウス / 周辺機器 / 3200 / 42
・モニター / 周辺機器 / 45000 / 0
・キーボード / 周辺機器 / 8500 / 18

すべて Stream で処理すること
在庫がある商品名をカンマ区切りで出力
在庫総額(価格×在庫の合計)
平均価格(小数第1位まで)
最も高い商品の名前
カテゴリ別の合計金額(価格の合計、TreeMapでキー順)
品切れ商品があるかの判定

期待される出力(6行)
在庫あり:ノートPC, マウス, キーボード
在庫総額:927,400円
平均価格:46,175.0円
最高額:ノートPC
カテゴリ別:{PC=128000, 周辺機器=56700}
品切れあり:true
総合★★★広告解放
期待される結果

出力6行が完全一致

ヒント

joining(", ") で連結、mapToInt().sum() で合計、average().orElse(0) で平均、max(Comparator...) で最大、groupingBy(..., TreeMap::new, summingInt(...)) でカテゴリ別集計、anyMatch で判定。

模範解答
import java.util.*;
import java.util.stream.*;

public class Main {

    static class Product {
        private String name;
        private String category;
        private int price;
        private int stock;

        Product(String name, String category, int price, int stock) {
            this.name = name;
            this.category = category;
            this.price = price;
            this.stock = stock;
        }

        String getName() { return name; }
        String getCategory() { return category; }
        int getPrice() { return price; }
        int getStock() { return stock; }
    }

    public static void main(String[] args) {
        List<Product> items = List.of(
                new Product("ノートPC", "PC", 128000, 5),
                new Product("マウス", "周辺機器", 3200, 42),
                new Product("モニター", "周辺機器", 45000, 0),
                new Product("キーボード", "周辺機器", 8500, 18));

        String inStock = items.stream()
                .filter(p -> p.getStock() > 0)
                .map(Product::getName)
                .collect(Collectors.joining(", "));
        System.out.println("在庫あり:" + inStock);

        int total = items.stream()
                .mapToInt(p -> p.getPrice() * p.getStock())
                .sum();
        System.out.println("在庫総額:" + String.format("%,d", total) + "円");

        double avg = items.stream()
                .mapToInt(Product::getPrice)
                .average()
                .orElse(0);
        System.out.println("平均価格:" + String.format("%,.1f", avg) + "円");

        String maxName = items.stream()
                .max(Comparator.comparingInt(Product::getPrice))
                .map(Product::getName)
                .orElse("なし");
        System.out.println("最高額:" + maxName);

        Map<String, Integer> byCategory = items.stream()
                .collect(Collectors.groupingBy(
                        Product::getCategory,
                        TreeMap::new,
                        Collectors.summingInt(Product::getPrice)));
        System.out.println("カテゴリ別:" + byCategory);

        boolean hasOutOfStock = items.stream().anyMatch(p -> p.getStock() == 0);
        System.out.println("品切れあり:" + hasOutOfStock);
    }
}
解説

JV12で30行以上かかった集計が、それぞれ3〜5行で書けた。とくにカテゴリ別集計は劇的だ。JV12では Map を2つ用意し、getOrDefault で加算する処理を書いていたが、Stream なら groupingBy の1文で済む。

Stream の利点は「何をしたいか」がそのまま書けることにある。filter → map → collect という並びは「絞って、変換して、集める」と読める。for文とifで書くと、繰り返しの手続きがコードの大半を占め、目的が埋もれてしまう。

ただしStreamが常に優れているわけではない。単純なループなら for のほうが読みやすいことも多く、デバッグもしやすい。複雑な集計や、絞り込みと変換が連続する処理——そういう場面で Stream が力を発揮する。

average().orElse(0)Optional にも触れておきたい。空のリストに平均は存在しないので、Javaは「値が無いかもしれない」という型で返す。orElse で既定値を指定することで、null チェックを書かずに済む——nullを避ける現代的な設計だ。

次章では日付とユーティリティを学ぶ。実務で必ず使う、日付計算と数値整形を押さえる。