0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Java用語、結局なんだっけ? #5】モダンJava編 ― ラムダ・Stream・Optional・record

0
Posted at

はじめに

株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。

Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
コーポレートサイト

「Java用語、結局なんだっけ?」シリーズの第5回です。

テーマ
#1 環境・基盤編
#2 DB接続編
#3 Web/サーバー編
#4 環境・DB・Web・例外スレッド
#5(本記事) モダンJava編
#6 フレームワーク編
#7 ビルド・運用編

今回は モダンJava編 です。Java 8 以降の 関数型プログラミング寄りの機能 と、Java 16 で正式導入された record までを整理します。


この記事のゴール

  • ラムダ式・メソッド参照・関数型インターフェースの関係を説明できる
  • Stream API の基本パターンを使い分けられる
  • Optional を正しく扱える
  • record の用途を理解できる

① ラムダ式 ― 「メソッドを式として書く」

一言で言うと

「引数を受け取って何かを返す」処理を、短い式で書く構文 です。
Java 8 から導入。

旧スタイル vs ラムダ

// Java 7以前:匿名クラス
Runnable task = new Runnable() {
    @Override
    public void run() {
        System.out.println("実行");
    }
};

// Java 8以降:ラムダ式
Runnable task = () -> System.out.println("実行");

行数が 5行 → 1行 に。Runnable インターフェースが「メソッド1つだけ」だから可能になる仕組みです(後述)。

構文パターン

// 引数なし
() -> System.out.println("Hello")

// 引数1つ(型省略可)
n -> n * 2

// 引数複数
(a, b) -> a + b

// 複数行(中括弧と return が必要)
(a, b) -> {
    int sum = a + b;
    return sum * 2;
}

② 関数型インターフェース ― 「ラムダの受け皿」

一言で言うと

抽象メソッドが1つだけのインターフェース です。
ラムダ式は「関数型インターフェースの実装」として動きます。

標準で用意されているもの

インターフェース シグネチャ 用途
Runnable void run() 引数なし・戻り値なし
Supplier<T> T get() 引数なし・戻り値あり
Consumer<T> void accept(T) 引数あり・戻り値なし
Function<T, R> R apply(T) 引数あり・戻り値あり
Predicate<T> boolean test(T) 引数あり・真偽値を返す
BiFunction<T, U, R> R apply(T, U) 引数2つ

使用例

Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4));  // → true

Function<String, Integer> length = s -> s.length();
System.out.println(length.apply("Hello"));  // → 5

@FunctionalInterface アノテーション

自作で関数型インターフェースを作るときは、@FunctionalInterface を付けると安全です。

@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);  // 抽象メソッドが2つ以上だとコンパイルエラー
}

Calculator add = (a, b) -> a + b;
System.out.println(add.calculate(3, 5));  // → 8

よくある誤解

  • 「ラムダ式は新しい型の変数」:違う。関数型インターフェース型の実装
  • 「抽象メソッドが複数あっても関数型」:違う。1つだけでないと関数型インターフェースにならない。

③ メソッド参照 ― 「ラムダのさらに短い書き方」

一言で言うと

既存のメソッドをそのままラムダのように使える構文 です。
:: で書きます。

パターン

// 1. 静的メソッド参照
Function<String, Integer> parse = Integer::parseInt;
// 同じ意味のラムダ:s -> Integer.parseInt(s)

// 2. インスタンスメソッド参照(既存のオブジェクト)
PrintStream out = System.out;
Consumer<String> print = out::println;
// 同じ意味:s -> out.println(s)

// 3. インスタンスメソッド参照(型のメソッド)
Function<String, Integer> length = String::length;
// 同じ意味:s -> s.length()

// 4. コンストラクタ参照
Supplier<ArrayList<String>> creator = ArrayList::new;
// 同じ意味:() -> new ArrayList<>()

よくある誤解

  • :: は型の指定」:違う。メソッドを参照する記号
  • String::length は静的メソッド呼び出し」:違う。「文字列の length() を呼ぶ関数」を作っている。

④ Stream API ― 「コレクション処理の宣言的記法」

一言で言うと

フィルタリング・変換・集計などを「処理の流れ」として書ける API です。
Java 8 から導入。

基本パターン

List<Integer> numbers = List.of(1, 2, 3, 4, 5);

List<Integer> evenSquares = numbers.stream()
        .filter(n -> n % 2 == 0)   // 偶数だけ
        .map(n -> n * n)            // 二乗に変換
        .collect(Collectors.toList());

System.out.println(evenSquares);  // → [4, 16]

よく使うオペレーター

メソッド 用途
filter 条件で絞り込み
map 要素を別の値に変換
flatMap ネストしたStreamをフラット化
sorted 並び替え
distinct 重複除去
limit(n) 最初のn個
count 件数
sumaverage 数値の集計(mapToInt 等の後)
collect(toList()) List化

Stream API のメリット

  • 意図がそのままコードに:「フィルタして」「変換して」「集めて」が読める
  • 並列化が容易.parallelStream() で並列実行可能
  • イミュータブル:元のリストを変更しない

よくある誤解

  • 「Stream は遅い」:基本的に通常のforとほぼ同等。並列化(parallelStream)は逆に遅くなることもある
  • 「Stream は再利用できる」:違う。1回consumeしたら再利用不可

⑤ Optional ― 「nullかもしれない値の安全な扱い」

一言で言うと

「値があるか、ないか」を型として表現するクラス です。
Java 8 から導入。

使い方

// nullになりうるメソッドの戻り値
public Optional<User> findUserById(long id) {
    User user = repository.get(id);
    return Optional.ofNullable(user);
}

// 呼び出し側
Optional<User> user = findUserById(1L);

// パターンA:デフォルト値
String name = user.map(User::getName).orElse("名無し");

// パターンB:例外を投げる
User u = user.orElseThrow(() -> new NotFoundException());

// パターンC:値があれば処理
user.ifPresent(u -> System.out.println(u.getName()));

「やってはいけない」使い方

// NG: Optional.get() の濫用
if (user.isPresent()) {
    user.get().getName();
}
// → これは null チェックと変わらない。map / ifPresent を使う

// NG: フィールドに Optional
class User {
    private Optional<String> email;  // ← シリアライズ不可、設計ミスのサイン
}

// NG: メソッド引数に Optional
public void process(Optional<String> name) { }  // ← オーバーロード推奨

Optional の使いどころ

  • メソッドの戻り値(最も推奨される使い方)
  • ❌ フィールドの型
  • ❌ メソッドの引数
  • ❌ コレクション内の要素

よくある誤解

  • 「Optional はNPE対策の万能薬」:違う。Optional 自身が null になりうる(戻り値で null Optional は返すべきでない)。
  • 「Optional.get() は安全」:違う。空のOptionalで呼ぶと NoSuchElementException。NPEと変わらない。

⑥ record ― 「データクラスを1行で」

一言で言うと

フィールド宣言とコンストラクタ・getter・equals・hashCode・toString を自動生成するクラス です。
Java 14 でプレビュー、Java 16 で正式導入。

旧スタイル vs record

// 旧:通常クラス(30行近く)
public class Point {
    private final int x;
    private final int y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public int getX() { return x; }
    public int getY() { return y; }
    @Override
    public boolean equals(Object o) { /* 略 */ }
    @Override
    public int hashCode() { /* 略 */ }
    @Override
    public String toString() { /* 略 */ }
}

// 新:record(1行!)
public record Point(int x, int y) {}

record でできること

public record Point(int x, int y) {
    // メソッド追加もできる
    double distanceFromOrigin() {
        return Math.sqrt(x * x + y * y);
    }

    // バリデーション付きコンストラクタ
    public Point {
        if (x < 0 || y < 0) {
            throw new IllegalArgumentException("座標は0以上を指定");
        }
    }
}

// 使い方
Point p = new Point(3, 4);
System.out.println(p.x());            // → 3 (getter ではなく x() という形)
System.out.println(p.distanceFromOrigin());  // → 5.0
System.out.println(p);                // → Point[x=3, y=4]

record の特徴

  • すべてのフィールドが final(不変クラス)
  • アクセサは getX() ではなく x()
  • equalshashCodetoString 自動生成
  • 継承不可(final クラス)

record の使いどころ

  • DTO(Data Transfer Object)
  • 値オブジェクト(金額・座標・日付範囲等)
  • API レスポンス・リクエストの型
  • ❌ JPA エンティティ(可変が必要)
  • ❌ 継承が必要なクラス

よくある誤解

  • 「record はJavaの新しいキーワード」:違う。特殊なクラス宣言。実体は Record クラスのサブクラス。
  • 「record で getter が getX() になる」:違う。x() という形になる。

用語まとめ早見表

用語 一言で 導入
ラムダ式 メソッドを式として書く構文 Java 8
関数型インターフェース 抽象メソッド1つだけのインターフェース Java 8
メソッド参照 既存メソッドを :: で参照 Java 8
Stream API コレクション処理の宣言的記法 Java 8
Optional 値の有無を型で表現 Java 8
record データクラスを1行で書く Java 16

現場あるある誤解集

❌ 誤解 ⭕ 正しい理解
「ラムダ = 新しい型の変数」 関数型インターフェースの実装
「Stream は配列より速い」 並列化しない限り、ほぼ同等
「Optional はNPE対策の万能薬」 使い方を誤るとNPEの代わりに別の例外が出る
「Optional.get() が安全」 空だと例外。orElse / orElseThrow / map を使う
「record は通常クラスの置き換え」 不変・継承不可なのでJPAエンティティ等には不向き
「Optional をフィールドに持つ」 設計ミスのサイン。getterだけOptionalを返す
「Stream を変数に保持して再利用」 1回consumeしたら再利用不可

演習問題

問題1:関数型インターフェース ⭐

次の関数型インターフェースのうち、String を引数に取り、int を返すものはどれですか?

  • A. Predicate<String>
  • B. Consumer<String>
  • C. Function<String, Integer>
  • D. Supplier<Integer>
模範解答

正解:C

解説

  • C Function<String, Integer> → 引数 String、戻り値 Integer(intにオートボクシング)。正解。
  • A Predicate<String> → 引数 String、戻り値 boolean
  • B Consumer<String> → 引数 String、戻り値 void
  • D Supplier<Integer> → 引数なし、戻り値 Integer

ポイントFunction<T, R>T が引数の型、R が戻り値の型。


問題2:Stream API ⭐

次のコードを Stream API を使って書き直すとどうなりますか?

List<Integer> result = new ArrayList<>();
for (Integer n : numbers) {
    if (n > 10) {
        result.add(n * 2);
    }
}
模範解答
List<Integer> result = numbers.stream()
        .filter(n -> n > 10)
        .map(n -> n * 2)
        .collect(Collectors.toList());

ポイント

  • ループ+if+add の3ステップが、filtermapcollect の3メソッドに対応
  • 「10より大きい数を、2倍にして、リストに集める」がコード上に並ぶ

問題3:Optional ⭐

次の中で、Optional の正しい使い方 はどれですか?(複数選択可)

  • A. メソッドの戻り値の型として使う
  • B. クラスのフィールド型として使う
  • C. メソッドの引数の型として使う
  • D. orElse でデフォルト値を指定する
  • E. Optional.get()if (opt.isPresent()) の中で呼ぶ
模範解答

正解:A と D

解説

  • A:戻り値の型としてはOptionalの本来の使い方
  • B:フィールドの型は通常型、getterで Optional を返す
  • C:引数の型としては推奨されない(オーバーロードで対応)
  • DorElse は正しい使い方
  • E:「isPresent + get」は null チェックと変わらない。map / ifPresent / orElse 系を使う

ポイント:Optional は 戻り値の型として使うのが基本。フィールド・引数には使わない。


まとめ

モダンJavaの6用語のおさらいです。

  1. ラムダ式:メソッドを式として書く構文
  2. 関数型インターフェース:抽象メソッド1つのインターフェース(ラムダの受け皿)
  3. メソッド参照:: で既存メソッドを参照
  4. Stream API:コレクション処理の宣言的記法
  5. Optional:値の有無を型で表現
  6. record:データクラスを1行で書く

これらは現代Java開発のスタンダードです。
「使えるけど雰囲気でやっている」状態を脱して、設計意図まで言える ようになると、Spring Bootなどのフレームワークが格段に理解しやすくなります。


次回予告

次回(#6)は フレームワーク編 です。

  • Spring / Spring Boot
  • DI(依存性注入)、IoCコンテナ
  • Bean、AOP
  • Lombok、JPA

を解説します。


参考


@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?