基礎LESSON 13

この教材で学ぶこと JVM、record、collection、interfaceからJavaの設計を学びます。

Java入門

対象読者: 基本文法を学び、静的型付けとobject-oriented designへ進みたい人。

この教材でできるようになること

  • source、compiler、bytecode、JVMの関係を説明できる
  • class、record、interfaceを用途から選べる
  • collectionと例外を扱える
  • mutableな状態を小さく保つ設計を説明できる

1. Javaが動くまで

Main.java → javac → Main.class(bytecode)→ JVMで実行
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, Java");
    }
}
javac Main.java
java Main

Javaはlanguageであると同時に、JVMを中心とするplatformでもあります。

2. recordで値を表す

public record Lesson(String id, String title, Level level) {
    public Lesson {
        if (id == null || id.isBlank()) {
            throw new IllegalArgumentException("id must not be blank");
        }
        if (title == null || title.isBlank()) {
            throw new IllegalArgumentException("title must not be blank");
        }
    }
}

enum Level {
    BEGINNER, INTERMEDIATE, ADVANCED
}

recordは値を保持するdata carrierに向いています。検証をcompact constructorへ集めれば、不正状態の生成を防げます。

3. collectionを扱う

import java.util.List;

public final class Curriculum {
    public static long countBeginnerLessons(List<Lesson> lessons) {
        return lessons.stream()
            .filter(lesson -> lesson.level() == Level.BEGINNER)
            .count();
    }
}

引数で受け取ったcollectionを不要に変更せず、結果を返す関数として書くと予測しやすくなります。

4. interfaceで境界を作る

import java.util.List;

public interface LessonRepository {
    List<Lesson> findAll();
    void save(Lesson lesson);
}

domain logicがdatabaseやHTTP clientへ直接依存せずinterfaceへ依存すれば、保存方法を差し替えやすくなります。ただし、実装が一つで変更理由もない段階から何でもinterface化する必要はありません。

5. 例外を意味で選ぶ

public Lesson requireLesson(String id, LessonRepository repository) {
    return repository.findAll().stream()
        .filter(lesson -> lesson.id().equals(id))
        .findFirst()
        .orElseThrow(() -> new IllegalArgumentException("unknown lesson: " + id));
}

catchする側が回復できる粒度で例外を選び、秘密情報や内部構成を利用者向けmessageへ出さないようにします。

6. packageで責務を分ける

src/main/java/example/study/
├── domain/
│   ├── Lesson.java
│   └── Curriculum.java
├── application/
│   └── CompleteLesson.java
└── infrastructure/
    └── FileLessonRepository.java

名前はframeworkではなく、扱うdomainや責務を表すようにします。

演習: 学習計画domain

次を実装してください。

  • Lesson: id、title、estimatedMinutes
  • StudyPlan: lesson一覧を持つ
  • 合計時間を返すmethod
  • id重複を拒否する処理
  • 空titleと0以下の時間を拒否

正常系だけでなく、空一覧、重複id、境界値をテストします。

理解チェック

  • source、bytecode、JVMを区別できる
  • class、record、interfaceを使う理由を説明できる
  • collection操作を副作用の小さい関数へ分けられる
  • 不正状態を生成時に拒否できる

公式資料

次に読む

設計とリファクタリングで、責務と依存関係を整理します。