Skip to content

Java チートシート

エンタープライズ級のオブジェクト指向言語、一度書けばどこでも動く。

01

はじめに

Hello World

すべての Java プログラムは main() から開始されます。ファイル名は public クラス名と一致する必要があります(Main.java → Main.class)。javac がバイトコード(.class)にコンパイルし、java が JVM 上で実行します。System.out.println は stdout に出力し、printf はフォーマット指定子(%s、%d、%f、%n は改行)をサポートします。

java
// Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
        System.out.printf("Name: %s, Age: %d%n", "Alice", 30);
    }
}

// Compile: javac Main.java  -> Main.class
// Run:     java Main
// Package: java -cp . com.example.Main

// Every Java program needs:
// 1. A class (public class, file name must match)
// 2. A main method: public static void main(String[] args)

変数とプリミティブ型

Java には8つのプリミティブ型(int、long、double、float、boolean、char、byte、short)と参照型(String、配列、オブジェクト)があります。定数には 'final' を使用します。'var'(Java 10+)はコンパイル時に型を推論します — 型が明らかなローカル変数に使用します。数値のアンダースコア(100_000)は可読性を向上させます。

java
// Primitive types (8 total)
int age = 30;              // 32-bit integer
long bigNum = 100_000L;   // 64-bit integer
double price = 19.99;     // 64-bit float (default for decimals)
float pi = 3.14f;         // 32-bit float
boolean active = true;    // true/false
char grade = 'A';         // 16-bit Unicode character
byte b = 127;             // 8-bit signed
short s = 32767;          // 16-bit signed

// Reference types
String name = "Alice";    // Object (not primitive)
int[] nums = {1, 2, 3};  // Array object

// Constants
final double PI = 3.14159; // can't be reassigned

// var (Java 10+, local type inference)
var count = 42;    // inferred as int
var list = new ArrayList<String>(); // inferred as ArrayList<String>

ラッパークラスとボクシング

ラッパークラス(Integer、Double、Boolean など)はプリミティブ型のオブジェクト版です。自動ボクシング/アンボクシングで自動変換されます。Integer は -128 から 127 までの値をキャッシュするため、小さい数では == が動作しますが大きい数では失敗します — 常に .equals() を使用してください。ラッパーはコレクション(プリミティブを保持できない)に必要です。

java
// Wrapper classes (Object versions of primitives)
Integer wrapped = Integer.valueOf(42);  // explicit
Integer auto = 42;                       // autoboxing
int unboxed = auto;                      // unboxing

// Useful methods
int max = Integer.MAX_VALUE;     // 2147483647
String bin = Integer.toBinaryString(42);
int parsed = Integer.parseInt("42");
String s = String.valueOf(42);

// Other wrappers: Double, Boolean, Character, Long, Float
Double d = 3.14;
Boolean b = Boolean.TRUE;
Character c = 'A';

// Be careful with == on wrappers
Integer a = 127, b2 = 127;  // a == b2: true (cached)
Integer x = 128, y = 128;   // x == y: false (not cached)
// Always use .equals() for Integer comparison

パッケージとインポート

パッケージはクラスを整理し、名前の衝突を防ぎます。慣習:逆ドメイン名(com.example.app)。特定のクラスをインポートするかワイルドカード(*)を使用します。静的インポートは定数やメソッド(Math.PI、Math.sqrt)を取り込みます。完全修飾名はインポートなしで動作しますが冗長です。java.lang パッケージは自動インポートされます。

java
// Package declaration (must be first line)
package com.example.app;

// Import specific class
import java.util.List;
import java.util.ArrayList;

// Import all classes in a package
import java.util.*;

// Static import (for static members)
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;

// Usage
double area = PI * 5 * 5;
double root = sqrt(16);

// Fully qualified name (no import needed)
java.time.LocalDate today = java.time.LocalDate.now();

// Package naming convention: reverse domain
// com.google.gson, org.apache.commons, io.netty.channel

入力と出力

System.out(stdout)、System.err(stderr)、System.in(stdin)。Scanner はコンソール入力を読み取る最も簡単な方法です — トークンを解析します(nextInt、nextDouble、nextLine)。リソースを解放するために必ず Scanner を閉じてください。コマンドライン引数は args[] にあります(args[0] は C のようにプログラム名ではなく最初の引数です)。

java
import java.util.Scanner;

// Console output
System.out.println("Hello");      // with newline
System.out.print("No newline");   // without newline
System.out.printf("Pi: %.2f%n", 3.14159); // formatted

// Console input with Scanner
Scanner scanner = new Scanner(System.in);
System.out.print("Enter name: ");
String name = scanner.nextLine();

System.out.print("Enter age: ");
int age = scanner.nextInt();

System.out.printf("Hi %s, age %d%n", name, age);
scanner.close(); // always close

// Command-line arguments
// java Main arg1 arg2
// args[0] = "arg1", args[1] = "arg2"
02

文字列とフォーマット

String メソッド

文字列は不変です — メソッドは新しい文字列を返します。内容比較には常に .equals() を使用してください(== は参照を比較します)。compareTo() は順序付けのために負/ゼロ/正を返します(ソートに便利)。split() は String[] を返します。可変文字列には StringBuilder を使用してください。

java
String s = "Hello, World";

// Length and access
int len = s.length();        // 12
char c = s.charAt(0);        // 'H'

// Comparison
s.equals("Hello, World");    // true (content comparison)
s.equalsIgnoreCase("hello, world"); // true
s.compareTo("Apple");        // positive (s > "Apple")
"abc".compareTo("abd");      // negative

// Search
s.indexOf("World");          // 7 (-1 if not found)
s.lastIndexOf("l");          // 10
s.contains("World");         // true
s.startsWith("Hello");       // true
s.endsWith("World");         // true

// Extract
s.substring(7);              // "World"
s.substring(0, 5);           // "Hello"

// Transform
s.toUpperCase();             // "HELLO, WORLD"
s.toLowerCase();             // "hello, world"
s.replace("o", "0");         // "Hell0, W0rld"
s.trim();                    // remove whitespace
s.split(", ");               // ["Hello", "World"]

StringBuilder と連結

+ による文字列連結は毎回新しい String を作成します(ループ内では非効率)。StringBuilder は可変で、文字列を段階的に構築するのに効率的です。StringBuffer はスレッドセーフ版です(ほとんど必要ありません)。String.join() はデリミタで結合します。Java 11+ は文字列の繰り返しに repeat() を追加しました。

java
// String concatenation (creates new String each time)
String s = "Hello" + ", " + "World";
String formatted = String.format("%s is %d", "Alice", 30);

// StringBuilder (mutable, efficient for many concatenations)
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(", ");
sb.append("World");
sb.insert(5, " there");
sb.delete(5, 11);
sb.reverse();
String result = sb.toString();

// StringBuffer (thread-safe, slower than StringBuilder)
StringBuffer sbf = new StringBuffer("thread-safe");

// Join strings
String joined = String.join(", ", "a", "b", "c"); // "a, b, c"

// Repeat (Java 11+)
String repeated = "ab".repeat(3); // "ababab"

文字列フォーマット

printf/format は C スタイルのフォーマット指定子を使用します:%d(int)、%f(float)、%s(string)、%c(char)、%b(boolean)、%x(16進数)。幅(%5d)、左揃え(%-5d)、ゼロ埋め(%05d)、精度(%.2f)。%n はプラットフォームの改行です。テキストブロック(Java 15+)は三重引用符でエスケープなしの複数行文字列を可能にします。

java
// printf / format specifiers
System.out.printf("Int: %d%n", 42);
System.out.printf("Float: %.2f%n", 3.14159);  // 3.14
System.out.printf("String: %s%n", "hello");
System.out.printf("Char: %c%n", 'A');
System.out.printf("Bool: %b%n", true);
System.out.printf("Hex: %x%n", 255);           // ff
System.out.printf("Octal: %o%n", 8);           // 10

// Width and padding
System.out.printf("[%5d]%n", 42);      // [   42]
System.out.printf("[%-5d]%n", 42);     // [42   ]
System.out.printf("[%05d]%n", 42);     // [00042]
System.out.printf("[%8.2f]%n", 3.14);  // [    3.14]

// String.format returns a String
String s = String.format("Name: %s, Age: %d", "Alice", 30);

// Text blocks (Java 15+)
String json = """
    {
        "name": "Alice",
        "age": 30
    }
    """;

正規表現

Java の正規表現は Pattern(コンパイル済み)と Matcher(入力に適用)を使用します。String メソッド(matches、split、replaceAll)は便利なショートカットです。Java 文字列リテラルではバックスラッシュを2倍にする必要があります(\d で \d)。グループは括弧でキャプチャされ、置換では $1、$2 で参照されます。繰り返し使用する場合はパターンを一度コンパイルしてください。

java
import java.util.regex.*;

// String methods
"hello123".matches("[a-z]+\d+"); // true
"a,b,c".split(",");               // ["a", "b", "c"]
"hello".replaceAll("l", "L");     // "heLLo"

// Pattern and Matcher
Pattern p = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
Matcher m = p.matcher("Date: 2024-01-15");

if (m.find()) {
    System.out.println(m.group()); // "2024-01-15"
}

// Find all matches
while (m.find()) {
    System.out.println(m.group());
}

// Replace with regex
String result = "2024-01-15".replaceAll(
    "(\\d{4})-(\\d{2})-(\\d{2})",
    "$3/$2/$1"); // "15/01/2024"

// Common patterns
String email = "^[\\w.]+@[\\w.]+\\.\\w+$";
String phone = "^\\d{3}-\\d{4}$";

数値と数学

Math クラスは静的数学関数を提供します。Math.random() は 0.0-1.0 を返します。より制御が必要な場合は java.util.Random(シード設定可能)や java.security.SecureRandom(暗号学的)を使用します。Integer/Double には静的ユーティリティメソッドがあります。浮動小数点精度に注意してください — 金融計算には BigDecimal を使用します。

java
// Math class
double sqrt = Math.sqrt(16);     // 4.0
double pow = Math.pow(2, 10);    // 1024.0
int abs = Math.abs(-5);          // 5
int max = Math.max(3, 7);        // 7
int min = Math.min(3, 7);        // 3
double rounded = Math.round(3.7); // 4
double ceil = Math.ceil(3.1);    // 4.0
double floor = Math.floor(3.9);  // 3.0
double random = Math.random();   // 0.0 to 1.0

// Constants
double pi = Math.PI;             // 3.14159...
double e = Math.E;               // 2.71828...

// Integer/Long methods
int sum = Integer.sum(3, 4);     // 7
int max2 = Integer.max(3, 7);    // 7

// Rounding modes
double r = Math.round(3.5);      // 4 (round half up)
double r2 = Math.floor(3.5 + 0.5); // alternative

// Random (java.util.Random)
import java.util.Random;
Random rand = new Random();
int n = rand.nextInt(100);  // 0-99
double d = rand.nextDouble(); // 0.0-1.0
boolean b = rand.nextBoolean();
03

制御フロー

If / Else

Java の if/else は C/C++ と同様に動作します。条件は boolean でなければなりません — JavaScript のような真/偽判定はありません(0 と空でない文字列は真ではありません)。三項演算子(cond ? a : b)は式であり文ではありません。単文本体でもブレースを使用してください(コードスタイルのベストプラクティス)。

java
int score = 85;

if (score >= 90) {
    System.out.println("A");
} else if (score >= 80) {
    System.out.println("B");
} else if (score >= 70) {
    System.out.println("C");
} else {
    System.out.println("F");
}

// Ternary operator
String grade = score >= 60 ? "Pass" : "Fail";

// Nested if
if (score >= 60) {
    if (score >= 90) {
        System.out.println("Excellent");
    }
}

// Note: conditions must be boolean (no truthy/falsy)
// if (score) { } // Error: int is not boolean

Switch と式

従来の switch はフォールスルーします(break を使用)。Java 14+ の switch 式(->)はフォールスルーせず、値を返せます。複数の case ラベルにカンマを使用します(case 1, 2, 3)。'yield' は複雑なブロックから値を返します。switch 式は網羅的です — enum の場合は default またはすべての case が必要です。

java
// Traditional switch (fall-through)
int day = 3;
switch (day) {
    case 1:
        System.out.println("Mon");
        break;
    case 2:
    case 3:
    case 4:
        System.out.println("Midweek");
        break;
    case 6:
    case 7:
        System.out.println("Weekend");
        break;
    default:
        System.out.println("Invalid");
}

// Switch expression (Java 14+, no fall-through)
String type = switch (day) {
    case 1, 2, 3, 4, 5 -> "Weekday";
    case 6, 7 -> "Weekend";
    default -> "Invalid";
};

// Switch with yield (for complex blocks)
int result = switch (day) {
    case 1, 2, 3, 4, 5 -> {
        int hours = 8;
        yield hours * 5;
    }
    case 6, 7 -> 0;
    default -> -1;
};

ループ

Java には for、while、do-while ループがあります。拡張 for(for-each)は配列と任意の Iterable で動作します。break はループを終了し、continue は次の反復にスキップします。コレクションでは、インデックス付きループより for-each またはストリームを優先してください。do-while は少なくとも1回実行されます(稀に使用)。

java
// For loop
for (int i = 0; i < 5; i++) {
    System.out.println(i);
}

// Enhanced for (for-each)
int[] nums = {1, 2, 3, 4, 5};
for (int n : nums) {
    System.out.println(n);
}

List<String> names = List.of("Alice", "Bob");
for (String name : names) {
    System.out.println(name);
}

// While loop
int count = 0;
while (count < 3) {
    System.out.println(count);
    count++;
}

// Do-while (runs at least once)
int i = 0;
do {
    System.out.println(i);
    i++;
} while (i < 3);

// Break and continue
for (int j = 0; j < 10; j++) {
    if (j == 5) break;      // exit loop
    if (j % 2 == 0) continue; // skip iteration
    System.out.println(j);
}

ラベル付き Break と Continue

ラベル(outer:)はネストされたループから外側のループを break/continue できます。これは稀にしか必要ありません — return でメソッドに抽出する方が通常はクリーンです。ラベルはループの前にコロンを付けて配置します。break label はラベル付きループを終了し、continue label はその次の反復にスキップします。

java
// Labels for breaking out of nested loops
outer:
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (i == 1 && j == 1) {
            break outer; // exits both loops
        }
        System.out.println(i + "," + j);
    }
}

// Labeled continue
outer:
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (j == 1) {
            continue outer; // skip to next i
        }
        System.out.println(i + "," + j);
    }
}

// Alternative: extract to method and use return
void findPair(int[][] matrix, int target) {
    for (int i = 0; i < matrix.length; i++) {
        for (int j = 0; j < matrix[i].length; j++) {
            if (matrix[i][j] == target) return; // exit method
        }
    }
}

配列

配列は固定長です(動的には ArrayList を使用)。Arrays.sort() はインプレースでソートします。Arrays.toString() は読みやすい表現を提供します。Arrays.copyOf() は新しい長さでコピーを作成します。多次元配列では、各行が異なる長さを持てます(ジャグ配列)。配列のユーティリティメソッドには Arrays を使用してください。

java
// Declare and initialize
int[] nums = {1, 2, 3, 4, 5};
int[] empty = new int[5]; // [0, 0, 0, 0, 0]
String[] names = new String[3]; // [null, null, null]

// Access and modify
nums[0] = 10;
int first = nums[0]; // 10
int length = nums.length; // 5

// Multidimensional
int[][] matrix = {{1, 2}, {3, 4}};
int val = matrix[0][1]; // 2

// Arrays utility class
import java.util.Arrays;
int[] sorted = {3, 1, 2};
Arrays.sort(sorted); // [1, 2, 3]
int[] copy = Arrays.copyOf(nums, 3);
String str = Arrays.toString(nums); // "[10, 2, 3, 4, 5]"
boolean eq = Arrays.equals(nums, copy);

// Fill
int[] filled = new int[5];
Arrays.fill(filled, 42); // [42, 42, 42, 42, 42]

// Binary search (sorted array only)
int idx = Arrays.binarySearch(sorted, 2); // index of 2
04

メソッドと関数

メソッドの定義

Java メソッドは常にクラス内にあります。'static' はメソッドがクラスに属することを意味します(インスタンスなしで呼び出し)。戻り型(int、String、void)は名前の前に宣言されます。パラメータは型付きです。Java にはデフォルトパラメータ値はありません — 代わりにメソッドオーバーロードを使用します。

java
public class Calculator {
    // Method with return type
    public static int add(int a, int b) {
        return a + b;
    }

    // Void method (no return)
    public static void printResult(int result) {
        System.out.println("Result: " + result);
    }

    // Method with default (no overloading needed)
    public static String greet(String name, String greeting) {
        return greeting + ", " + name + "!";
    }

    public static void main(String[] args) {
        int sum = add(3, 4);
        printResult(sum);

        String msg = greet("Alice", "Hello");
        System.out.println(msg);
    }
}

メソッドのオーバーロード

メソッドオーバーロードは同じ名前で異なるパラメータリスト(型、数、順序)の複数メソッドを許可します。Java は引数の型に基づいてコンパイル時にオーバーロードを解決します。オーバーロードはコンストラクタとユーティリティメソッドで一般的です。オーバーライド(継承とランタイムディスパッチを伴う)とは異なります。

java
public class MathUtils {
    // Overloaded methods (same name, different params)
    public static int add(int a, int b) {
        return a + b;
    }

    public static double add(double a, double b) {
        return a + b;
    }

    public static int add(int a, int b, int c) {
        return a + b + c;
    }

    public static String add(String a, String b) {
        return a + b;
    }
}

// Java picks the most specific match
MathUtils.add(1, 2);        // int version -> 3
MathUtils.add(1.5, 2.5);    // double version -> 4.0
MathUtils.add(1, 2, 3);     // 3-param version -> 6
MathUtils.add("Hello", "!"); // String version -> "Hello!"

可変長引数と値渡し

可変長引数(Type... name)は可変引数を許可し、配列として受け取ります。Java は常に値渡しです:プリミティブはコピーされ、オブジェクト参照はコピーされます(しかし同じオブジェクトを指します)。したがって、メソッド内でパラメータを変更しても呼び出し元の変数には影響しませんが、指す先のオブジェクトを変更すると影響します。

java
// Varargs: variable number of arguments
public static int sum(int... nums) {
    int total = 0;
    for (int n : nums) {
        total += n;
    }
    return total;
}

sum(1, 2, 3);           // 6
sum(1, 2, 3, 4, 5);     // 15
sum();                  // 0 (empty array)
int[] arr = {1, 2, 3};
sum(arr);               // 6 (pass array to varargs)

// Java is ALWAYS pass-by-value
public static void modify(int x) {
    x = 100; // doesn't affect the caller's variable
}

int n = 5;
modify(n);
System.out.println(n); // still 5

// For objects, the reference is passed by value
public static void addItem(List<String> list) {
    list.add("new"); // modifies the same list object
}

再帰

再帰はメソッドが自分自身を呼び出すことです。停止するためのベースケースを必ず持ってください。Java は末尾再帰を最適化しないため(一部の言語とは異なります)、深い再帰は StackOverflowError を引き起こす可能性があります。パフォーマンスが重要な、または深い再帰には反復に変換してください。メモ化(結果のキャッシュ)はフィボナッチのような再帰ソリューションを高速化できます。

java
// Factorial
public static int factorial(int n) {
    if (n <= 1) return 1;        // base case
    return n * factorial(n - 1); // recursive case
}
// factorial(5) = 5 * 4 * 3 * 2 * 1 = 120

// Fibonacci
public static int fib(int n) {
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
}

// Tail recursion (Java doesn't optimize this)
public static int factorialTail(int n, int acc) {
    if (n <= 1) return acc;
    return factorialTail(n - 1, n * acc);
}
// Call: factorialTail(5, 1)

// Be careful: deep recursion causes StackOverflowError
// For deep recursion, use iteration or a loop instead

Lambda 式

Lambda(Java 8+)は匿名関数です。型は関数型インターフェース(単一の抽象メソッド)です。一般的なもの:Function<T,R>(入力→出力)、Predicate<T>(boolean テスト)、Consumer<T>(消費、戻り値なし)、Supplier<T>(生成、入力なし)。メソッド参照(String::length)は単一メソッドを呼び出す lambda の短縮形です。

java
import java.util.function.*;

// Lambda syntax: (params) -> expression
Function<Integer, Integer> square = x -> x * x;
Function<String, Integer> length = s -> s.length();
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;

// With type annotations
BinaryOperator<Integer> multiply = (Integer a, Integer b) -> a * b;

// Multi-line lambda
Function<String, String> process = s -> {
    String upper = s.toUpperCase();
    return upper.substring(0, 3);
};

// Predicate (boolean test)
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<String> isEmpty = String::isEmpty; // method reference

// Consumer (no return)
Consumer<String> printer = s -> System.out.println(s);
Consumer<String> printer2 = System.out::println; // method reference

// Supplier (no input, produces value)
Supplier<Double> random = () -> Math.random();

// Usage
int result = square.apply(5); // 25
boolean even = isEven.test(4); // true
printer.accept("Hello"); // prints "Hello"
05

クラスと OOP

クラスとコンストラクタ

クラスはオブジェクトのテンプレートです。フィールドが状態を保持し、メソッドが振る舞いを定義します。コンストラクタは新しいオブジェクトを初期化します('this' でフィールドとパラメータを区別)。@Override はメソッドがスーパークラスのメソッドをオーバーライドすることを示します(toString は Object から)。カプセル化:private フィールド、public getter/setter。

java
public class Person {
    // Fields (instance variables)
    private String name;
    private int age;

    // Constructor
    public Person(String name, int age) {
        this.name = name;  // 'this' refers to the current instance
        this.age = age;
    }

    // Methods
    public String getName() { return name; }
    public int getAge() { return age; }

    public void setAge(int age) {
        if (age >= 0) this.age = age;
    }

    public String greet() {
        return "Hi, I'm " + name;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

// Usage
Person p = new Person("Alice", 30);
System.out.println(p.getName());  // "Alice"
System.out.println(p);            // uses toString()

アクセス修飾子と static

アクセス修飾子:public(どこでも)、private(クラスのみ)、protected(クラス + サブクラス + パッケージ)、default/package-private(同じパッケージ)。静的メンバーはインスタンスではなくクラスに属します — すべてのオブジェクトで共有されます。静的初期化子はクラスロード時に1回実行されます。定数(static final)、ユーティリティメソッド、カウンタに static を使用します。

java
public class BankAccount {
    // Access modifiers:
    public String owner;      // accessible everywhere
    private double balance;   // class only
    protected String type;    // class + subclasses + same package
    String id;                // package-private (default)

    // Static field (shared by all instances)
    private static int accountCount = 0;

    // Static constant
    public static final double MIN_BALANCE = 100.0;

    // Static method (call without instance)
    public static int getAccountCount() {
        return accountCount;
    }

    // Static initializer (runs once when class loads)
    static {
        System.out.println("BankAccount class loaded");
    }

    public BankAccount(String owner) {
        this.owner = owner;
        this.balance = MIN_BALANCE;
        accountCount++; // increment shared counter
    }
}

int count = BankAccount.getAccountCount(); // static method call

継承と super

Java はクラス継承に 'extends' を使用します(単一継承のみ)。super() は親コンストラクタを呼び出します(最初の行でなければなりません)。@Override はメソッドオーバーライドを示します(ランタイムポリモーフィズム)。Dog IS-A Animal。'is-a' 関係に継承を使用し、コード再利用には合成(has-a)を使用します。Java 17+ は継承を制限する sealed クラスをサポートします。

java
// Parent class
class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
        System.out.println("Animal constructor");
    }

    public void eat() {
        System.out.println(name + " is eating");
    }
}

// Child class (extends)
class Dog extends Animal {
    private String breed;

    public Dog(String name, String breed) {
        super(name); // must be first line — call parent constructor
        this.breed = breed;
    }

    // Override parent method
    @Override
    public void eat() {
        super.eat(); // call parent's eat()
        System.out.println(name + " the " + breed + " eats dog food");
    }

    public void bark() {
        System.out.println("Woof!");
    }
}

Dog dog = new Dog("Rex", "Labrador");
dog.eat();   // calls Dog's eat()
dog.bark();  // Dog-specific method

抽象クラスとインターフェース

抽象クラスはインスタンス化できず、抽象(本体なし)と具象メソッドの両方を持てます。インターフェースは契約を定義します — すべてのメソッドはデフォルトで public abstract です。Java 8+ はインターフェースでデフォルトメソッド(本体付き)と静的メソッドを許可します。クラスは1つの抽象クラスを拡張できますが、複数のインターフェースを実装できます。共有コードには抽象クラスを、契約にはインターフェースを使用します。

java
// Abstract class (can't be instantiated)
abstract class Shape {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    // Abstract method (must be implemented by subclasses)
    public abstract double area();

    // Concrete method (inherited)
    public String describe() {
        return color + " " + this.getClass().getSimpleName();
    }
}

// Interface (pure contract, Java 8+ can have default methods)
interface Drawable {
    void draw(); // abstract by default

    // Default method (Java 8+)
    default void drawTwice() {
        draw();
        draw();
    }

    // Static method in interface
    static Drawable empty() {
        return () -> System.out.println("nothing");
    }
}

// A class can extend one class and implement multiple interfaces
class Circle extends Shape implements Drawable {
    private double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public void draw() {
        System.out.println("Drawing " + describe());
    }
}

ポリモーフィズムとキャスト

ポリモーフィズム:親参照は子オブジェクトを保持できます。メソッド呼び出しは実際のオブジェクトの実装にディスパッチされます(ランタイムポリモーフィズム)。ClassCastException を回避するため、ダウンキャストの前に instanceof を使用します。Java 16+ のパターンマッチング(instanceof Circle c)はチェックとキャストを組み合わせます。コレクションでの正しい動作のために equals() と hashCode() を一緒にオーバーライドしてください。

java
// Polymorphism: one interface, many forms
Shape s1 = new Circle("red", 5);
Shape s2 = new Square("blue", 3);

// Calls the overridden method (runtime dispatch)
System.out.println(s1.area()); // Circle's area
System.out.println(s2.area()); // Square's area

// instanceof check
if (s1 instanceof Circle) {
    Circle c = (Circle) s1; // downcast
    System.out.println("Radius: " + c.radius);
}

// Pattern matching (Java 16+)
if (s1 instanceof Circle c) {
    System.out.println("Radius: " + c.radius); // c is already cast
}

// Upcasting (automatic)
Circle circle = new Circle("green", 2);
Shape shape = circle; // upcast (no explicit cast needed)

// Object class methods (all classes inherit from Object)
circle.equals(circle);   // reference equality by default
circle.hashCode();       // hash code
circle.getClass();       // Class<Circle>
circle.toString();       // string representation

レコードと Enum

レコード(Java 16+)は不変データクラスです — コンパイラがコンストラクタ、getter、equals、hashCode、toString を生成します。DTO と値オブジェクトに使用します。Enum は型安全な定数で、フィールド、メソッド、コンストラクタを持てます。Enum は Comparable を実装し、values() と valueOf() メソッドを持ちます。どちらもモダン Java に不可欠です。

java
// Record (Java 16+): concise data class
public record Point(int x, int y) {}

// Equivalent to a class with:
// - final fields x, y
// - constructor
// - getters x(), y()
// - equals, hashCode, toString

Point p = new Point(3, 4);
System.out.println(p.x());      // 3
System.out.println(p.y());      // 4
System.out.println(p);          // Point[x=3, y=4]

// Compact constructor (validation)
public record Age(int value) {
    public Age {
        if (value < 0 || value > 150) {
            throw new IllegalArgumentException("Invalid age");
        }
    }
}

// Enum (named constants)
public enum Direction {
    UP, DOWN, LEFT, RIGHT;

    public Direction opposite() {
        return switch (this) {
            case UP -> DOWN;
            case DOWN -> UP;
            case LEFT -> RIGHT;
            case RIGHT -> LEFT;
        };
    }
}

Direction d = Direction.UP;
Direction opp = d.opposite(); // DOWN
06

コレクションとジェネリクス

List(ArrayList と LinkedList)

ArrayList は配列をベースにします(get/set が高速、中間の挿入/削除が遅い)。LinkedList は双方向リンクリストをベースにします(両端の挿入/削除が高速、ランダムアクセスが遅い)。List.of() は不変リストを作成します。ほとんどのケースでは ArrayList を使用し、頻繁な終端操作にのみ LinkedList を使用します。どちらも List インターフェースを実装します。

java
import java.util.*;

// ArrayList (fast random access, slow insert/delete in middle)
List<String> list = new ArrayList<>();
list.add("Alice");
list.add("Bob");
list.add(0, "Carol");     // insert at index
list.set(1, "Dave");      // replace at index
String name = list.get(0); // "Carol"
list.remove(0);            // remove by index
list.remove("Dave");       // remove by value
int size = list.size();    // 1
boolean has = list.contains("Bob");

// LinkedList (fast insert/delete at ends)
LinkedList<Integer> linked = new LinkedList<>();
linked.addFirst(1);
linked.addLast(2);
linked.removeFirst();
linked.peek(); // see first element

// Immutable list (Java 9+)
List<String> immutable = List.of("a", "b", "c");
// immutable.add("d"); // UnsupportedOperationException

// Iterate
for (String s : list) {
    System.out.println(s);
}
list.forEach(System.out::println); // method reference

Set(HashSet と TreeSet)

Set は一意の要素を格納します。HashSet が最速ですが順序なしです。TreeSet は要素をソートします(自然順序または Comparator)。LinkedHashSet は挿入順序を維持します。セット操作:addAll(和)、retainAll(積)、removeAll(差)。HashSet でカスタムオブジェクトを使用する場合、equals() と hashCode() をオーバーライドしてください。

java
import java.util.*;

// HashSet (fast, unordered)
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("apple"); // duplicate ignored
System.out.println(set.size()); // 2
System.out.println(set.contains("apple")); // true
set.remove("banana");

// TreeSet (sorted, slower)
Set<Integer> sorted = new TreeSet<>();
sorted.add(3);
sorted.add(1);
sorted.add(2);
System.out.println(sorted); // [1, 2, 3]

// LinkedHashSet (maintains insertion order)
Set<String> ordered = new LinkedHashSet<>();
ordered.add("c");
ordered.add("a");
ordered.add("b");
System.out.println(ordered); // [c, a, b]

// Set operations
Set<Integer> a = new HashSet<>(Set.of(1, 2, 3));
Set<Integer> b = new HashSet<>(Set.of(2, 3, 4));
a.addAll(b);    // union: [1, 2, 3, 4]
a.retainAll(b); // intersection: [2, 3]
a.removeAll(b); // difference: [1]

// Immutable set
Set<String> immutable = Set.of("x", "y", "z");

Map(HashMap と TreeMap)

Map はキーと値のペアを格納します。HashMap が最速(順序なし)。TreeMap はキーでソート。LinkedHashMap は挿入順序を維持。getOrDefault は null チェックを回避します。compute/merge は値の更新に強力です。カスタムキーの場合、equals() と hashCode() をオーバーライドしてください。Map.of() は不変マップを作成します(Java 9+)。

java
import java.util.*;

// HashMap (fast, unordered)
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 30);
ages.put("Bob", 25);
ages.put("Alice", 31); // overwrite

// Access
int age = ages.get("Alice"); // 31
int defaultAge = ages.getOrDefault("Eve", 0); // 0

// Check
boolean has = ages.containsKey("Alice");
boolean hasVal = ages.containsValue(25);

// Remove
ages.remove("Bob");

// Iterate
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

ages.forEach((key, val) -> System.out.println(key + "=" + val));

// Compute (Java 8+)
ages.compute("Alice", (k, v) -> v + 1); // increment
ages.putIfAbsent("Carol", 28);
ages.merge("Alice", 1, Integer::sum); // add 1

// TreeMap (sorted by keys)
Map<String, Integer> sorted = new TreeMap<>();
// LinkedHashMap (maintains insertion order)
Map<String, Integer> ordered = new LinkedHashMap<>();

Queue と Deque

Queue は FIFO(先入れ先出し)です。Deque は両端(両端から追加/削除可能)。PriorityQueue は自然順序または Comparator で要素を順序付けします(デフォルトは最小ヒープ)。スタックにはレガシーの Stack クラスではなく ArrayDeque(push/pop)を使用します。Queue/Deque 操作には LinkedList より ArrayDeque が高速です。

java
import java.util.*;

// Queue (FIFO)
Queue<String> queue = new LinkedList<>();
queue.add("first");   // throws if full (capacity-restricted)
queue.offer("second"); // returns false if full
String head = queue.peek(); // see head (null if empty)
String removed = queue.poll(); // remove and return head

// Deque (double-ended)
Deque<Integer> deque = new ArrayDeque<>();
deque.addFirst(1);
deque.addLast(2);
deque.peekFirst(); // 1
deque.peekLast();  // 2
deque.pollFirst(); // 1
deque.pollLast();  // 2

// PriorityQueue (min-heap by default)
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.add(3);
pq.add(1);
pq.add(2);
System.out.println(pq.poll()); // 1 (smallest first)

// Max-heap (reverse order)
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
maxHeap.add(1);
maxHeap.add(3);
System.out.println(maxHeap.poll()); // 3 (largest first)

// Stack (legacy, prefer Deque)
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); // add to front
stack.push(2);
stack.pop();   // 2 (remove from front)

ジェネリクス

ジェネリクスは型安全なコレクションとクラスを可能にします。<T> は型パラメータです。境界付き型(<T extends Comparable<T>>)は特定の振る舞いを持つ型に制限します。ワイルドカード:?(任意)、? extends T(共変、読み取り専用)、? super T(反変、書き込み専用)。ジェネリクスは型消去を使用します — 型はコンパイル時にチェックされ、実行時に消去されます。

java
// Generic class
public class Box<T> {
    private T value;

    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

Box<String> stringBox = new Box<>();
stringBox.set("hello");
String s = stringBox.get();

Box<Integer> intBox = new Box<>();
intBox.set(42);

// Generic method
public static <T> T firstOf(List<T> list) {
    return list.get(0);
}

String first = firstOf(List.of("a", "b"));

// Bounded type parameter
public static <T extends Comparable<T>> T max(List<T> list) {
    T result = list.get(0);
    for (T item : list) {
        if (item.compareTo(result) > 0) {
            result = item;
        }
    }
    return result;
}

// Wildcards
void process(List<?> list) { }          // any type
void processNums(List<? extends Number> list) { } // Number or subclass
void addNums(List<? super Integer> list) { }      // Integer or superclass

イテレータと Comparable

Iterator は反復中の安全な削除を許可します(it.remove())。ListIterator は双方向トラバーサルと set/add を追加します。Comparable は自然順序を定義します(compareTo)。Comparator はカスタム順序を定義します(comparing、comparingInt、reversed、thenComparing)。流暢なソートには Comparator.comparing() を使用します。Collections.sort() は自然順序を使用します。

java
import java.util.*;

// Iterator
List<String> list = List.of("a", "b", "c");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    System.out.println(s);
    // it.remove(); // safe removal during iteration
}

// ListIterator (bidirectional)
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
    lit.set(lit.next().toUpperCase()); // replace
}

// Comparable (natural ordering)
public class Person implements Comparable<Person> {
    String name;
    int age;

    @Override
    public int compareTo(Person other) {
        return Integer.compare(this.age, other.age);
    }
}

// Comparator (custom ordering)
Comparator<Person> byName = Comparator.comparing(p -> p.name);
Comparator<Person> byAgeDesc = Comparator.comparingInt((Person p) -> p.age).reversed();

List<Person> people = new ArrayList<>();
people.sort(byName);
people.sort(byAgeDesc);
Collections.sort(people); // uses Comparable
07

ストリームと関数型

ストリームの基礎

ストリーム(Java 8+)は宣言的データ処理を提供します。.stream()(コレクション)または Stream.of() で作成。中間操作(filter、map、sorted)は遅延です — 終端操作(collect、reduce、count、forEach)が呼ばれた時にのみ実行されます。toList()(Java 16+)は collect(Collectors.toList()) の簡潔な代替です。

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

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

// Filter and collect
List<Integer> evens = nums.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList()); // [2, 4, 6]

// Map (transform)
List<String> doubled = nums.stream()
    .map(n -> "num" + n)
    .collect(Collectors.toList());

// Reduce
int sum = nums.stream().reduce(0, Integer::sum); // 21
int product = nums.stream().reduce(1, (a, b) -> a * b);

// Count
long count = nums.stream().filter(n -> n > 3).count(); // 3

// Find
Optional<Integer> first = nums.stream().filter(n -> n > 3).findFirst();
boolean anyMatch = nums.stream().anyMatch(n -> n > 5);
boolean allMatch = nums.stream().allMatch(n -> n > 0);

// ForEach
nums.stream().forEach(System.out::println);

// toList() shortcut (Java 16+)
List<Integer> result = nums.stream().filter(n -> n > 3).toList();

ストリーム操作

sorted() は要素を順序付けします(自然順序または Comparator)。distinct() は重複を削除。limit(n)/skip(n) はページネーション。flatMap はネストされたストリームを平坦化します — 1対多変換に不可欠。peek() はデバッグ用(副作用)。groupingBy は分類子で要素をグループ化するマップを作成します。ストリームは遅延です — 操作が効率的にチェーンされます。

java
List<String> names = List.of("Alice", "Bob", "Charlie", "David");

// Sorted
List<String> sorted = names.stream()
    .sorted()
    .toList();

// Sorted by length
List<String> byLength = names.stream()
    .sorted(Comparator.comparing(String::length))
    .toList();

// Distinct
List<Integer> distinct = List.of(1, 2, 2, 3, 3, 3).stream()
    .distinct()
    .toList(); // [1, 2, 3]

// Limit and Skip
List<Integer> limited = nums.stream()
    .skip(2)   // skip first 2
    .limit(3)  // take next 3
    .toList();

// FlatMap (flatten nested structures)
List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> flat = nested.stream()
    .flatMap(List::stream)
    .toList(); // [1, 2, 3, 4]

// Peek (debug, side-effect)
nums.stream()
    .peek(n -> System.out.println("before: " + n))
    .filter(n -> n > 2)
    .peek(n -> System.out.println("after: " + n))
    .toList();

// Grouping
Map<Integer, List<String>> byLength = names.stream()
    .collect(Collectors.groupingBy(String::length));

コレクタとリダクション

コレクタは豊富なリダクション操作を提供します:joining(文字列連結)、groupingBy(キーでグループ化)、partitioningBy(boolean で分割)、toMap(マップ作成)、summarizingInt(統計:count、sum、min、max、average)。コレクタは合成できます(ダウンストリームコレクタ付きの groupingBy)。これらは冗長なループを宣言的なワンライナーに置き換えます。

java
import java.util.stream.*;

List<Person> people = List.of(
    new Person("Alice", 30),
    new Person("Bob", 25),
    new Person("Charlie", 35)
);

// Join strings
String joined = people.stream()
    .map(Person::getName)
    .collect(Collectors.joining(", ")); // "Alice, Bob, Charlie"

// Group by
Map<Integer, List<Person>> byAge = people.stream()
    .collect(Collectors.groupingBy(Person::getAge));

// Partition (boolean)
Map<Boolean, List<Person>> partition = people.stream()
    .collect(Collectors.partitioningBy(p -> p.getAge() > 28));

// Count by group
Map<Integer, Long> countByAge = people.stream()
    .collect(Collectors.groupingBy(Person::getAge, Collectors.counting()));

// Summarizing
IntSummaryStatistics stats = people.stream()
    .collect(Collectors.summarizingInt(Person::getAge));
System.out.println(stats.getAverage()); // 30.0
System.out.println(stats.getMax());     // 35

// To map
Map<String, Integer> nameToAge = people.stream()
    .collect(Collectors.toMap(Person::getName, Person::getAge));

// Reducing
int totalAge = people.stream()
    .collect(Collectors.reducing(0, Person::getAge, Integer::sum));

Optional

Optional<T> は値を含むかもしれないし含まないかもしれないコンテナです。不在の明示的な処理を強制します — もう NullPointerException はありません。非 null 値には of() を、null の可能性がある場合は ofNullable() を使用します。map/flatMap/filter でチェーン。isPresent() なしで get() を使用しないでください — orElse/orElseThrow を優先。Optional はフィールドではなく戻り値型用に設計されています。

java
import java.util.Optional;

// Creating Optional
Optional<String> present = Optional.of("hello");
Optional<String> empty = Optional.empty();
Optional<String> nullable = Optional.ofNullable(null); // empty if null

// Checking
present.isPresent(); // true
empty.isEmpty();     // true (Java 11+)

// Getting values
String val = present.get(); // throws if empty (avoid!)
String safe = present.orElse("default");
String computed = present.orElseGet(() -> computeDefault());
String orThrow = present.orElseThrow(() -> new RuntimeException("missing"));

// Transform (map/flatMap)
Optional<Integer> length = present.map(String::length); // Optional[5]
Optional<String> upper = present.map(s -> s.toUpperCase());

// Filter
Optional<String> filtered = present.filter(s -> s.length() > 3);

// ifPresent
present.ifPresent(s -> System.out.println(s));
present.ifPresentOrElse(
    s -> System.out.println("Got: " + s),
    () -> System.out.println("Empty")
);

// Chaining (avoid null checks)
String result = getUser(1)
    .map(User::getProfile)
    .map(Profile::getEmail)
    .orElse("no email");

関数型インターフェース

関数型インターフェースは正確に1つの抽象メソッドを持ちます(複数のデフォルトメソッドを持てます)。@FunctionalInterface は任意ですが意図を文書化します。Java は java.util.function に多くを提供します:Function、Predicate、Consumer、Supplier、plus Bi- およびプリミティブバリアント。可能な場合はカスタムインターフェースの作成の代わりにこれらを使用します。これらは lambda 式とメソッド参照を可能にします。

java
import java.util.function.*;

// Built-in functional interfaces
Function<String, Integer> strToInt = Integer::parseInt;
BiFunction<String, String, String> concat = String::concat;

Predicate<String> isEmpty = String::isEmpty;
BiPredicate<String, String> contains = String::contains;

Consumer<String> printer = System.out::println;
BiConsumer<String, Integer> printPair = (s, i) -> System.out.println(s + ":" + i);

Supplier<List<String>> listFactory = ArrayList::new;

// Primitive specializations
IntFunction<String> intToStr = String::valueOf;
ToIntFunction<String> length = String::length;
IntPredicate isPositive = n -> n > 0;
IntConsumer intPrinter = System.out::println;
IntSupplier randomInt = () -> (int)(Math.random() * 100);

// Binary operators
BinaryOperator<Integer> max = Integer::max;
IntBinaryOperator sum = Integer::sum;

// Unary operators
UnaryOperator<String> trim = String::trim;
IntUnaryOperator negate = n -> -n;

// Custom functional interface
@FunctionalInterface
interface StringProcessor {
    String process(String input);

    // Can have default methods
    default StringProcessor andThen(StringProcessor after) {
        return input -> after.process(process(input));
    }
}
08

例外と I/O

Try / Catch / Finally

try/catch/finally は例外を処理します。finally は常に実行されます(クリーンアップに使用)。マルチキャッチ(catch A | B)は複数の例外を一緒に処理します。try-with-resources は AutoCloseable(ファイル、接続、ストリーム)を自動クローズします — 手動の finally クリーアップより優先されます。リソースは宣言の逆順でクローズされます。

java
try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("Math error: " + e.getMessage());
} catch (Exception e) {
    System.out.println("General error: " + e);
} finally {
    // Always runs (even if return/throw in try/catch)
    System.out.println("Cleanup");
}

// Multi-catch (Java 7+)
try {
    riskyOperation();
} catch (IOException | SQLException e) {
    // Handle both exceptions the same way
    log.error(e);
}

// Try-with-resources (auto-close, Java 7+)
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"));
     PrintWriter pw = new PrintWriter("output.txt")) {
    String line = br.readLine();
    pw.println(line);
} catch (IOException e) {
    e.printStackTrace();
}
// br and pw are auto-closed (in reverse order)

チェック例外と非チェック例外

チェック例外(Exception を継承)は 'throws' で宣言またはキャッチする必要があります — コンパイラが強制します。回復可能な条件(ファイルが見つからない、ネットワークエラー)に使用します。非チェック例外(RuntimeException を継承)は宣言不要です — プログラミングエラー(null ポインタ、無効な引数)に使用します。議論:チェック例外は処理を強制しますがコードを煩雑にします;多くのフレームワークは非チェックを好みます。

java
// Checked exceptions (must be declared or caught)
public void readFile(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    // IOException is checked — compiler enforces handling
}

// Unchecked exceptions (RuntimeException, no need to declare)
public int divide(int a, int b) {
    if (b == 0) {
        throw new IllegalArgumentException("Divisor cannot be zero");
        // RuntimeException — no 'throws' needed
    }
    return a / b;
}

// Common checked exceptions:
// IOException, SQLException, ClassNotFoundException

// Common unchecked exceptions:
// NullPointerException, IllegalArgumentException,
// IndexOutOfBoundsException, ArithmeticException,
// ClassCastException, IllegalStateException

// Custom checked exception
class DataException extends Exception {
    public DataException(String msg) { super(msg); }
}

// Custom unchecked exception
class ValidationException extends RuntimeException {
    public ValidationException(String msg) { super(msg); }
}

ファイル I/O(NIO.2)

NIO.2(java.nio.file)はモダンなファイル API です。Files.readString/writeString(Java 11+)はテキストに便利です。Files.lines() は遅延 Stream を返します — 大きなファイルに効率的(try-with-resources で閉じる必要あり)。Path.of() は古い File クラスを置き換えます。Files.createDirectories() はフルパスを作成します。常に IOException を処理してください。

java
import java.nio.file.*;
import java.io.*;

// Read entire file (small files)
List<String> lines = Files.readAllLines(Path.of("input.txt"));
String content = Files.readString(Path.of("config.json")); // Java 11+
byte[] bytes = Files.readAllBytes(Path.of("image.png"));

// Write file
Files.writeString(Path.of("output.txt"), "Hello, World!");
Files.write(Path.of("data.bin"), bytes);

// Append
Files.writeString(Path.of("log.txt"), "entry\n",
    StandardOpenOption.APPEND, StandardOpenOption.CREATE);

// Stream lines (large files, lazy)
try (Stream<String> lineStream = Files.lines(Path.of("large.txt"))) {
    lineStream.filter(l -> l.contains("ERROR"))
              .forEach(System.out::println);
}

// Copy, move, delete
Files.copy(Path.of("src.txt"), Path.of("dest.txt"));
Files.move(Path.of("old.txt"), Path.of("new.txt"));
Files.delete(Path.of("temp.txt"));

// Check existence
boolean exists = Files.exists(Path.of("file.txt"));

// Create directories
Files.createDirectories(Path.of("a/b/c"));

Reader と Writer(テキスト)

BufferedReader/Writer はテキスト I/O に効率的です(バッファリングがシステムコールを削減)。PrintWriter は printf スタイルのフォーマットを提供します。Scanner は入力を解析します(nextInt、nextDouble、nextLine)。InputStreamReader はバイトストリームを文字ストリームにブリッジします(非 UTF-8 の場合は charset を指定)。ストリームが確実に閉じられるように try-with-resources を常に使用してください。

java
import java.io.*;
import java.nio.file.*;

// BufferedReader (efficient text reading)
try (BufferedReader br = Files.newBufferedReader(Path.of("input.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}

// BufferedWriter (efficient text writing)
try (BufferedWriter bw = Files.newBufferedWriter(Path.of("output.txt"))) {
    bw.write("First line");
    bw.newLine();
    bw.write("Second line");
}

// PrintWriter (convenient formatting)
try (PrintWriter pw = new PrintWriter("output.txt")) {
    pw.println("Hello");
    pw.printf("Name: %s, Age: %d%n", "Alice", 30);
}

// Scanner (parsing input)
try (Scanner sc = new Scanner(Path.of("data.txt"))) {
    while (sc.hasNextLine()) {
        String line = sc.nextLine();
        // Parse tokens
        Scanner lineSc = new Scanner(line);
        if (lineSc.hasNextInt()) {
            int n = lineSc.nextInt();
        }
    }
}

// InputStreamReader (bytes to chars, e.g., from InputStream)
Reader reader = new InputStreamReader(System.in);

日付と時刻(java.time)

java.time(Java 8+)はモダンな日付/時刻 API で、古い Date/Calendar を置き換えます。LocalDate(日付のみ)、LocalTime(時刻のみ)、LocalDateTime(両方)、ZonedDateTime(タイムゾーン付き)。すべて不変でスレッドセーフです。日付差には Period、時間差には Duration を使用します。解析/フォーマットには DateTimeFormatter。機械タイムスタンプ(UTC)には Instant。

java
import java.time.*;
import java.time.format.*;
import java.time.temporal.*;

// Current date/time
LocalDate today = LocalDate.now();      // 2024-01-15
LocalTime now = LocalTime.now();        // 14:30:45.123
LocalDateTime dt = LocalDateTime.now(); // both
ZonedDateTime zdt = ZonedDateTime.now(); // with timezone

// Create specific
LocalDate date = LocalDate.of(2024, 1, 15);
LocalTime time = LocalTime.of(14, 30, 0);
LocalDateTime specific = LocalDateTime.of(2024, 1, 15, 14, 30);

// Parsing and formatting
LocalDate parsed = LocalDate.parse("2024-01-15");
String formatted = date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
// "15/01/2024"

// Manipulation (immutable, returns new)
LocalDate tomorrow = today.plusDays(1);
LocalDate lastMonth = today.minusMonths(1);
LocalDate nextYear = today.plusYears(1);

// Period (date-based)
Period age = Period.between(LocalDate.of(1990, 1, 1), today);
System.out.println(age.getYears()); // 34

// Duration (time-based)
Duration dur = Duration.between(time, LocalTime.now());
System.out.println(dur.toMinutes());

// Instant (machine time, UTC)
Instant instant = Instant.now();
Instant epoch = Instant.ofEpochSecond(0);

並行性の基礎

Java の並行性:Thread(低レベル)、ExecutorService(スレッドプール — 推奨)、CompletableFuture(非同期合成、Promise のような)。parallelStream() は並列処理に ForkJoinPool を使用します。synchronized ブロックは共有状態を保護します。Atomic 変数(AtomicInteger など)はロックフリーのスレッドセーフ操作を提供します。複雑な並行性には java.util.concurrent コレクション(ConcurrentHashMap、BlockingQueue)を使用します。

java
import java.util.concurrent.*;

// Create a thread
Thread thread = new Thread(() -> {
    System.out.println("Running in: " + Thread.currentThread().getName());
});
thread.start();
thread.join(); // wait for completion

// ExecutorService (thread pool)
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<Integer> future = executor.submit(() -> {
    Thread.sleep(1000);
    return 42;
});
Integer result = future.get(); // blocks until done
executor.shutdown();

// CompletableFuture (async, Java 8+)
CompletableFuture<String> cf = CompletableFuture
    .supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World")
    .thenApply(String::toUpperCase);
String asyncResult = cf.join(); // "HELLO WORLD"

// Parallel stream
List<Integer> nums = List.of(1, 2, 3, 4, 5);
int sum = nums.parallelStream().mapToInt(Integer::intValue).sum();

// Synchronized
synchronized (this) {
    // only one thread at a time
}

// Atomic variables
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
counter.compareAndSet(0, 1);
09

Lambda 式

Lambda 構文の基礎

Lambda(Java 8+)は関数型インターフェースの簡潔な実装です。構文:(params) -> expression または (params) -> { statements; }。コンパイラはターゲット型からパラメータ型を推論します。単一パラメータの lambda は括弧を省略でき、ゼロパラメータは空の括弧が必要です。Lambda は関数型プログラミングを可能にし、Streams API の基盤です。

java
// Anonymous class (verbose, pre-Java 8)
Runnable r1 = new Runnable() {
    public void run() {
        System.out.println("Old way");
    }
};

// Lambda expression (Java 8+)
Runnable r2 = () -> System.out.println("Lambda");

// With parameters and body
Comparator<Integer> cmp = (a, b) -> {
    int diff = a - b;
    return diff;
};

// Type inference (omit types)
Comparator<Integer> cmp2 = (a, b) -> a - b;

// Single param, no parentheses needed
Consumer<String> printer = s -> System.out.println(s);

// Zero params need empty parens
Runnable noop = () -> {};

関数型インターフェース

関数型インターフェースは正確に1つの抽象メソッドを持ちます(SAM 型)。@FunctionalInterface アノテーションはコンパイラにこれを強制させます。Lambda は関数型インターフェースのみをターゲットにできます。デフォルトメソッドと静的メソッドは許可され、単一メソッドルールを壊しません。これが Java の型システムで lambda を機能させる基盤です。

java
// Functional interface: exactly one abstract method
@FunctionalInterface
interface MathOperation {
    int operate(int a, int b);
}

// Implement with lambda
MathOperation add = (a, b) -> a + b;
MathOperation mul = (a, b) -> a * b;
MathOperation max = (a, b) -> Math.max(a, b);

int result = add.operate(3, 4);  // 7
int m = mul.operate(3, 4);       // 12

// @FunctionalInterface is optional but recommended
// It prevents accidentally adding a second abstract method
// Default and static methods don't count toward the limit
interface StringProcessor {
    String process(String s);
    default StringProcessor andThen(StringProcessor next) {
        return s -> next.process(this.process(s));
    }
}

組み込み関数型インターフェース

java.util.function は約40の既製関数型インターフェースを提供するため、独自のものを書くことはほとんどありません。コア4つ:Function(変換)、Predicate(テスト)、Consumer(消費)、Supplier(生成)。Bi- バリアントは2つの引数を取ります。プリミティブバリアント(IntFunction、ToIntFunction など)はボクシングオーバーヘッドを回避します。カスタムインターフェースの作成の代わりにこれらを使用します。

java
import java.util.function.*;

// Function<T,R>: input -> output
Function<String, Integer> len = String::length;
int n = len.apply("hello");  // 5

// Predicate<T>: input -> boolean (for filtering)
Predicate<String> isEmpty = String::isEmpty;
boolean e = isEmpty.test("");  // true

// Consumer<T>: input -> void (side effects)
Consumer<String> print = System.out::println;
print.accept("hi");

// Supplier<T>: no input -> output (factories, lazy)
Supplier<Double> random = Math::random;
double r = random.get();

// BiFunction<T,U,R>: two inputs -> output
BiFunction<String, Integer, String> repeat =
    (s, i) -> s.repeat(i);

// Primitives variants avoid boxing
IntFunction<String> f = i -> "n=" + i;
IntPredicate positive = i -> i > 0;
ToIntFunction<String> length = String::length;
IntBinaryOperator sum = (a, b) -> a + b;

メソッド参照

メソッド参照(::)は単一メソッドを呼び出すだけの lambda の短縮形です。4種類:静的(Class::static)、バインド済みインスタンス(obj::method)、非バインド済みインスタンス(Class::method — 最初のパラメータがレシーバになる)、コンストラクタ(Class::new)。lambda が1つのメソッドに転送するだけの場合は使用してください — より読みやすいです。そうでなければ明示的な lambda を使用します。

java
import java.util.*;

List<String> names = List.of("alice", "bob", "charlie");

// Lambda form
names.forEach(s -> System.out.println(s));
// Method reference (shorthand)
names.forEach(System.out::println);

// Four kinds of method references:

// 1. Static method: ClassName::staticMethod
names.stream().map(String::toUpperCase);

// 2. Instance method of particular object: instance::method
var printer = System.out;
names.forEach(printer::println);

// 3. Instance method of arbitrary object: ClassName::instanceMethod
List<String> upper = names.stream()
    .map(String::toUpperCase)
    .toList();

// 4. Constructor: ClassName::new
Supplier<ArrayList<String>> factory = ArrayList::new;
ArrayList<String> list = factory.get();

変数のキャプチャ(実質的 final)

Lambda はローカル変数をキャプチャできますが、final または「実質的 final」(再代入されない)でなければなりません。これは lambda がスタックフレームより長生きする可能性があるためです。回避するには、単一要素配列や AtomicInteger/ホルダーオブジェクトを使用します。インスタンスフィールドと静的フィールドにはこの制限はありません。lambda 内の 'this' は lambda 自身ではなく囲むクラスインスタンスを指します。

java
import java.util.function.*;

int x = 10;
// Capturing a local variable — must be final or effectively final
Supplier<Integer> getter = () -> x * 2;
System.out.println(getter.get());  // 20

// x = 20;  // ERROR: would break the lambda capture
// Local variables captured by lambdas must be final/effectively final

// Workaround: use an array or wrapper (mutable container)
int[] counter = {0};
Runnable inc = () -> counter[0]++;
inc.run();
inc.run();
System.out.println(counter[0]);  // 2

// Instance/static fields CAN be modified (no restriction)
class Holder {
    int value = 0;
    Runnable bump = () -> value++;  // OK, field access
}

// 'this' inside a lambda refers to the enclosing instance
class Outer {
    String name = "Outer";
    Runnable r = () -> System.out.println(this.name);  // "Outer"
}

コンストラクタ参照

コンストラクタ参照(ClassName::new)は簡潔に新しいインスタンスを作成します。結果型を選択する Collectors.toCollection()、配列作成(Type[]::new)、ファクトリパターンで便利です。レコードと不変オブジェクトの場合、コンストラクタ参照はコピーを構築する慣用的な方法です。Function/Supplier ターゲットと自然にペアになります。

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

// Supplier constructor reference
Supplier<StringBuilder> sbFactory = StringBuilder::new;
StringBuilder sb = sbFactory.get();

// Function constructor reference (with one arg)
Function<String, StringBuilder> sbFromString = StringBuilder::new;
StringBuilder named = sbFromString.apply("Hello");

// In streams: collect into a specific collection
List<String> names = List.of("a", "b", "c");
ArrayList<String> copy = names.stream()
    .collect(Collectors.toCollection(ArrayList::new));

// Array constructor reference
IntFunction<String[]> arrayFactory = String[]::new;
String[] arr = arrayFactory.apply(5);  // new String[5]

// Copying via constructor
record Point(int x, int y) {}
Function<Point, Point> copyCtor = Point::new;
Point p = copyCtor.apply(new Point(1, 2));
10

Optional と Null 安全性

Optional の作成

Optional は値を保持するかもしれないししないかもしれないコンテナです。値なしには empty()、値が非 null と確信できる場合は of()(そうでなければ NPE をスロー)、null の可能性がある場合は ofNullable() を使用します。Optional は呼び出し元に不在ケースの明示的な処理を強制します。Optional が期待される場所で null を返さないでください — それは目的を損ないます。

java
import java.util.Optional;

// empty() — no value
Optional<String> empty = Optional.empty();

// of() — value must be non-null (throws NPE if null)
Optional<String> present = Optional.of("hello");

// ofNullable() — accepts null safely
Optional<String> maybe = Optional.ofNullable(getName());

// From a stream that may produce 0 or 1 elements
Optional<Integer> first = List.of(1, 2, 3).stream().findFirst();

// Common helper pattern
public Optional<User> findUser(long id) {
    User u = db.lookup(id);
    return Optional.ofNullable(u);
}

String getName() { return Math.random() > 0.5 ? "Alice" : null; }
class User {}
class Db { User lookup(long id) { return null; } }
Db db = new Db();

値の安全な消費

isPresent+get より ifPresent/ifPresentOrElse を優先します。orElse は定数デフォルトを返し、orElseGet は Supplier を取るためデフォルトが遅延計算されます(デフォルトが高コストな場合に重要)。orElseThrow は不在を例外に変換します。目標は .get() を盲目的に呼ばないことです — それは Optional が排除しようとした NPE リスクを再導入します。

java
import java.util.Optional;

Optional<String> name = Optional.of("Alice");

// isPresent / isEmpty (Java 11+)
if (name.isPresent()) {
    System.out.println(name.get());  // "Alice"
}
// Avoid .get() without checking — throws NoSuchElementException

// ifPresent: run action only if value exists
name.ifPresent(System.out::println);

// ifPresentOrElse (Java 9+)
name.ifPresentOrElse(
    System.out::println,
    () -> System.out.println("No name")
);

// orElse: provide default
String s1 = name.orElse("Anonymous");

// orElseGet: lazy default (computed only if needed)
String s2 = name.orElseGet(() -> expensiveDefault());

// orElseThrow: throw if absent
String s3 = name.orElseThrow(() -> new IllegalStateException("missing"));

String expensiveDefault() { return "computed"; }

map と flatMap による変換

map は含まれる値を変換します(Optional<T> -> Optional<R>)。flatMap はマッピング関数自体が Optional を返す場合に使用され、ネストされた Optional を防ぎます。filter は述語が一致する場合のみ値を保持します。map/filter/flatMap のチェーンにより、最初の空でショートサーキットするパイプラインを構築できます — ネストされた null チェックよりはるかにクリーンです。

java
import java.util.Optional;

Optional<String> name = Optional.of("Alice");

// map: transform the value if present
Optional<Integer> length = name.map(String::length);  // Optional[5]
Optional<String> upper = name.map(String::toUpperCase);

// flatMap: when the transform itself returns Optional
// (avoids Optional<Optional<T>>)
public Optional<String> findEmail(long id) {
    return Optional.ofNullable(db.get(id));
}
Optional<String> email = Optional.of(1L)
    .flatMap(this::findEmail);  // Optional<email> not Optional<Optional<email>>

// filter: keep only if predicate matches
Optional<Integer> adultAge = Optional.of(25)
    .filter(a -> a >= 18);  // Optional[25]
Optional<Integer> kid = Optional.of(10)
    .filter(a -> a >= 18);  // Optional.empty

// Chain transformations
String label = Optional.of("alice")
    .map(String::strip)
    .filter(s -> !s.isEmpty())
    .map(s -> s.substring(0, 1).toUpperCase() + s.substring(1))
    .orElse("unknown");  // "Alice"

class Db { String get(long id) { return "[email protected]"; } }
Db db = new Db();
Optional<String> findEmail(long id) { return Optional.ofNullable(db.get(id)); }

回避すべきアンチパターン

Optional はフィールドやパラメータではなく戻り値型用に設計されています。Serializable ではなく、フィールドとしてオーバーヘッドを追加します。チェックなしで .get() を使用せず、isPresent()+get() も使用しないでください — それは単に冗長な null チェックです。コレクションは既に空を表現するため、Optional でラップしないでください。Optional を値が不在かもしれないという戻り値型シグナルとして使用します。

java
import java.util.Optional;

// BAD: using Optional for fields (not serializable, wastes memory)
class Bad {
    private Optional<String> name;  // DON'T
}

// GOOD: use plain field, return Optional from accessor
class Good {
    private String name;
    public Optional<String> getName() { return Optional.ofNullable(name); }
}

// BAD: Optional as method parameter (clutters API)
public void process(Optional<String> input) {}  // DON'T

// GOOD: method overloading or nullable param
public void process(String input) {}
public void process() { process(null); }

// BAD: .get() without check
String x = findName().get();  // throws if empty

// BAD: .isPresent() + .get() — defeats the purpose
Optional<String> opt = findName();
if (opt.isPresent()) {
    use(opt.get());  // just use ifPresent or map instead
}

// BAD: returning Optional from collections
public Optional<Item> find(...) {
    // Collections already express emptiness — return empty List, not Optional<List>
    return Optional.ofNullable(items);
}

ストリームとの Optional

Optional.stream()(Java 9+)は0または1要素の Stream を生成し、ストリームから Optional をエレガントに flatMap できます。これはストリーム処理中に不在値をスキップする最もクリーンな方法です。冗長な filter(isPresent).map(get) パターンを回避し、パイプラインを宣言的に保ちます。

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

// stream() on Optional: 0 or 1 element stream
Optional<String> opt = Optional.of("hi");
opt.stream().forEach(System.out::println);

// Useful: flatMap Optional out of a stream
class User {
    String email;  // may be null
    User(String e) { email = e; }
    Optional<String> getEmail() { return Optional.ofNullable(email); }
}

List<User> users = List.of(
    new User("[email protected]"),
    new User(null),
    new User("[email protected]")
);

// Extract emails, skipping nulls — clean with Optional::stream
List<String> emails = users.stream()
    .flatMap(u -> u.getEmail().stream())
    .toList();  // [[email protected], [email protected]]

// Without Optional::stream you'd need filter+map
List<String> emails2 = users.stream()
    .map(User::getEmail)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .toList();
11

Streams API ディープダイブ

ストリームの作成

ストリームはコレクション、配列、静的ファクトリから作成できます。iterate() と generate() は無限ストリームを生成します — 常に limit() を続けます。述語付き iterate(Java 9+)は単純な iterate より安全です。IntStream/LongStream/DoubleStream は数値作業のボクシングを回避します。ストリームは使い捨てです:一度終端操作が実行されると、ストリームは消費されます。

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

// From collections
Stream<String> s1 = List.of("a", "b").stream();
Stream<String> s2 = Set.of("x").stream();

// From arrays
int[] nums = {1, 2, 3};
IntStream s3 = Arrays.stream(nums);
Stream<String> s4 = Arrays.stream(new String[]{"a", "b"});

// Static factory methods
Stream<Integer> s5 = Stream.of(1, 2, 3);
Stream<Integer> s6 = Stream.empty();
Stream<Integer> s7 = Stream.iterate(1, n -> n * 2);  // infinite
Stream<Integer> s8 = Stream.iterate(1, n -> n < 100, n -> n + 1);  // bounded (Java 9+)
Stream<Double> s9 = Stream.generate(Math::random);  // infinite
Stream<String> s10 = Stream.ofNullable(null);  // 0 or 1 element (Java 9+)

// From functions (infinite, must limit)
List<Integer> powers = Stream.iterate(1, n -> n * 2)
    .limit(10)
    .toList();

// Numeric ranges
IntStream range = IntStream.range(0, 5);       // 0,1,2,3,4
IntStream closed = IntStream.rangeClosed(1, 5); // 1,2,3,4,5

中間操作

中間操作は遅延です — 終端操作が呼び出されるまで実行されません。filter は要素を保持し、map は1:1で変換し、flatMap は1:多に変換します。distinct/sorted/limit/skip はステートフルです。takeWhile/dropWhile(Java 9+)は最初の非マッチング要素で停止します(すべてをスキャンする filter とは異なります)。本番の副作用ではなくデバッグに peek を使用します。

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

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

// filter: keep matching
List<Integer> evens = nums.stream()
    .filter(n -> n % 2 == 0).toList();

// map: transform
List<String> labels = nums.stream()
    .map(n -> "n=" + n).toList();

// flatMap: one-to-many
List<Integer> expanded = List.of(List.of(1, 2), List.of(3))
    .stream().flatMap(List::stream).toList();  // [1,2,3]

// distinct: remove duplicates
List<Integer> uniq = nums.stream().distinct().toList();

// sorted
List<Integer> asc = nums.stream().sorted().toList();
List<Integer> desc = nums.stream().sorted(Comparator.reverseOrder()).toList();

// peek: inspect (mainly for debugging)
nums.stream().peek(n -> System.out.println("seen " + n)).count();

// limit / skip
List<Integer> first3 = nums.stream().limit(3).toList();
List<Integer> after2 = nums.stream().skip(2).toList();

// takeWhile / dropWhile (Java 9+)
List<Integer> lt5 = nums.stream().takeWhile(n -> n < 5).toList();

コレクタ:grouping と partitioning

Collectors.groupingBy は Java ストリームの SQL GROUP BY です。分類子関数がキーを定義し、オプションのダウンストリームコレクタが各グループを処理します(counting、summing、mapping など)。partitioningBy は boolean 述語の特殊ケース(正確に2つのバケット)です。ソート済みキーには TreeMap サプライヤを渡します。これらは強力に合成できます — マルチレベルグループ化を構築できます。

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

record Person(String name, String city, int age) {}

List<Person> people = List.of(
    new Person("Alice", "NYC", 30),
    new Person("Bob", "LA", 25),
    new Person("Carol", "NYC", 35),
    new Person("Dave", "LA", 40)
);

// groupingBy: Map<key, List<item>>
Map<String, List<Person>> byCity = people.stream()
    .collect(Collectors.groupingBy(Person::city));
// {NYC=[Alice,Carol], LA=[Bob,Dave]}

// groupingBy with downstream collector
Map<String, Long> countByCity = people.stream()
    .collect(Collectors.groupingBy(Person::city, Collectors.counting()));

Map<String, Integer> sumAgeByCity = people.stream()
    .collect(Collectors.groupingBy(Person::city,
        Collectors.summingInt(Person::age)));

Map<String, List<String>> namesByCity = people.stream()
    .collect(Collectors.groupingBy(Person::city,
        Collectors.mapping(Person::name, Collectors.toList())));

// partitioningBy: Map<Boolean, List> (2 buckets)
Map<Boolean, List<Person>> byAge = people.stream()
    .collect(Collectors.partitioningBy(p -> p.age() >= 30));

// groupingBy with TreeMap for sorted keys
Map<String, List<Person>> sorted = people.stream()
    .collect(Collectors.groupingBy(Person::city, TreeMap::new, Collectors.toList()));

リダクションと統計

reduce はすべての要素を単一値に結合します — 空ストリームの安全性のためにアイデンティティを提供します。summaryStatistics は1パスで count/sum/min/max/avg を提供します。Collectors.joining はデリミタ付き文字列の構築に便利です。teeing(Java 12+)は2つのコレクタを並列実行し結果をマージします — 1パスで2つの集計(min と max など)が必要な場合に便利です。

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

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

// reduce: combine all into one
int sum = nums.stream().reduce(0, Integer::sum);          // 15
Optional<Integer> product = nums.stream().reduce((a, b) -> a * b);  // Optional[120]

// Built-in summary collectors
IntSummaryStatistics stats = nums.stream()
    .mapToInt(Integer::intValue)
    .summaryStatistics();
// stats.getCount(), getSum(), getMin(), getMax(), getAverage()

// Common terminal collectors
long count = nums.stream().collect(Collectors.counting());
double avg = nums.stream().collect(Collectors.averagingInt(Integer::intValue));
int max = nums.stream().collect(Collectors.maxBy(Comparator.naturalOrder())).orElse(0);

// join strings
String joined = List.of("a", "b", "c").stream()
    .collect(Collectors.joining(", ", "[", "]"));  // "[a, b, c]"

// teeing (Java 12+): two collectors, merge results
record Range(int min, int max) {}
Range range = nums.stream().collect(Collectors.teeing(
    Collectors.minBy(Comparator.naturalOrder()),
    Collectors.maxBy(Comparator.naturalOrder()),
    (mn, mx) -> new Range(mn.orElse(0), mx.orElse(0))
));

数値ストリーム

IntStream/LongStream/DoubleStream はプリミティブ特殊化で、ボクシングオーバーヘッドを回避します — 数値作業に使用します。mapToInt/mapToLong/mapToDouble はオブジェクトストリームをプリミティブストリームに変換し、boxed() は戻します。プリミティブストリームは特殊化された終端操作(sum、average、max)を持ち、空ストリームを処理するために OptionalInt/Double を返します。パフォーマンスが重要な数値パイプラインに最適です。

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

// IntStream / LongStream / DoubleStream avoid boxing
IntStream range = IntStream.rangeClosed(1, 100);
int sum = range.sum();                       // 5050
double avg = IntStream.of(1, 2, 3).average().orElse(0);

// mapToInt / mapToLong / mapToDouble from object stream
int totalAge = people().stream().mapToInt(Person::age).sum();

// boxed: convert primitive stream back to object stream
List<Integer> list = IntStream.range(0, 5).boxed().toList();

// mapToObj: primitive -> objects
List<String> labels = IntStream.range(1, 4)
    .mapToObj(i -> "item-" + i).toList();

// asLongStream / asDoubleStream
LongStream longs = IntStream.range(0, 5).asLongStream();

// Common numeric operations
int max = IntStream.of(3, 1, 4, 1, 5).max().orElse(Integer.MIN_VALUE);
boolean anyEven = IntStream.of(1, 3, 5).anyMatch(n -> n % 2 == 0);

// Iterate to build numeric sequences
List<Integer> fib = Stream.iterate(new int[]{0, 1}, a -> new int[]{a[1], a[0] + a[1]})
    .limit(10).mapToInt(a -> a[0]).boxed().toList();

List<Person> people() { return List.of(new Person("a", "c", 30)); }
record Person(String name, String city, int age) {}

並列ストリーム

parallelStream は共通 ForkJoinPool(CPU コア数にサイズ設定)全体で作業を分割します。大規模データセットで CPU 集約的、ステートレス、順序非依存の操作にのみ使用します — 小さなデータではオーバーヘッドが利益を上回ります。共有された可変状態を回避します(競合を引き起こす)。並列ストリームの I/O は共有プールをブロックします — ブロッキング作業にはカスタム ForkJoinPool を使用します。並列が速いと想定する前に測定してください。

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

// parallelStream: uses common ForkJoinPool
long sum = List.of(1, 2, 3, 4, 5).parallelStream()
    .mapToInt(Integer::intValue).sum();

// Convert sequential to parallel
long count = IntStream.range(0, 1_000_000).parallel()
    .filter(n -> n % 2 == 0).count();

// Order may differ — use forEachOrdered if order matters
List.of(1, 2, 3, 4).parallelStream()
    .forEachOrdered(System.out::println);

// Collecting preserves encounter order (but work is parallel)
List<Integer> doubled = IntStream.range(0, 1000).parallel()
    .map(n -> n * 2).boxed().toList();

// Custom thread pool (avoid blocking the common pool)
import java.util.concurrent.ForkJoinPool;
ForkJoinPool pool = new ForkJoinPool(8);
int result = pool.submit(() ->
    IntStream.range(0, 1000).parallel().sum()
).get();

// WHEN to use parallel: large dataset, CPU-heavy per element,
// order-independent, stateless operations
// WHEN NOT: small data, I/O-bound, shared mutable state, ordered ops
12

ジェネリクス ディープダイブ

ジェネリッククラスとメソッド

ジェネリクスは型安全な再利用可能コードを可能にします。クラスは型パラメータ(<T>)を宣言し、メソッドもできます(戻り型の前の <T>)。ダイヤモンド演算子 <> は構築時に型を推論します。ジェネリクスはコンパイル時にチェックされます — ClassCastException を介して実行時ではなく早期に型エラーをキャッチすることでコレクションと API を安全にします。

java
// Generic class
public class Box<T> {
    private T value;
    public void set(T v) { value = v; }
    public T get() { return value; }
}

Box<String> strBox = new Box<>();
strBox.set("hello");
String s = strBox.get();  // no cast needed

// Multiple type parameters
public class Pair<K, V> {
    private final K key;
    private final V value;
    public Pair(K k, V v) { key = k; value = v; }
    public K key() { return key; }
    public V value() { return value; }
}

Pair<String, Integer> p = new Pair<>("age", 30);

// Generic method (independent of class type params)
public static <T> T first(List<T> list) {
    return list.get(0);
}

// Generic method with multiple type params
public static <K, V> Map<K, V> zip(List<K> keys, List<V> values) {
    Map<K, V> m = new HashMap<>();
    for (int i = 0; i < keys.size(); i++) m.put(keys.get(i), values.get(i));
    return m;
}

境界付き型パラメータ

境界付き型パラメータ(<T extends Bound>)は使用できる型を制限し、バインドのメソッドを呼び出せます。<T extends Number> は T が Number またはサブタイプでなければならないことを意味します。複数境界は & を使用します — 最大1つのクラス(最初でなければならない)、残りはインターフェース。特定の機能(比較可能性、数値操作)が必要なアルゴリズムの作成に不可欠です。

java
// Upper bound: T must be a subtype of Number
public static <T extends Number> double sum(List<T> nums) {
    double total = 0;
    for (Number n : nums) total += n.doubleValue();
    return total;
}

sum(List.of(1, 2, 3));        // Integer is a Number
sum(List.of(1.0, 2.5));       // Double is a Number
// sum(List.of("a"));          // compile error

// Multiple bounds: T must extend all (first is class, rest interfaces)
interface Comparable<T> { int compareTo(T o); }
interface Serializable {}

public static <T extends Number & Comparable<T> & Serializable>
    T max(List<T> list) {
    T best = list.get(0);
    for (T t : list) if (t.compareTo(best) > 0) best = t;
    return best;
}

// Bound lets you call methods of the bound
public static <T extends CharSequence> int totalLength(List<T> items) {
    int len = 0;
    for (CharSequence c : items) len += c.length();  // can call .length()
    return len;
}

ワイルドカード:?、extends、super

ワイルドカードはジェネリック型を柔軟にします。? extends T(共変)は T を読み取れるが書き込めません — プロデューサに使用します。? super T(反変)は T を書き込めるが Object としてのみ読み取れます — コンシューマに使用します。PECS ルール(Producer Extends, Consumer Super)がどちらを使用するかをガイドします。copy(dest, src) が古典的な例です:dest はコンシューマ(super)、src はプロデューサ(extends)。

java
import java.util.*;

// ? (unbounded wildcard) — any type
void printAll(List<?> list) {
    for (Object o : list) System.out.println(o);
}

// ? extends T (upper-bounded / covariant) — producer (PECS: Producer Extends)
double sum(List<? extends Number> nums) {
    double total = 0;
    for (Number n : nums) total += n.doubleValue();
    return total;
}
sum(List.of(1, 2, 3));     // List<Integer> OK
sum(List.of(1.0, 2.0));    // List<Double> OK
// nums.add(5);  // ERROR: can't add (don't know exact type)

// ? super T (lower-bounded / contravariant) — consumer (PECS: Consumer Super)
void addNumbers(List<? super Integer> list) {
    list.add(1); list.add(2); list.add(3);  // OK to add Integer
}
addNumbers(new ArrayList<Number>());  // OK
addNumbers(new ArrayList<Object>());  // OK
// Number n = list.get(0);  // only safe to read as Object

// PECS rule: Producer Extends, Consumer Super
// If you read from a collection, use ? extends T
// If you write to a collection, use ? super T
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (T t : src) dest.add(t);
}

型消去

Java のジェネリクスは型消去を使用します — ジェネリック型はコンパイル時にのみ存在し、実行時には List<String> と List<Integer> は両方とも単なる List です。これは後方互換性を可能にしますが制限があります:new T()、ジェネリック配列の作成、ジェネリックでの instanceof、同じ消去シグネチャのオーバーロードメソッドはできません。ヒープ汚染は未チェックキャストが間違った型をジェネリクスに入れ、エラーを実行時に延期する時に発生します。

java
import java.util.*;

// At runtime, generic types are erased to their bounds (or Object)
// List<String>, List<Integer>, List<?> all become List at runtime

List<String> strings = new ArrayList<>();
List<Integer> ints = new ArrayList<>();
// Runtime: both are just ArrayList

// You CANNOT do these due to erasure:
// new T()              — can't instantiate type param
// new T[]              — can't create generic array
// instanceof List<String>  — only instanceof List (raw)
// class MyException<T> extends Exception  — can't extend Throwable generically
// static T field       — no static generic fields

// Erasure means overloads clash:
// void process(List<String> list) {}
// void process(List<Integer> list) {}  // ERROR: same erasure

// Checking types at runtime requires Class<T>
public static <T> List<T> filter(List<?> items, Class<T> type) {
    List<T> result = new ArrayList<>();
    for (Object o : items) {
        if (type.isInstance(o)) result.add(type.cast(o));
    }
    return result;
}

// Heap pollution: when unchecked warnings lead to runtime ClassCastException
List<String> polluted = (List<String>)(List) List.of(1, 2);  // unchecked
// String s = polluted.get(0);  // ClassCastException at runtime

ジェネリックメソッドと推論

型推論によりコンパイラはコンテキスト(引数とターゲット型)から型引数を決定するため、明示的に書くことはほとんどありません。ダイヤモンド演算子 <> はコンストラクタの推論です。ターゲットタイピングは変数の期待型を使用します。推論が曖昧さを解決できない場合のみ明示的な型ウィットネス(Class.<T>method())を使用します。推論によりジェネリックコードは非ジェネリックコードと同様にクリーンに読めます。

java
import java.util.*;

// Type inference: compiler figures out T from arguments
public static <T> T pick(T a, T b) { return Math.random() > 0.5 ? a : b; }
String s = pick("hello", "world");      // T inferred as String
Number n = pick(1, 2.0);                // T inferred as Number (common supertype)

// Target typing: inference uses the expected type
List<String> list = Collections.emptyList();  // T inferred from target

// Inference with method chains
List<Integer> nums = List.of(1, 2, 3);
String joined = nums.stream()
    .map(Object::toString)              // Stream<String>
    .collect(Collectors.joining(","));  // inferred

// Explicit type witness (rarely needed)
Collections.<String>emptyList();

// Generic constructor
class Holder<T> {
    private T value;
    <U extends T> Holder(U init) { value = init; }  // constructor type param
    T get() { return value; }
}
Holder<Number> h = new Holder<>(42);  // U=Integer, T=Number

import java.util.stream.Collectors;
class Math { static double random() { return 0.5; } }

ジェネリックインターフェースとパターン

ジェネリックインターフェース(Repository<T,ID> のような)は再利用可能な契約を定義します。自己参照バインドパターン(class X implements Comparable<X>)は compareTo が同じ型のみを受け入れることを保証します。型トークンパターン(Class<T> をキーとして使用)は型消去を回避し、異種コンテナでランタイム型安全性を提供します。これらのパターンは Spring Data のようなフレームワークの基盤です。

java
// Generic interface
interface Repository<T, ID> {
    Optional<T> findById(ID id);
    List<T> findAll();
    void save(T entity);
}

// Implement with concrete types
class UserRepository implements Repository<User, Long> {
    public Optional<User> findById(Long id) { /* ... */ return Optional.empty(); }
    public List<User> findAll() { return List.of(); }
    public void save(User entity) {}
}

// Generic interface with self-referencing bound (Comparable pattern)
interface Comparable<T> {
    int compareTo(T other);
}
class Temperature implements Comparable<Temperature> {
    private final double celsius;
    Temperature(double c) { celsius = c; }
    public int compareTo(Temperature other) {
        return Double.compare(celsius, other.celsius);
    }
}

// Generic builder pattern
class Builder<T> {
    private T value;
    public Builder<T> set(T v) { value = v; return this; }
    public T build() { return value; }
}

// Type token pattern for runtime type safety
class TypeSafeMap {
    private final Map<Class<?>, Object> map = new HashMap<>();
    public <T> void put(Class<T> type, T value) { map.put(type, value); }
    public <T> T get(Class<T> type) { return type.cast(map.get(type)); }
}

record User(String name) {}
import java.util.Optional;
13

アノテーション

組み込みアノテーション

Java の組み込みアノテーション:@Override(オーバーライドのタイプミスをキャッチ — 常に使用)、@Deprecated(API を使用すべきでないことを通知、since/forRemoval メタデータ付き)、@SuppressWarnings(特定の警告を抑制 — 狭く使用)、@FunctionalInterface(SAM ルールを強制)。これらはコンパイル時の安全性とドキュメントを向上させる日常的なアノテーションです。

java
import java.util.*;

// @Override: declares intent to override (compiler checks)
class Animal {
    public void sound() { System.out.println("..."); }
}
class Dog extends Animal {
    @Override
    public void sound() { System.out.println("Woof"); }
}

// @Deprecated: marks API as outdated
class OldApi {
    @Deprecated(since = "1.5", forRemoval = true)
    public void legacy() {}
}

// @SuppressWarnings: silence compiler warnings
@SuppressWarnings("unchecked")
List<String> list = (List<String>) new ArrayList();

// @FunctionalInterface: enforces single abstract method
@FunctionalInterface
interface Op { int apply(int a, int b); }

// Common warning keys: unchecked, deprecation, rawtypes, null

// Java 17+ sealed-related
@Deprecated
class ToRemove {}

カスタムアノテーション

カスタムアノテーションは @interface で宣言します。メンバーはメソッドのように見えますがアノテーション属性です — デフォルト値を持てます。@Target で適用場所(TYPE、METHOD、FIELD など)を制限し、@Retention で可用性を制御します。マーカーアノテーション(メンバーなし)は要素をタグ付けするだけです。アノテーション自体は振る舞いを持たず、プロセッサ(リフレクション、アノテーションツール)がそれらを読み取り行動します。

java
import java.lang.annotation.*;

// Define an annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
    String value() default "";
    long timeout() default 0L;
}

// Use it
class MyTests {
    @Test
    public void quickCheck() {}

    @Test(timeout = 5000)
    public void slowCheck() {}

    @Test("custom-name")
    public void named() {}
}

// Annotation with default values
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface Entity {
    String table() default "";
    String[] columns() default {};
}

@Entity(table = "users", columns = {"id", "name"})
class User {}

// Marker annotation (no members)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Service {}

Retention と Target

@Retention はアノテーションがどれだけ生存するかを制御します:SOURCE(コンパイル時のみ、@Override のような)、CLASS(バイトコードに含まれるが実行時には見えない — デフォルト)、RUNTIME(リフレクションでアクセス可能)。@Target はアノテーションがどこに出現できるかを制限します。Java 8+ は TYPE_USE と TYPE_PARAMETER を追加し、ジェネリクスとキャストにアノテーションを付けられます(List<@NonNull String>)。リフレクションアクセスが必要な場合のみ RUNTIME を選択してください。

java
import java.lang.annotation.*;

// RetentionPolicy.SOURCE: discarded by compiler (e.g., @Override)
@Retention(RetentionPolicy.SOURCE)
@interface CompileOnly {}

// RetentionPolicy.CLASS: kept in .class but not loaded (default)
@Retention(RetentionPolicy.CLASS)
@interface BytecodeOnly {}

// RetentionPolicy.RUNTIME: available via reflection at runtime
@Retention(RetentionPolicy.RUNTIME)
@interface RuntimeVisible {}

// ElementType targets
@Target(ElementType.TYPE)         // classes, interfaces, enums
@interface ForType {}

@Target(ElementType.METHOD)
@interface ForMethod {}

@Target(ElementType.FIELD)
@interface ForField {}

@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@interface ForTypeUse {}

// Java 8+ type-use annotations (annotate any type occurrence)
@RuntimeVisible String[] names;  // example usage
List<@RuntimeVisible String> typed;

リフレクションによるアノテーション読み取り

RUNTIME 保持のアノテーションはリフレクションで読めます:isAnnotationPresent() は存在をチェックし、getAnnotation() は取得します。これがフレームワーク(Spring、JUnit、JAX-RS)が宣言的に振る舞いをワイヤリングする方法です — メソッド/クラスにタグ付けし、フレームワークがスキャンしてディスパッチします。コンパイル時のアノテーション処理(アノテーションプロセッサ)はランタイムリフレクションコストなしでコード生成の代替です。

java
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Route {
    String path();
    String method() default "GET";
}

class Api {
    @Route(path = "/users", method = "GET")
    public void listUsers() {}

    @Route(path = "/users", method = "POST")
    public void createUser() {}
}

// Scan methods for @Route at runtime
for (Method m : Api.class.getDeclaredMethods()) {
    if (m.isAnnotationPresent(Route.class)) {
        Route r = m.getAnnotation(Route.class);
        System.out.println(r.method() + " " + r.path() + " -> " + m.getName());
    }
}
// Output:
// GET /users -> listUsers
// POST /users -> createUser

// Reading annotations on a class
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Table { String name(); }

@Table(name = "orders")
class Order {}
Table t = Order.class.getAnnotation(Table.class);
System.out.println(t.name());  // "orders"

Repeatable とメタアノテーション

@Repeatable(Java 8+)はコンテナアノテーションを定義することで同じアノテーションを複数回適用できます。@Inherited はアノテーションをサブクラスに伝播させます(クラスレベルアノテーションのみ)。@Documented は Javadoc にアノテーションを含めます。@Target と ANNOTATION_TYPE はメタアノテーション(他のアノテーションにアノテーションを付けるもの)を作成します — これが Spring が @RestController = @Controller + @ResponseBody のような合成可能なアノテーションステレオタイプを構築する方法です。

java
import java.lang.annotation.*;

// Repeatable: allow same annotation multiple times
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Schedule {
    String cron();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Schedules {
    Schedule[] value();  // container annotation
}

// Make Schedule repeatable
@Repeatable(Schedules.class)
@interface Schedule2 {
    String cron();
}

// Now you can repeat it (Java 8+)
class Job {
    @Schedule2(cron = "0 0 * * *")
    @Schedule2(cron = "0 30 * * *")
    public void run() {}
}

// Meta-annotations: annotations on annotations
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)  // can only annotate other annotations
@interface TestCategory {}

@TestCategory
@Retention(RetentionPolicy.RUNTIME)
@interface UnitTest {}

// Inherited: subclass inherits the annotation
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Persistent {}
@Persistent class Base {}
class Child extends Base {}  // Child also has @Persistent

// Documented: appears in Javadoc
@Documented
@interface PublicApi {}
14

リフレクション

Class オブジェクトとクラスの取得

ロードされた各型には一意の Class オブジェクトがあります — リフレクションのエントリポイントです。クラスリテラル(Type.class)、instance.getClass()、または Class.forName()(動的ロード、ClassNotFoundException をスロー)で取得します。Class オブジェクトは名前、修飾子、スーパークラス、インターフェース、型チェック(isInterface、isArray、isEnum、isRecord)を公開します。isAssignableFrom はポリモーフィック関係をチェックします。

java
import java.lang.reflect.*;

// Three ways to get a Class object
Class<String> c1 = String.class;                  // class literal
Class<?> c2 = "hello".getClass();                 // from instance
Class<?> c3 = Class.forName("java.lang.String");  // by name (throws checked)

// Basic introspection
Class<?> c = String.class;
System.out.println(c.getName());          // "java.lang.String"
System.out.println(c.getSimpleName());    // "String"
System.out.println(c.getPackage());       // "package java.lang"
System.out.println(c.getSuperclass());    // "class java.lang.Object"
System.out.println(Modifier.toString(c.getModifiers()));  // "public final"

// Check type relationships
System.out.println(c.isInterface());      // false
System.out.println(c.isArray());          // false
System.out.println(c.isEnum());           // false
System.out.println(c.isRecord());         // false (Java 16+)
System.out.println(CharSequence.class.isAssignableFrom(c));  // true

// Primitive class objects
Class<?> intClass = int.class;
Class<?> intArrayClass = int[].class;
System.out.println(intClass.isPrimitive());  // true

フィールド、メソッド、コンストラクタの検査

getDeclaredFields/Methods/Constructors はこのクラスで宣言されたすべてのメンバー(private を含む)を返します。getFields/getMethods は public メンバーのみを返しますが継承されたものを含みます。特定のメンバーを見つけるには、getDeclaredField(name) または getDeclaredMethod(name, paramTypes...) を使用します — パラメータ型はオーバーロードを曖昧化するために必要です。setAccessible(true) を呼ばない限り、リフレクションはアクセス制御をバイパスしません。

java
import java.lang.reflect.*;
import java.util.*;

class Sample {
    public String name;
    private int count;
    public Sample() {}
    public Sample(String n) { name = n; }
    private void secret() {}
    public int compute(int x) { return x * 2; }
}

Class<?> c = Sample.class;

// Fields: getDeclaredFields includes private; getFields only public
for (Field f : c.getDeclaredFields()) {
    System.out.println(f.getName() + " : " + f.getType().getSimpleName()
        + " (" + Modifier.toString(f.getModifiers()) + ")");
}

// Methods
for (Method m : c.getDeclaredMethods()) {
    System.out.println(m.getName()
        + " params=" + Arrays.toString(m.getParameterTypes())
        + " returns=" + m.getReturnType().getSimpleName());
}

// Constructors
for (Constructor<?> ctor : c.getConstructors()) {
    System.out.println("ctor params=" + Arrays.toString(ctor.getParameterTypes()));
}

// Lookup specific member
Field nameField = c.getDeclaredField("name");
Method compute = c.getDeclaredMethod("compute", int.class);
Constructor<?> ctor = c.getDeclaredConstructor(String.class);

メソッドの呼び出しとインスタンスの作成

Method.invoke(obj, args...) はメソッドをリフレクティブに呼び出します — 常に Object を返すため、結果をキャストします。setAccessible(true) は Java アクセスチェックをバイパスします(private メンバーが到達可能に;モジュールで --add-opens が必要な場合あり)。Constructor.newInstance() はオブジェクトを作成します — new のリフレクティブな同等物。Array.newInstance はランタイム既知のコンポーネント型の配列を作成します。リフレクションは直接呼び出しより遅く、コンパイル時の安全性をバイパスします。

java
import java.lang.reflect.*;

class Greeter {
    public String greet(String name) { return "Hello, " + name; }
    private String secret() { return "hidden"; }
}

Object obj = new Greeter();
Class<?> c = obj.getClass();

// Invoke public method
Method greet = c.getMethod("greet", String.class);
String result = (String) greet.invoke(obj, "Alice");  // "Hello, Alice"

// Invoke private method
Method sec = c.getDeclaredMethod("secret");
sec.setAccessible(true);  // bypass access check
String s = (String) sec.invoke(obj);  // "hidden"

// Create instances via constructor
Constructor<?> noArg = c.getConstructor();
Object o1 = noArg.newInstance();

// With args
class Person {
    String name;
    public Person(String n) { name = n; }
    public String toString() { return "Person(" + name + ")"; }
}
Constructor<?> ctor = Person.class.getConstructor(String.class);
Object p = ctor.newInstance("Bob");
System.out.println(p);  // "Person(Bob)"

// Array creation via reflection
Object strArray = Array.newInstance(String.class, 5);
Array.set(strArray, 0, "first");
String v = (String) Array.get(strArray, 0);

フィールドの読み取りと変更

Field.get(instance) はフィールドの値を読み取り、Field.set(instance, value) は書き込みます。プリミティブには型固有アクセサ(getInt/setInt)を使用してボクシングを回避します。静的フィールドはインスタンス引数として null を取ります。private フィールドには setAccessible(true) が必要です。リフレクションフィールドアクセスは、シリアライゼーションライブラリ(Jackson、Gson)や ORM フレームワークがオブジェクト状態をジェネリックに読み書きする方法です。

java
import java.lang.reflect.*;

class Config {
    public String env = "dev";
    private int retries = 3;
    public static String VERSION = "1.0";
}

Config cfg = new Config();
Class<?> c = cfg.getClass();

// Read public field
Field env = c.getField("env");
String e = (String) env.get(cfg);  // "dev"

// Read private field
Field retries = c.getDeclaredField("retries");
retries.setAccessible(true);
int r = retries.getInt(cfg);  // 3
// For objects: Object val = field.get(instance);

// Modify fields
env.set(cfg, "prod");
retries.setInt(cfg, 5);
System.out.println(cfg.env);     // "prod"

// Static fields: pass null as the instance
Field version = c.getField("VERSION");
String v = (String) version.get(null);  // "1.0"
version.set(null, "2.0");

// Type-specific getters/setters avoid boxing
// getInt/setInt, getLong/setLong, getBoolean/setBoolean, etc.
// For reference types, use get()/set()

動的プロキシとユースケース

java.lang.reflect.Proxy は実行時にインターフェースを実装する動的プロキシを作成します — InvocationHandler がすべての呼び出しをインターセプトします。これが Spring AOP、Hibernate レイジーロード、Mockito モックの動作方法です。リフレクションはほとんどの Java フレームワーク(DI、ORM、シリアライゼーション、テスト)を動かしますが、コストがあります:直接呼び出しより遅い、より弱い型安全性、モジュールシステムの制限。ランタイム柔軟性が必要な場合に使用し、通常のコードには使用しません。

java
import java.lang.reflect.*;
import java.util.*;

// JDK dynamic proxy: implements interfaces at runtime
interface UserService {
    String getUser(long id);
    void deleteUser(long id);
}

// InvocationHandler intercepts every method call
class LoggingHandler implements InvocationHandler {
    private final Object target;
    LoggingHandler(Object t) { target = t; }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Calling " + method.getName() + "(" + Arrays.toString(args) + ")");
        long start = System.nanoTime();
        Object result = method.invoke(target, args);
        System.out.println("  -> " + result + " (" + (System.nanoTime() - start) + "ns)");
        return result;
    }
}

UserService real = id -> "user-" + id;
UserService proxied = (UserService) Proxy.newProxyInstance(
    UserService.class.getClassLoader(),
    new Class<?>[]{UserService.class},
    new LoggingHandler(real)
);
proxied.getUser(42);  // logs the call and result

// Common reflection use cases:
// - Frameworks: Spring DI, JPA entities, Jackson serialization
// - AOP/proxies: transactions, logging, security
// - Annotation processing at runtime
// - Test frameworks: JUnit discovers @Test methods
// - Code generation tools

// Caution: reflection is slower, bypasses compile-time checks,
// and can break under modules (--add-opens). Prefer alternatives when possible.
15

JDBC とデータベースアクセス

Connection と DriverManager

DriverManager.getConnection() はデータベース接続を開きます — リークを回避するため常に try-with-resources でラップしてください。JDBC 4 以降、ドライバは ServiceLoader 経由で自動登録されるため、Class.forName() はほとんど必要ありません。URL フォーマットはベンダーにより異なります。接続オプション(SSL、タイムアウト)には Properties を使用します。本番では、直接 DriverManager 呼び出しより接続プール(HikariCP)を優先します。

java
import java.sql.*;

// Basic connection (try-with-resources auto-closes)
String url = "jdbc:postgresql://localhost:5432/mydb";
try (Connection conn = DriverManager.getConnection(url, "user", "pass")) {
    System.out.println("Connected: " + conn.getSchema());
    // ... use connection
}

// Modern: no need for Class.forName() with JDBC 4+ (auto-discovery)
// Legacy: Class.forName("org.postgresql.Driver");

// Common URL patterns:
// jdbc:postgresql://host:5432/db
// jdbc:mysql://host:3306/db
// jdbc:oracle:thin:@host:1521:db
// jdbc:sqlite:/path/to/db.sqlite
// jdbc:h2:mem:test  (in-memory H2)

// Connection properties
import java.util.Properties;
Properties props = new Properties();
props.setProperty("user", "user");
props.setProperty("password", "pass");
props.setProperty("ssl", "true");
try (Connection c = DriverManager.getConnection(url, props)) {
    // ...
}

Statement と PreparedStatement

パラメータ付きのクエリには常に Statement より PreparedStatement を使用します — SQL 構造をデータから分離することで SQL インジェクションを防ぎます。パラメータは1ベースのインデックスで型固有セッターで設定します。PreparedStatement は再利用でき(パラメータを再設定して再実行)、バルク操作のバッチング(addBatch/executeBatch)をサポートします。Statement は DDL のような静的で信頼できる SQL にのみ適しています。

java
import java.sql.*;

// Statement: plain SQL (vulnerable to injection — avoid for user input)
try (Connection c = DriverManager.getConnection("jdbc:h2:mem:", "sa", "");
     Statement st = c.createStatement()) {

    st.execute("CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(100))");
    st.execute("INSERT INTO users VALUES (1, 'Alice')");

    // NEVER do this with user input — SQL injection!
    // st.execute("SELECT * FROM users WHERE name = '" + userInput + "'");
}

// PreparedStatement: parameterized, safe from injection, can be reused
String sql = "INSERT INTO users (id, name) VALUES (?, ?)";
try (Connection c = DriverManager.getConnection("jdbc:h2:mem:", "sa", "");
     PreparedStatement ps = c.prepareStatement(sql)) {

    // Set parameters by index (1-based!)
    ps.setInt(1, 1);
    ps.setString(2, "Alice");
    ps.executeUpdate();

    // Reuse with different params
    ps.setInt(1, 2);
    ps.setString(2, "Bob");
    ps.executeUpdate();

    // Batch inserts
    for (int i = 3; i <= 100; i++) {
        ps.setInt(1, i);
        ps.setString(2, "user" + i);
        ps.addBatch();
    }
    ps.executeBatch();
}

ResultSet とクエリ

ResultSet はクエリ行上のカーソルです — next() で進めます(終了で false を返す)。列を名前(読みやすい)または1ベースのインデックスで読み取ります。wasNull() は SQL NULL をプリミティブデフォルト(例:getInt は NULL で 0 を返す)から区別します。デフォルトの ResultSet は前方専用です。TYPE_SCROLL_INSENSITIVE + CONCUR_UPDATABLE でランダムアクセスとインプレース更新を可能にしますが、モダンなアプリではほとんど使用されません。

java
import java.sql.*;
import java.util.*;

record User(int id, String name, String email) {}

// Execute query and map rows
String sql = "SELECT id, name, email FROM users WHERE active = ?";
List<User> users = new ArrayList<>();
try (Connection c = getConnection();
     PreparedStatement ps = c.prepareStatement(sql)) {
    ps.setBoolean(1, true);
    try (ResultSet rs = ps.executeQuery()) {
        while (rs.next()) {  // advance to next row
            users.add(new User(
                rs.getInt("id"),       // by column name
                rs.getString("name"),
                rs.getString("email")  // by column name (preferred)
            ));
        }
    }
}

// Column access by index (1-based) or name (more readable)
// rs.getInt(1), rs.getString(2), rs.getBoolean("active")

// Handle NULLs
String nick = rs.getString("nickname");
if (rs.wasNull()) nick = "anonymous";  // distinguish NULL from real null

// Scrollable/updatable ResultSet (needs specific flags)
Statement st = c.createStatement(
    ResultSet.TYPE_SCROLL_INSENSITIVE,
    ResultSet.CONCUR_UPDATABLE
);
ResultSet rs = st.executeQuery("SELECT * FROM users");
rs.absolute(5);  // jump to row 5
rs.updateString("name", "newname");
rs.updateRow();  // persist change

Connection getConnection() throws SQLException {
    return DriverManager.getConnection("jdbc:h2:mem:", "sa", "");
}

トランザクションとバッチ

JDBC はデフォルトで各文を自動コミットします — autoCommit(false) で文をトランザクションにグループ化します。成功でコミット、失敗でロールバック。セーブポイントはトランザクション内の部分的ロールバックを許可します。分離レベルは同時変更の可視性を制御します(READ_COMMITTED が一般的なデフォルト。SERIALIZABLE が最も安全だが最も遅い)。トランザクション状態のリークを回避するため、常に autoCommit を復元するか接続を閉じてください。

java
import java.sql.*;

// Transactions: disabled by default (auto-commit = true)
try (Connection c = getConnection()) {
    c.setAutoCommit(false);  // start transaction
    try (PreparedStatement ps = c.prepareStatement(
            "UPDATE accounts SET balance = balance - ? WHERE id = ?")) {
        ps.setInt(1, 100); ps.setInt(2, 1); ps.executeUpdate();  // debit
        ps.setInt(1, -100); ps.setInt(2, 2); ps.executeUpdate();  // credit
    }
    c.commit();  // commit both

    // If any step fails, rollback
} catch (SQLException e) {
    // connection auto-closed; rollback happens implicitly on close if not committed
}

// Explicit rollback pattern
try (Connection c = getConnection()) {
    c.setAutoCommit(false);
    try {
        // ... multiple statements
        c.commit();
    } catch (SQLException e) {
        c.rollback();  // undo all changes in this transaction
        throw e;
    }
}

// Savepoints: partial rollback
Statement st = c.createStatement();
st.execute("INSERT INTO log VALUES (1)");
Savepoint sp = c.setSavepoint("before-risky");
st.execute("INSERT INTO log VALUES (2)");
c.rollback(sp);  // undo only after savepoint
c.commit();

// Transaction isolation levels
c.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
// Levels: NONE, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE

Connection getConnection() throws SQLException {
    return DriverManager.getConnection("jdbc:h2:mem:", "sa", "");
}

接続プーリング(HikariCP)

接続プール(HikariCP が事実上の標準)は接続をウォームに保ち再利用し、リクエストごとに新しい TCP+認証接続を開く10-100ms のコストを回避します。最大プールサイズ(DB 容量で制限)、タイムアウト、ライフタイムを設定します。getConnection() で借用し、閉じることで返します(閉じられるのではなくプールに戻ります)。シャットダウン時に常に DataSource を閉じてください。Spring Boot では HikariCP が自動設定されます。

java
import com.zaxxer.hikari.*;
import java.sql.*;
import java.util.*;

// HikariCP: high-performance JDBC connection pool
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
config.setUsername("user");
config.setPassword("pass");
config.setMaximumPoolSize(10);
config.setMinimumIdle(2);
config.setConnectionTimeout(30_000);   // ms to wait for a connection
config.setIdleTimeout(600_000);        // ms before idle connections close
config.setMaxLifetime(1_800_000);      // ms max connection lifetime
config.setPoolName("app-pool");

HikariDataSource ds = new HikariDataSource(config);

// Borrow a connection, use it, return it (auto via try-with-resources)
try (Connection c = ds.getConnection();
     PreparedStatement ps = c.prepareStatement("SELECT * FROM users")) {
    try (ResultSet rs = ps.executeQuery()) {
        while (rs.next()) System.out.println(rs.getString("name"));
    }
}  // connection returns to pool here

// Shutdown pool when app stops
ds.close();

// Why pool? Opening a TCP+auth connection is expensive (~10-100ms).
// A pool keeps connections warm and reuses them across requests,
// dramatically reducing latency under load.

// Alternative pools: Apache DBCP, c3p0, Tomcat JDBC, Agroal
16

I/O と NIO ディープダイブ

InputStream と OutputStream(バイト)

InputStream/OutputStream は生バイトを処理します。常に Buffered* バリアントでラップしてください — バッファなし I/O はバイトごとにシステムコールを行い、パフォーマンスに壊滅的です。read() はストリーム終了で -1 を返します。transferTo()(Java 9+)は効率的なバルクコピーを行います。readAllBytes() は便利ですがストリーム全体をメモリにロードします — 小さなファイルのみに使用。常にストリームを閉じてください(try-with-resources)。

java
import java.io.*;

// Read bytes from a file
try (InputStream in = new FileInputStream("input.bin")) {
    int b;
    while ((b = in.read()) != -1) {  // -1 = end of stream
        // process byte
    }
}

// Buffered for performance (8KB default buffer)
try (InputStream in = new BufferedInputStream(new FileInputStream("big.bin"))) {
    byte[] buffer = new byte[8192];
    int read;
    while ((read = in.read(buffer)) != -1) {
        // process buffer[0..read]
    }
}

// readAllBytes (small files only — loads everything into memory)
byte[] all = new FileInputStream("small.bin").readAllBytes();

// Write bytes
try (OutputStream out = new BufferedOutputStream(new FileOutputStream("out.bin"))) {
    out.write(65);  // single byte
    out.write(new byte[]{66, 67, 68});
    out.flush();  // force buffered data to disk
}

// Copy streams (Java 9+)
try (InputStream in = new FileInputStream("src.bin");
     OutputStream out = new FileOutputStream("dst.bin")) {
    in.transferTo(out);  // efficient bulk copy
}

// Standard streams
System.in.read();   // stdin (InputStream)
System.out.write(65);  // stdout (PrintStream)

チャネルとバッファ(NIO)

NIO チャネル + ByteBuffer はストリームの高性能代替です。バッファには position/limit/capacity があり、flip() は書き込みから読み取りモードに切り替え、clear() は書き込み用にリセットし、compact() は未読データを保持します。ダイレクトバッファ(allocateDirect)は JVM ヒープ外に存在し、大規模 I/O のコピーステップを回避します。チャネル間の transferTo はサポートされた OS でゼロコピーを使用できます。ストリーミングパフォーマンスが重要な場合に NIO を使用します。

java
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
import java.io.*;

// Channel: high-performance, block-oriented I/O
try (FileChannel ch = FileChannel.open(Path.of("data.bin"),
        StandardOpenOption.READ, StandardOpenOption.WRITE)) {

    // ByteBuffer: fixed-capacity block of bytes
    ByteBuffer buf = ByteBuffer.allocate(1024);
    int read = ch.read(buf);  // fill buffer from channel
    buf.flip();  // switch from write mode to read mode

    while (buf.hasRemaining()) {
        byte b = buf.get();
    }
    buf.clear();  // reset for next read (or compact() to preserve unread)
}

// Direct buffer: outside JVM heap, faster for large I/O (no copy)
ByteBuffer direct = ByteBuffer.allocateDirect(64 * 1024);

// Scatter/gather: read into multiple buffers / write from multiple
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ch.read(new ByteBuffer[]{header, body});  // scatter

// Transfer between channels (zero-copy on some OSes)
try (FileChannel src = FileChannel.open(Path.of("a.bin"));
     FileChannel dst = FileChannel.open(Path.of("b.bin"), StandardOpenOption.WRITE)) {
    src.transferTo(0, src.size(), dst);
}

// ByteOrder
buf.order(ByteOrder.LITTLE_ENDIAN);
int value = buf.getInt();

Path 操作(NIO.2)

Path(NIO.2)は古い File クラスを置き換えます。Path 操作(getFileName、getParent、resolve、normalize、relativize)は純粋な文字列計算です — ディスクに触れません。Files.* メソッドがファイルシステムと相互作用します:サイズ、タイムスタンプ、権限、シンボリックリンク。normalize() は . と .. セグメントをクリーンアップします。resolveSibling はリネームに便利です(同じディレクトリ、異なる名前)。モダンコードでは File より Path を優先します。

java
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.IOException;

// Creating Path objects
Path p1 = Path.of("a/b/c.txt");          // relative
Path p2 = Path.of("/usr/local/bin");     // absolute
Path p3 = Paths.get("config.json");      // older API, same thing

// Path manipulation (doesn't touch filesystem)
System.out.println(p1.getFileName());    // "c.txt"
System.out.println(p1.getParent());      // "a/b"
System.out.println(p1.getRoot());        // "" (relative)
System.out.println(p2.getRoot());        // "/"
System.out.println(p1.toAbsolutePath()); // "/cwd/a/b/c.txt"
System.out.println(p1.normalize());      // removes . and ..
System.out.println(p1.resolve("d.txt")); // "a/b/c.txt/d.txt"
System.out.println(p1.resolveSibling("x.txt")); // "a/b/x.txt"
System.out.println(p1.relativize(Path.of("a/b")));  // "../.."

// File metadata
Path file = Path.of("notes.txt");
System.out.println(Files.size(file));            // bytes
System.out.println(Files.getLastModifiedTime(file));
System.out.println(Files.isReadable(file));
System.out.println(Files.isWritable(file));

// PosixFilePermissions (Linux/macOS)
String perms = "rwxr-xr--";
Set<PosixFilePermission> set = PosixFilePermissions.fromString(perms);
Files.setPosixFilePermissions(file, set);

// Symbolic links
Path link = Path.of("link.txt");
Files.createSymbolicLink(link, Path.of("target.txt"));
Path target = Files.readSymbolicLink(link);

ディレクトリ巡回とファイルツリー

Files.list() は1レベルをリストし、Files.walk() はツリーを再帰的に巡回します(Stream を返す、閉じる必要あり)。Files.find() は巡回中にパスと属性でフィルタします。完全制御には walkFileTree と FileVisitor でサブツリーをスキップし、エラーを処理し、ディレクトリ訪問前/後にアクションを実行できます。すべてファイルハンドルを保持する Stream を返します — 常に try-with-resources を使用します。高コストな巡回を制限するには max depth を使用します。

java
import java.nio.file.*;
import java.io.IOException;
import java.util.stream.*;

// List directory entries (one level)
try (Stream<Path> entries = Files.list(Path.of("."))) {
    entries.filter(Files::isRegularFile)
           .map(Path::getFileName)
           .forEach(System.out::println);
}

// Walk file tree recursively (depth-first)
try (Stream<Path> walk = Files.walk(Path.of("/project"))) {
    walk.filter(Files::isRegularFile)
        .filter(p -> p.toString().endsWith(".java"))
        .forEach(System.out::println);
}

// Walk with depth limit
try (Stream<Path> walk = Files.walk(Path.of("/project"), 3)) {
    walk.forEach(System.out::println);
}

// Find with BiPredicate (path + attributes)
try (Stream<Path> found = Files.find(Path.of("/logs"), 10,
        (path, attrs) -> attrs.isRegularFile()
            && attrs.size() > 1_000_000
            && path.toString().endsWith(".log"))) {
    found.forEach(p -> System.out.println("Large log: " + p));
}

// FileVisitor for full control (pre/post visit, skip subtrees)
Files.walkFileTree(Path.of("/project"), new SimpleFileVisitor<>() {
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        System.out.println("Visiting " + file);
        return FileVisitResult.CONTINUE;
    }
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
        if (dir.getFileName().toString().equals("target")) {
            return FileVisitResult.SKIP_SUBTREE;  // skip target dirs
        }
        return FileVisitResult.CONTINUE;
    }
});

import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.BasicFileAttributes;

WatchService とファイルイベント

WatchService は登録されたディレクトリのファイルシステムイベント(作成、変更、削除)を受信します。take() はイベントが到着するまでブロックし、pollEvents() はそれらを排出します。親ディレクトリを監視し context() でフィルタして特定のファイルを追跡します。イベントは統合または消失する可能性があります(OVERFLOW)。再帰的監視にはすべてのサブディレクトリを登録する必要があります。WatchService は OS ネイティブ(Linux では inotify、macOS では FSEvents)ですが API は低レベルです — 複雑なニーズにはライブラリを検討してください。

java
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import java.io.IOException;

// Watch a directory for changes
try (WatchService watcher = FileSystems.getDefault().newWatchService()) {
    Path dir = Path.of("/var/log");
    dir.register(watcher,
        ENTRY_CREATE,
        ENTRY_DELETE,
        ENTRY_MODIFY);

    System.out.println("Watching " + dir + "...");
    while (true) {
        WatchKey key = watcher.take();  // blocks until event
        for (WatchEvent<?> event : key.pollEvents()) {
            Path changed = dir.resolve((Path) event.context());
            System.out.println(event.kind() + " -> " + changed);

            if (event.kind() == OVERFLOW) continue;  // events lost

            // React to change
            if (event.kind() == ENTRY_CREATE) {
                System.out.println("New file: " + changed);
            }
        }
        if (!key.reset()) break;  // key no longer valid (dir deleted)
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

// Note: WatchService watches directories, not individual files.
// To watch a file, watch its parent dir and filter by context().
// Events may be coalesced (rapid modifications may yield one event).
// For recursive watching, register each subdirectory manually
// or use a library like Apache Commons IO's FileAlterationMonitor.
17

レコードとパターンマッチング

レコードの基礎

レコード(Java 16+)は透明な不変データキャリアです。ヘッダー `record Name(Type1 f1, Type2 f2)` がコンストラクタ、アクセサ(f1()、f2() — getF1() ではなく)、equals、hashCode、toString を生成します。DTO、値オブジェクト、関数結果に理想的です。コンパクトコンストラクタ(単なる `{ ... }`)はフィールド再代入なしで検証または正規化します。レコードはインターフェースを実装できますがクラスを拡張できません。

java
// Record: concise immutable data carrier (Java 16+)
public record Point(int x, int y) {}

// Equivalent to writing:
// - final class with private final int x, y
// - constructor Point(int x, int y)
// - accessors x(), y()
// - equals, hashCode, toString (all auto-generated)

Point p = new Point(3, 4);
System.out.println(p.x());          // 3 (accessor, not getX())
System.out.println(p.y());          // 4
System.out.println(p);              // "Point[x=3, y=4]"
System.out.println(p.equals(new Point(3, 4)));  // true

// Immutable: no setters
// p.setX(5);  // no such method

// Custom record with validation
public record Age(int value) {
    public Age {
        if (value < 0 || value > 150) {
            throw new IllegalArgumentException("Invalid age: " + value);
        }
    }
}

// Record with multiple components
public record Employee(String name, int id, double salary, String dept) {}

// Records implement interfaces
public interface Named { String name(); }
public record Customer(String name, long id) implements Named {}

コンパクトコンストラクタとカスタムメソッド

コンパクトコンストラクタ(`public Name { ... }`)はフィールド割り当て前に実行されます — 正規化のためにパラメータに割り当て、コンパイラがフィールドに割り当てます。レコードは追加メソッドと静的ファクトリを持てますが、ヘッダーを超えるインスタンスフィールドは持てません。非正規コンストラクタは this(...) で正規コンストラクタに委譲する必要があります。より明確な構築(Point.origin())と共通インスタンスのキャッシュに静的ファクトリを使用します。

java
// Compact constructor: validation without reassigning
public record Email(String address) {
    public Email {
        if (!address.contains("@")) {
            throw new IllegalArgumentException("Bad email: " + address);
        }
        address = address.toLowerCase().strip();  // normalize
    }
}

// Add custom methods (but not mutable fields)
public record Money(double amount, String currency) {
    public Money plus(Money other) {
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currency mismatch");
        }
        return new Money(amount + other.amount, currency);
    }
    public Money times(int factor) {
        return new Money(amount * factor, currency);
    }
    public static Money usd(double amt) { return new Money(amt, "USD"); }
}

// Canonical + non-canonical constructors
public record Range(int start, int end) {
    // Compact canonical (validation)
    public Range {
        if (start > end) throw new IllegalArgumentException();
    }
    // Non-canonical convenience constructor
    public Range(int end) {
        this(0, end);
    }
}

// Static factories are common on records
public record Point(int x, int y) {
    public static Point origin() { return new Point(0, 0); }
    public static Point of(int x, int y) { return new Point(x, y); }
}

Sealed クラス

Sealed クラス/インターフェース(Java 17+)は `permits` 節で拡張できる型を制限します。許可された各サブタイプは final、sealed、または non-sealed でなければなりません。レコードと組み合わせて、代数的データ型(閉じた継承 + 不変データ)を形成します。主な利点:コンパイラがすべてのサブタイプを知っているため、switch 式は default ブロックなしで網羅的になれます — 新しいサブタイプを追加すると、コンパイラが更新が必要なすべての switch をフラグします。

java
// Sealed class: restricts which classes can extend it (Java 17+)
public sealed interface Shape
    permits Circle, Square, Triangle {}

public record Circle(double radius) implements Shape {}
public record Square(double side) implements Shape {}
public record Triangle(double base, double height) implements Shape {}

// Every permitted subtype must be final, sealed, or non-sealed
public non-sealed class WeirdShape implements Shape {}  // open again

// Why sealed? Enables exhaustive pattern matching
public double area(Shape s) {
    return switch (s) {
        case Circle c -> Math.PI * c.radius() * c.radius();
        case Square sq -> sq.side() * sq.side();
        case Triangle t -> 0.5 * t.base() * t.height();
        // No default needed — compiler knows all subtypes
    };
}

// Sealed class hierarchy with records = algebraic data types
// Combines: closed inheritance (sealed) + immutable data (record)

// Sealed classes also work with classes (not just interfaces)
public sealed abstract class Result permits Success, Failure {}
public final class Success<T>(T value) extends Result {}
public final class Failure(String error) extends Result {}

instanceof のパターンマッチング

instanceof のパターンマッチング(Java 16+)はテストが成功した場合にのみバインドされる変数を宣言し、明示的なキャストを排除します。変数のスコープはパターンの真から流れます — && 継続と早期リターン後に使用可能。Java 21 は switch でのパターンマッチング(case Type var when guard)を追加し、ガード付きの型ベースディスパッチを可能にします。これにより型チェックコードがはるかに簡潔でエラーが少なくなります。

java
// Old way: cast after instanceof
Object obj = "hello";
if (obj instanceof String) {
    String s = (String) obj;  // redundant cast
    System.out.println(s.length());
}

// Java 16+: pattern variable
if (obj instanceof String s) {
    System.out.println(s.length());  // s is in scope, no cast
}

// The variable is scoped by the pattern's truth
if (obj instanceof String s && s.length() > 3) {
    System.out.println("Long string: " + s);
}
// s is NOT in scope here if the && short-circuited

// Negation scoping
if (!(obj instanceof String s)) {
    // s NOT in scope here
    return;
}
// s IS in scope here (the instanceof must have been true)

// Combining with other patterns
Object data = 42;
String desc = switch (data) {
    case Integer i when i > 0 -> "positive int: " + i;
    case Integer i -> "non-positive int: " + i;
    case String s -> "string: " + s;
    case null -> "null";
    default -> "other";
};

Switch パターンマッチング(Java 21)

Switch パターンマッチング(Java 21、final)は型で switch し、レコードを分解し、ガード(when)を追加できます。Sealed 型と組み合わせて、コンパイラは網羅性を検証します — すべてのサブタイプがカバーされていれば default 不要。レコードパターン(case Point(int x, int y))は1ステップで分解します。null case は明示的です(NPE なし)。これにより Java は宣言的にドメインをモデリングする ML/Scala のパターンマッチングに近づきます。

java
// Java 21: switch pattern matching with type patterns and guards
sealed interface Event permits Login, Logout, Message, Error {}
record Login(String user) implements Event {}
record Logout(String user) implements Event {}
record Message(String from, String text) implements Event {}
record Error(int code, String detail) implements Event {}

String describe(Event e) {
    return switch (e) {
        case Login l -> l.user() + " logged in";
        case Logout l -> l.user() + " logged out";
        case Message m when m.from().equals("system") -> "[system] " + m.text();
        case Message m -> m.from() + ": " + m.text();
        case Error(int code, String detail) when code >= 500 -> "SERVER ERROR " + code;
        case Error(int code, String detail) -> "error " + code + ": " + detail;
        case null -> "no event";  // explicit null handling
    };
}

// Record patterns: destructure records in one go
record Point(int x, int y) {}
String classify(Object o) {
    return switch (o) {
        case Point(int x, int y) when x == y -> "diagonal";
        case Point(int x, int y) -> "point at (" + x + "," + y + ")";
        default -> "not a point";
    };
}

// Nested patterns
record Box(Point p) {}
String describe2(Object o) {
    return switch (o) {
        case Box(Point(int x, int y)) -> "box at " + x + "," + y;
        default -> "unknown";
    };
}
18

モジュール(JPMS)

module-info.java の基礎

module-info.java はモジュール(Java 9+ JPMS)を宣言します。requires は依存関係を追加し、exports はパッケージをアクセス可能にし、opens はリフレクティブアクセスを許可し(シリアライゼーション/DI フレームワークに必要)、uses/provides は ServiceLoader を接続します。このファイルなしでは、コードはレガシー動作で「名前のないモジュール」としてクラスパスに存在します。モジュールは強力なカプセル化(エクスポートされたパッケージのみ public)と信頼性のある設定(明示的な依存関係)を提供します。

java
// File: src/com.example.app/module-info.java
module com.example.app {
    requires java.sql;           // depends on java.sql module
    requires transitive com.example.lib;  // re-export (consumers see it too)
    requires static java.annotation;  // compile-time only (optional at runtime)

    exports com.example.app.api;       // public API visible to all
    exports com.example.app.internal to com.example.test;  // qualified export

    opens com.example.app.model to com.fasterxml.jackson.databind;  // reflection only
    opens com.example.app.dynamic;  // open to all for reflection

    uses com.example.app.spi.Plugin;  // service consumer
    provides com.example.app.spi.Plugin with com.example.app.plugins.DefaultPlugin;  // service provider
}

// Key directives:
// requires: depends on another module
// exports: makes packages public to other modules
// opens: allows deep reflection (for frameworks like Jackson, Hibernate)
// uses/provides: service loader integration

// A module without module-info.java is an "unnamed module" (classpath behavior)

requires、exports、opens

requires は依存関係を宣言し、requires transitive はそれを伝播させます(public API がそのモジュールの型を公開する場合に使用)。exports はパッケージを public にし、exports to は名前付きモジュールに制限します(修飾付きエクスポート)。opens はリフレクティブアクセス(深いリフレクション)を付与します — setAccessible(true) するフレームワークに不可欠。exports(public API)と opens(リフレクション)の区別が鍵です:強力なカプセル化がデフォルトで、パッケージごとにオプトインします。

java
// Module A: com.example.library
module com.example.library {
    // Public API anyone can use
    exports com.example.library.api;

    // Internal package: only visible to specific modules
    exports com.example.library.internal to com.example.app;

    // Allow reflection for frameworks (Jackson, JPA)
    opens com.example.library.model;

    // Only specific module can reflect
    opens com.example.library.config to com.example.app;

    // Dependencies
    requires java.logging;
    requires transitive java.sql;  // consumers of A also get java.sql
}

// Module B: com.example.app (consumer)
module com.example.app {
    requires com.example.library;  // use A's exported packages
    // Note: transitive means java.sql is also available here

    requires com.fasterxml.jackson.databind;
}

// 'requires transitive X' means: any module requiring this one
// also reads X. Use when your exported API exposes X's types.

// 'opens' vs 'exports':
// exports: compile-time + runtime access to public members
// opens: runtime reflective access to ALL members (including private)

// Reflective access without 'opens' fails with InaccessibleObjectException
// in Java 16+ (strong encapsulation enforced by default).

ServiceLoader とサービス

ServiceLoader は SPI パターンを実装します:1つのモジュールにインターフェース、実装はランタイムに発見されます。API モジュールがインターフェースをエクスポートし、プロバイダモジュールが `provides X with Y` を宣言し、コンシューマモジュールが `uses X` を宣言します。ServiceLoader.load(X.class) がモジュールパス上のすべてのプロバイダを見つけます。これがインターフェースと実装を疎結合にします — JDBC ドライバ、ロギングバックエンド(SLF4J)、Charset プロバイダはすべてこの方式で動作します。実装へのコンパイル時依存関係はありません。

java
// SPI (Service Provider Interface) pattern with modules

// 1. Define the service interface in an API module
module com.example.spi {
    exports com.example.spi;
}
package com.example.spi;
public interface Plugin {
    String name();
    void run();
}

// 2. Implement in a provider module
module com.example.plugin.impl {
    requires com.example.spi;
    provides com.example.spi.Plugin with com.example.plugin.impl.DefaultPlugin;
}
package com.example.plugin.impl;
import com.example.spi.Plugin;
public class DefaultPlugin implements Plugin {
    public String name() { return "default"; }
    public void run() { System.out.println("running"); }
}

// 3. Consume in an app module
module com.example.app {
    requires com.example.spi;
    uses com.example.spi.Plugin;  // declares intent to load services
}

// Loading services at runtime
import java.util.ServiceLoader;
ServiceLoader<Plugin> loader = ServiceLoader.load(Plugin.class);
for (Plugin p : loader) {
    System.out.println("Found: " + p.name());
    p.run();
}

// ServiceLoader is how JDBC drivers, SLF4J backends, and many
// plugin systems are discovered without compile-time dependencies.

モジュールパスとクラスパス

モジュールパス(--module-path)は強力なカプセル化を持つモジュラ JAR を保持し、クラスパス(-cp)はカプセル化なしのレガシー JAR を名前のないモジュールとして保持します。モジュラ JAR は両方で動作します。自動モジュールは module-info なしでモジュールパスに配置された JAR です — 名前はファイル名または Automatic-Module-Name マニフェスト属性から来ます。--add-opens は強力なカプセル化の下で壊れるレガシーリフレクションの脱出口です。

java
// Compile a module
//   javac -d out/com.example.app \
//         --module-source-path src \
//         --module com.example.app

// Run a modular app
//   java --module-path out --module com.example.app/com.example.app.Main

// Package as a modular JAR (includes module-info.class)
//   jar --create --file app.jar --main-class com.example.app.Main -C out/com.example.app .

// Module path vs classpath:
// --module-path: modules with module-info, strong encapsulation enforced
// --class-path (or -cp): legacy "unnamed module", everything public, no encapsulation

// Mixing: modular JARs can be used on the classpath too (automatic module)
//   java -cp lib/app.jar:lib/dep.jar com.example.app.Main
// An automatic module: a JAR without module-info on the module path.
// Its name is derived from the JAR filename (Automatic-Module-Name in MANIFEST.MF
// gives an explicit name).

// Inspect a module JAR
//   jar --describe-module --file app.jar

// List observable modules
//   java --list-modules
//   java --describe-module java.sql

// Add opens at runtime for legacy reflection (escape hatch)
//   java --add-opens com.example.app/com.example.app.internal=ALL-UNNAMED

jdeps と jlink(カスタムランタイム)

jdeps はバイトコードを分析してモジュール依存関係をリストします — モジュールへの移行と未使用依存関係の発見に便利です。jlink はアプリが必要とするモジュールのみを含むカスタム JRE を作成し、自己完結型でより小さく、起動が速いランタイムを生成します。Docker イメージとインストーラに理想的です:300MB の JDK インストールを要求するのではなく、アプリと30-50MB の JRE を出荷します。jdeps + jlink でリーンで自己完結型の Java デプロイが可能です。

java
# jdeps: analyze dependencies (find unused, list required modules)

# List dependencies of a JAR
jdeps --module-path lib app.jar

# Generate module-info.java for an existing JAR
jdeps --generate-module-info ./out app.jar

# Show which JDK modules a JAR uses
jdeps --print-module-deps --ignore-missing-deps app.jar
# Output: java.base,java.logging,java.sql

# jlink: create a custom stripped-down JRE containing only needed modules
jlink \
  --module-path "$JAVA_HOME/jmods:./out" \
  --add-modules com.example.app \
  --output ./custom-jre \
  --strip-debug \
  --compress=zip-6 \
  --no-header-files \
  --no-man-pages \
  --launcher app=com.example.app/com.example.app.Main

# The custom JRE is self-contained:
#   ./custom-jre/bin/app   # launches the app
#   ./custom-jre/bin/java  # the stripped JVM

# Benefits of jlink:
# - Smaller distribution (only needed modules)
# - Faster startup (less to load)
# - No need to install Java on target machine
# - Can cross-target (different OS/arch) with matching jmods

# Common workflow:
# 1. jdeps to find required modules
# 2. jlink to build a custom runtime
# 3. Package app + custom JRE together (Docker image, installer)
19

並行性ディープダイブ

ロック:ReentrantLock と ReadWriteLock

ReentrantLock は synchronized より多くの制御を提供します:tryLock(非ブロッキング/タイム付き)、公平性、割り込み可能性、ロック状態検査。常に finally で unlock します。ReadWriteLock は多数の並行読み取り側と1つの排他書き込み側を許可します — 読み取り多いキャッシュに最適。StampedLock(Java 8+)はさらに良い読み取りスループットのために楽観的読み取りを追加します。シンプルなケースには synchronized を優先し、高度な機能が必要な場合に Lock を使用します。

java
import java.util.concurrent.locks.*;

// ReentrantLock: more flexible than synchronized
class Counter {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();  // MUST be in finally
        }
    }

    public int get() {
        lock.lock();
        try { return count; }
        finally { lock.unlock(); }
    }
}

// tryLock with timeout (avoids deadlock-induced hangs)
if (lock.tryLock(1, java.util.concurrent.TimeUnit.SECONDS)) {
    try { /* work */ } finally { lock.unlock(); }
}

// Fair lock (FIFO ordering, slower)
ReentrantLock fair = new ReentrantLock(true);

// ReadWriteLock: many readers OR one writer
class Cache {
    private final ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    private final Lock read = rw.readLock();
    private final Lock write = rw.writeLock();
    private java.util.Map<String, String> data = new java.util.HashMap<>();

    public String get(String key) {
        read.lock();
        try { return data.get(key); }
        finally { read.unlock(); }
    }
    public void put(String key, String val) {
        write.lock();
        try { data.put(key, val); }
        finally { write.unlock(); }
    }
}

並行コレクション

ConcurrentHashMap は主力のスレッドセーフマップです — check-then-act の代わりにアトミック更新に compute/merge を使用します。CopyOnWriteArrayList は読み取り多く書き込み稀なリスト(イベントリスナー)に最適です — 書き込みは配列をコピーします。BlockingQueue はプロデューサ-コンシューマパイプラインの基盤です(満杯で put がブロック、空で take がブロック)。ConcurrentLinkedQueue は非ブロッキングで無制限です。これらは粗粒度で遅い synchronized ラッパ(Collections.synchronizedX)を置き換えます。

java
import java.util.concurrent.*;
import java.util.*;

// ConcurrentHashMap: thread-safe HashMap (no null keys/values)
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1);
map.putIfAbsent("a", 2);  // only if absent
map.compute("a", (k, v) -> v == null ? 1 : v + 1);  // atomic update
map.merge("a", 1, Integer::sum);  // add 1 atomically
Integer val = map.getOrDefault("a", 0);

// CopyOnWriteArrayList: snapshot semantics, fast reads, slow writes
CopyOnWriteArrayList<String> listeners = new CopyOnWriteArrayList<>();
listeners.add("x");  // copies entire array
for (String l : listeners) { /* safe iteration, no ConcurrentModificationException */ }

// BlockingQueue: producer-consumer pattern
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);
// Producer
queue.put("task");        // blocks if full
boolean added = queue.offer("task", 1, TimeUnit.SECONDS);  // timed
// Consumer
String task = queue.take();  // blocks if empty
String polled = queue.poll(1, TimeUnit.SECONDS);

// ConcurrentLinkedQueue: non-blocking, unbounded
Queue<String> q = new ConcurrentLinkedQueue<>();
q.offer("a");

// SkipListMap / SkipListSet: concurrent sorted collections
ConcurrentNavigableMap<Integer, String> sorted = new ConcurrentSkipListMap<>();

import java.util.concurrent.TimeUnit;

CountDownLatch と CyclicBarrier

CountDownLatch はワンショットゲートです — N スレッドがカウントダウンし、他は待機します。リセット不可。CyclicBarrier は再利用可能です — スレッドがランデブーポイントで互いに待機し、すべて到着時にオプションのアクションを実行します。Phaser が最も柔軟です:可変参加者、複数フェーズ、ツリー構造。スタートアップ調整に latch を、並列マルチフェーズアルゴリズムに barrier を、動的参加者数に phaser を使用します。

java
import java.util.concurrent.*;
import java.util.*;

// CountDownLatch: one-shot gate, N threads must arrive before proceeding
CountDownLatch ready = new CountDownLatch(3);
List<String> results = Collections.synchronizedList(new ArrayList<>());

for (int i = 0; i < 3; i++) {
    final int id = i;
    new Thread(() -> {
        try { Thread.sleep(id * 100); } catch (InterruptedException e) {}
        results.add("worker-" + id);
        ready.countDown();  // signal done
    }).start();
}
ready.await();  // main thread blocks until count reaches 0
System.out.println("All done: " + results);

// CyclicBarrier: reusable barrier, threads wait for each other
CyclicBarrier barrier = new CyclicBarrier(3, () ->
    System.out.println("--- phase complete ---"));

Runnable worker = () -> {
    try {
        System.out.println(Thread.currentThread().getName() + " phase 1");
        barrier.await();  // wait for all 3
        System.out.println(Thread.currentThread().getName() + " phase 2");
        barrier.await();  // reusable for next phase
    } catch (Exception e) {}
};
for (int i = 0; i < 3; i++) new Thread(worker).start();

// Phaser: more flexible (variable parties, multiple phases)
Phaser phaser = new Phaser(3);
phaser.register();  // dynamically add a party
phaser.arriveAndAwaitAdvance();

Semaphore と Exchanger

Semaphore は N 個の許可へのアクセスを制御します — acquire は1つ利用可能になるまでブロックし、release はそれを返します。レート制限、接続プール、または bounded-resource シナリオに使用します。tryAcquire は非ブロッキングとタイム付きバリアントを提供します。Exchanger は2つのスレッドがランデブーポイントで値を交換できます — ニッチですが2つのスレッドがバッファを交換するパイプライン設計にエレガントです。どちらも java.util.concurrent にあり、一部の調整パターンでロックより低レベルです。

java
import java.util.concurrent.*;

// Semaphore: limit concurrent access to N permits
Semaphore pool = new Semaphore(5);  // 5 concurrent allowed

pool.acquire();  // blocks until a permit is available
try {
    // critical section (at most 5 threads here at once)
    System.out.println("Working, permits left: " + pool.availablePermits());
} finally {
    pool.release();  // return the permit
}

// Try-acquire (non-blocking)
if (pool.tryAcquire()) {
    try { /* work */ } finally { pool.release(); }
} else {
    System.out.println("Too busy, try later");
}

// Timed acquire
if (pool.tryAcquire(1, TimeUnit.SECONDS)) {
    try { /* work */ } finally { pool.release(); }
}

// Use case: rate limiting, connection pools, parking lots

// Exchanger: two threads swap values
Exchanger<String> ex = new Exchanger<>();
new Thread(() -> {
    try {
        String got = ex.exchange("from-A");  // gives "from-A", receives "from-B"
        System.out.println("A got: " + got);
    } catch (InterruptedException e) {}
}).start();
String got = ex.exchange("from-B");  // gives "from-B", receives "from-A"
System.out.println("B got: " + got);

import java.util.concurrent.TimeUnit;

CompletableFuture 上級

CompletableFuture は Java の Promise です — thenApply(map)、thenCompose(flatMap)、thenCombine(2つの zip)で非同期作業を合成します。allOf はすべてを待機し、anyOf は最初を待機します。exceptionally はエラーから回復し、handle は両方をカバーします。orTimeout(Java 9+)は時間がかかりすぎるとキャンセルします。常に明示的な executor を渡します — デフォルトの commonPool はブロッキング作業の下で枯渇する可能性があります。これが Java でのリアクティブスタイル非同期コードの基盤です。

java
import java.util.concurrent.*;
import java.util.*;

ExecutorService pool = Executors.newFixedThreadPool(4);

// Async composition (like JS Promises)
CompletableFuture<String> future = CompletableFuture
    .supplyAsync(() -> "hello", pool)            // async supplier
    .thenApply(String::toUpperCase)              // transform
    .thenCompose(s -> CompletableFuture.supplyAsync(  // flatMap
        () -> s + " world"))
    .thenApply(s -> s + "!");

System.out.println(future.join());  // "HELLO world!"

// Combine two independent futures
CompletableFuture<Integer> a = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> b = CompletableFuture.supplyAsync(() -> 20);
CompletableFuture<Integer> sum = a.thenCombine(b, Integer::sum);
System.out.println(sum.join());  // 30

// Run multiple and wait for all (or any)
List<CompletableFuture<Integer>> futures = List.of(
    CompletableFuture.supplyAsync(() -> 1, pool),
    CompletableFuture.supplyAsync(() -> 2, pool),
    CompletableFuture.supplyAsync(() -> 3, pool)
);
CompletableFuture<Void> all = CompletableFuture.allOf(
    futures.toArray(new CompletableFuture[0]));
all.join();
List<Integer> results = futures.stream().map(CompletableFuture::join).toList();

// Error handling
CompletableFuture<String> safe = CompletableFuture
    .supplyAsync(() -> { throw new RuntimeException("boom"); })
    .exceptionally(ex -> "fallback: " + ex.getMessage());
System.out.println(safe.join());  // "fallback: boom"

// handle: both success and failure
CompletableFuture<String> handled = CompletableFuture
    .supplyAsync(() -> "ok")
    .handle((val, ex) -> ex == null ? val : "error");

// Timeout (Java 9+)
CompletableFuture<String> timed = CompletableFuture
    .supplyAsync(() -> { Thread.sleep(5000); return "slow"; })
    .orTimeout(1, TimeUnit.SECONDS)
    .exceptionally(ex -> "timed out");

仮想スレッド(Java 21)

仮想スレッド(Java 21)は JVM が少数のキャリア(プラットフォーム)スレッドプール上でスケジュールする軽量スレッドです。仮想スレッドが I/O でブロックすると一時停止され、キャリアが別のものを実行します — したがって数百万の並行ブロッキング操作を持てます。これにより複雑なリアクティブ/非同期チェーンの代わりにシンプルなブロッキングコードを書けます。I/O バウンドワークロード(HTTP ハンドラ、DB 呼び出し)に使用します。CPU バウンド作業にはプラットフォームスレッドまたは parallelStream が依然として適切です。

java
import java.util.concurrent.*;
import java.util.*;

// Virtual threads: lightweight, cheap, millions possible
// Java 21 LTS feature — "Project Loom"

// Start a virtual thread
Thread vt = Thread.ofVirtual().start(() -> {
    System.out.println("Running on: " + Thread);
});

// Builder pattern
Thread vt2 = Thread.ofVirtual().name("worker-1").start(() -> {
    // blocking I/O here is fine — virtual thread yields, not the OS thread
});

// Per-thread factory
ThreadFactory factory = Thread.ofVirtual().factory();

// ExecutorService for virtual threads (Java 21+)
try (ExecutorService es = Executors.newVirtualThreadPerTaskExecutor()) {
    // Submit a million tasks — each gets its own virtual thread
    List<Future<String>> futures = new ArrayList<>();
    for (int i = 0; i < 1_000_000; i++) {
        final int id = i;
        futures.add(es.submit(() -> {
            Thread.sleep(100);  // blocking call is cheap on virtual threads
            return "done-" + id;
        }));
    }
    // virtual threads yield when blocking, so 1M concurrent is feasible
}

// Why virtual threads?
// - Platform threads (OS threads) are heavy (~1MB stack, kernel scheduling)
// - Virtual threads are user-mode, ~KB, scheduled by JVM on a small carrier pool
// - Blocking I/O on a virtual thread doesn't block a platform thread
// - Lets you write straightforward blocking code at scale (no reactive complexity)

// Best practice: use virtual threads for I/O-bound work,
// NOT for CPU-bound work (use platform threads / parallelStream).
20

コレクションフレームワーク ディープダイブ

Comparator とソート

Comparator.comparing(keyExtractor) はキー関数からコンパレータを構築します — 生の compare ロジックを書くよりはるかにクリーンです。thenComparing はセカンダリソートキーをチェーンします。nullsFirst/nullsLast は null を安全に処理します。ボクシングを回避するには comparingInt/comparingLong/comparingDouble を使用します。List.sort() はインプレースでソートし(可変リストのみ)、Stream.sorted() は新しいソート済みストリームを返します。コンパレータはソート、TreeSet/TreeMap 順序付け、ストリーム操作を動かします。

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

record Person(String name, int age) {}

List<Person> people = List.of(
    new Person("Alice", 30),
    new Person("Bob", 25),
    new Person("Carol", 30),
    new Person("Dave", 25)
);

// Comparator.comparing (key extractor)
people.stream().sorted(Comparator.comparing(Person::name)).toList();
people.stream().sorted(Comparator.comparingInt(Person::age)).toList();

// Reversed
people.stream().sorted(Comparator.comparing(Person::age).reversed()).toList();

// Chained (thenComparing): sort by age, then by name
List<Person> sorted = people.stream().sorted(
    Comparator.comparingInt(Person::age)
              .thenComparing(Person::name)
).toList();
// [Bob(25), Dave(25), Alice(30), Carol(30)]

// Nulls handling
Comparator<String> cmp = Comparator.nullsFirst(Comparator.naturalOrder());
List.of("b", null, "a").stream().sorted(cmp).toList();  // [null, a, b]

// Custom comparator
Comparator<Person> byNameLen = (a, b) -> a.name().length() - b.name().length();

// Mutable list sort
List<String> names = new ArrayList<>(List.of("charlie", "alice", "bob"));
names.sort(Comparator.naturalOrder());
// names = [alice, bob, charlie]

// Comparing with primitive specializations avoids boxing
Comparator<Person> byAge = Comparator.comparingInt(Person::age);

不変とイミュータブルコレクション

List.of/Set.of/Map.of(Java 9+)は真にイミュータブルなコレクションを作成します — null なし、変更なし。Collections.unmodifiableX はバッキングコレクションの変更を反映する読み取り専用ビューを作成します。List.copyOf(Java 10+)は独立したイミュータブルコピーを作成します。Arrays.asList は配列の固定サイズビューです(set は動作、add/remove は不可)。ニーズに応じて選択:定数にはイミュータブルファクトリ、内部を安全に公開するには unmodifiable ビュー、防御的コピーには copyOf。

java
import java.util.*;

// Java 9+ immutable factories (List.of, Set.of, Map.of)
List<String> immutable = List.of("a", "b", "c");
Set<Integer> set = Set.of(1, 2, 3);
Map<String, Integer> map = Map.of("a", 1, "b", 2);
Map<String, Integer> bigMap = Map.ofEntries(
    Map.entry("x", 1), Map.entry("y", 2), Map.entry("z", 3)
);
// immutable.add("d");  // throws UnsupportedOperationException
// immutable.set(0, "z");  // throws
// nulls not allowed in these immutable collections

// Unmodifiable view (wraps an existing collection)
List<String> mutable = new ArrayList<>(List.of("a", "b"));
List<String> view = Collections.unmodifiableList(mutable);
// view.add("c");  // throws
mutable.add("c");  // but changes to backing list ARE visible in view
System.out.println(view);  // [a, b, c]

// CopyOf (Java 10+): creates immutable copy
List<String> copy = List.copyOf(mutable);  // independent immutable copy

// Arrays.asList: fixed-size view of an array
String[] arr = {"a", "b"};
List<String> fixed = Arrays.asList(arr);
fixed.set(0, "x");  // OK (writes through to array)
// fixed.add("c");  // throws (size fixed)

// To make a truly mutable copy:
List<String> mut = new ArrayList<>(Arrays.asList(arr));

Queue と Deque の実装

ArrayDeque が推奨されるスタックとキューの実装です — レガシーの Stack(同期化されている)と LinkedList より高速です。PriorityQueue は Comparator で要素を順序付けします(デフォルトは最小ヒープ) — スケジューリング、top-K 問題に使用します。Deque は両端をサポートします。スタックセマンティクスには addFirst/removeFirst、キュー セマンティクスには addLast/removeFirst を使用します。並行キューには java.util.concurrent 実装(LinkedBlockingQueue など)を使用します。

java
import java.util.*;

// Queue: FIFO (offer/poll/peek)
Queue<String> queue = new LinkedList<>();
queue.offer("a"); queue.offer("b");
System.out.println(queue.peek());  // "a" (head)
System.out.println(queue.poll());  // "a" (remove head)

// Deque: double-ended (add/remove at both ends)
Deque<String> deque = new ArrayDeque<>();
deque.addFirst("a"); deque.addLast("b");
System.out.println(deque.peekFirst());  // "a"
System.out.println(deque.peekLast());   // "b"
deque.removeFirst(); deque.removeLast();

// ArrayDeque as a stack (push/pop/peek)
Deque<String> stack = new ArrayDeque<>();
stack.push("first");  // addFirst
stack.push("second");
System.out.println(stack.pop());  // "second" (LIFO)
System.out.println(stack.peek()); // "first"

// PriorityQueue: orders by Comparator (not insertion order)
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(5); pq.offer(1); pq.offer(3);
System.out.println(pq.poll());  // 1 (smallest first)
System.out.println(pq.poll());  // 3

// With custom comparator
PriorityQueue<String> byLen = new PriorityQueue<>(Comparator.comparingInt(String::length));
byLen.offer("aaa"); byLen.offer("a"); byLen.offer("aa");
System.out.println(byLen.poll());  // "a"

// BlockingQueue implementations (concurrency): see concurrency-deep section
// ArrayDeque is faster than Stack/LinkedList for stack/queue use

Map merge、compute、getOrDefault

これらの Map メソッドは一般的なパターンをアトミックで簡潔にします。merge はワードカウントのイディオムです — 既存と新しい値を結合し、関数が null を返すとエントリを削除します。computeIfAbsent はレイジーキャッシュパターン(メモ化)です。getOrDefault は null チェックを回避します。replaceAll はすべての値を変換します。これらは check-then-act の get/put ダンスよりはるかにクリーンで、並行コードで ConcurrentHashMap のアトミック更新の構成要素です。

java
import java.util.*;

Map<String, Integer> counts = new HashMap<>();

// getOrDefault: safe read with default
int n = counts.getOrDefault("missing", 0);  // 0, no null

// putIfAbsent: only set if not present
counts.putIfAbsent("a", 1);  // sets to 1
counts.putIfAbsent("a", 2);  // no change (already present)

// compute: recompute value for a key
counts.compute("a", (k, v) -> v == null ? 1 : v + 1);  // increment

// computeIfAbsent: lazy initialization (cache pattern)
Map<String, List<String>> groups = new HashMap<>();
groups.computeIfAbsent("key", k -> new ArrayList<>()).add("item");
// creates list if absent, then adds — atomic, no race in single thread

// computeIfPresent: update only if present
counts.computeIfPresent("a", (k, v) -> v > 0 ? v - 1 : null);  // decrement, remove at 0

// merge: combine existing and new value (great for counting)
counts.merge("word", 1, Integer::sum);  // word count pattern
counts.merge("word", 1, Integer::sum);  // now 2
counts.merge("word", 1, Integer::sum);  // now 3

// merge with removal: function returns null -> entry removed
counts.merge("word", 1, (old, v) -> old > 1 ? old - 1 : null);

// replaceAll: transform all values
Map<String, Integer> doubled = new HashMap<>(counts);
doubled.replaceAll((k, v) -> v * 2);

// Word frequency counter (idiomatic)
String text = "the cat the dog the bird";
Map<String, Integer> freq = new HashMap<>();
for (String w : text.split(" ")) {
    freq.merge(w, 1, Integer::sum);
}

Collections ユーティリティメソッド

Collections.* は古典的ユーティリティを提供します:sort、binarySearch(ソート済み入力が必要)、shuffle、reverse、frequency、min/max。singleton/empty ファクトリはイミュータブルな単一要素または空のコレクションを返します — null を返すより emptyList() を優先します。nCopies はメモリ効率的です(1要素を共有)。同期ラッパはレガシーコード用に存在しますが java.util.concurrent コレクションを優先します。チェックラッパはランタイムでジェネリック型違反をキャッチし、raw 型との相互運用に便利です。

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

List<Integer> nums = new ArrayList<>(List.of(3, 1, 4, 1, 5, 9, 2, 6));

// Sorting
Collections.sort(nums);                    // in-place, natural order
Collections.sort(nums, Comparator.reverseOrder());
nums.sort(Comparator.naturalOrder());      // List.sort (preferred)

// Searching (list must be sorted first)
int idx = Collections.binarySearch(nums, 4);  // index, or negative if absent

// Shuffling & reversing
Collections.shuffle(nums);
Collections.reverse(nums);

// Frequency & disjoint
int freq = Collections.frequency(nums, 1);  // count occurrences
boolean dis = Collections.disjoint(List.of(1, 2), List.of(3, 4));  // true

// Min/max
int min = Collections.min(nums);
int max = Collections.max(nums, Comparator.reverseOrder());

// Singleton collections (immutable, single element)
Set<String> one = Collections.singleton("only");
List<Integer> oneList = Collections.singletonList(42);
Map<String, Integer> oneMap = Collections.singletonMap("k", 1);

// Empty collections (prefer over returning null)
List<Object> empty = Collections.emptyList();
Set<Object> emptySet = Collections.emptySet();

// nCopies (immutable list of n copies)
List<String> padding = Collections.nCopies(5, "x");  // [x,x,x,x,x]

// Synchronized wrappers (legacy — prefer concurrent collections)
List<String> sync = Collections.synchronizedList(new ArrayList<>());

// Checked wrappers (catch heap pollution at runtime)
List<String> checked = Collections.checkedList(new ArrayList<>(), String.class);
// checked.add(123);  // throws ClassCastException at the add site
21

テスト(JUnit 5 と Mockito)

JUnit 5 の基礎

JUnit 5(Jupiter)アノテーション:@Test はテストをマークし、@BeforeEach/@AfterEach は各テストの前後に実行し、@BeforeAll/@AfterAll はクラスに1回実行(static でなければならない)。@DisplayName はテスト名をカスタマイズします。@Disabled はテストをスキップします。assertThrows は例外を検証します。テストは独立であるべきです — static フィールドではなく @BeforeEach で状態をリセットします。JUnit 5 は org.junit.jupiter.api にあります(JUnit 4 の org.junit とは異なります)。

java
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {
    private Calculator calc;

    @BeforeAll
    static void setUpOnce() {
        // runs once before all tests (must be static)
        System.out.println("Starting CalculatorTest");
    }

    @BeforeEach
    void setUp() {
        // runs before each test
        calc = new Calculator();
    }

    @Test
    @DisplayName("2 + 2 should equal 4")
    void addsTwoNumbers() {
        assertEquals(4, calc.add(2, 2));
    }

    @Test
    void dividesByZeroThrows() {
        ArithmeticException ex = assertThrows(
            ArithmeticException.class,
            () -> calc.divide(1, 0)
        );
        assertEquals("Division by zero", ex.getMessage());
    }

    @AfterEach
    void tearDown() {
        // runs after each test (cleanup)
        calc = null;
    }

    @AfterAll
    static void tearDownOnce() {
        // runs once after all tests
    }

    @Disabled("until bug #42 is fixed")
    @Test
    void skippedTest() {}
}

class Calculator {
    int add(int a, int b) { return a + b; }
    int divide(int a, int b) {
        if (b == 0) throw new ArithmeticException("Division by zero");
        return a / b;
    }
}

アサーション

JUnit 5 アサーション:assertEquals/assertNotEquals、assertTrue/False、assertNull/NotNull、assertSame(同一性)。assertAll はチェックをグループ化し、一部が失敗してもすべて実行します。assertTimeout は遅いテストを失敗させます。メッセージは文字列または Supplier(遅延 — 失敗時のみ構築、テスト合格時の文字列連結を回避)。assertIterableEquals は順序付きコレクションを比較します。これらは org.junit.jupiter.api.Assertions から来ます。

java
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class AssertionsTest {

    @Test
    void equality() {
        assertEquals(4, 2 + 2);
        assertEquals(4, 2 + 2, "math is broken");
        assertNotEquals(5, 2 + 2);
    }

    @Test
    void booleans() {
        assertTrue(5 > 3);
        assertFalse(5 < 3, "5 should not be less than 3");
    }

    @Test
    void nullness() {
        assertNull(null);
        assertNotNull(new Object());
    }

    @Test
    void sameInstance() {
        String a = "x";
        assertSame(a, a);          // ==
        assertNotSame(a, new String("x"));
    }

    @Test
    void collections() {
        assertIterableEquals(List.of(1, 2, 3), List.of(1, 2, 3));
        assertLinesMatch(List.of("a.*", "b"), List.of("abc", "b"));
    }

    @Test
    void groupedAssertions() {
        // All run even if one fails — reports all failures
        assertAll("person",
            () -> assertEquals("Alice", "Alice"),
            () -> assertEquals(30, 30),
            () -> assertNotNull("x")
        );
    }

    @Test
    void timeout() {
        // Fails if it takes longer than 100ms
        assertTimeout(Duration.ofMillis(100), () -> {
            Thread.sleep(10);
        });
        // assertTimeoutPreemptively: stops the task early (in another thread)
    }

    @Test
    void customMessage() {
        int result = 5;
        assertEquals(4, result, () -> "expected 4 but got " + result);
        // Supplier<String> — message built lazily only on failure
    }

    import java.time.Duration;
    import java.util.List;
}

パラメータ化テスト

パラメータ化テストは同じテストロジックを複数入力で実行します。@ValueSource は単一引数配列を提供します。@CsvSource は CSV 行を複数パラメータにマップします。@MethodSource(最も柔軟)は静的 Stream<Arguments> を使用します。@EnumSource は enum 値を反復します。@NullAndEmptySource は null/空のケースを追加します。これによりコピーアンドペーストのテストメソッドを排除し、データ駆動テストをクリーンにします。表示名は簡単な診断のためにパラメータを自動的に含みます。

java
import org.junit.jupiter.params.*;
import org.junit.jupiter.params.provider.*;
import static org.junit.jupiter.api.Assertions.*;

class ParameterizedTests {

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3, 4, 5})
    void positiveNumbersArePositive(int n) {
        assertTrue(n > 0);
    }

    @ParameterizedTest
    @ValueSource(strings = {"", "  ", "\t"})
    void blankStrings(String s) {
        assertTrue(s.isBlank());
    }

    @ParameterizedTest
    @NullAndEmptySource
    @ValueSource(strings = {"  ", "\t"})
    void blankOrNull(String s) {
        assertTrue(s == null || s.isBlank());
    }

    @ParameterizedTest
    @CsvSource({
        "1, 1, 2",
        "2, 3, 5",
        "10, -5, 5"
    })
    void addition(int a, int b, int expected) {
        assertEquals(expected, a + b);
    }

    @ParameterizedTest
    @CsvFileSource(resources = "/testdata.csv", numLinesToSkip = 1)
    void fromCsv(int a, int b, int expected) {
        assertEquals(expected, a + b);
    }

    @ParameterizedTest
    @MethodSource("additionProvider")
    void fromMethod(int a, int b, int expected) {
        assertEquals(expected, a + b);
    }
    static java.util.stream.Stream<Arguments> additionProvider() {
        return java.util.stream.Stream.of(
            Arguments.of(1, 1, 2),
            Arguments.of(2, 3, 5)
        );
    }

    @ParameterizedTest
    @EnumSource(TimeUnit.class)
    void allEnums(TimeUnit unit) {
        assertNotNull(unit);
    }
}

ライフサイクル、ネスト、条件付き

@Nested はライフサイクルを共有する内部テストクラスを作成します — 外側のセットアップが内側のテストに適用される BDD スタイルの 'when X then Y' 構造に最適。条件付きアノテーション(@EnabledOnOs、@EnabledIfSystemProperty、@EnabledIfEnvironmentVariable)は環境に基づいてテストをスキップします。@Tag は選択的実行のためにテストをグループ化します(例:fast vs slow、unit vs integration)。ネストクラスは @BeforeAll を持てません(非 static であるため)。これらの機能によりテスト編成が表現的になります。

java
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.condition.*;

class LifecycleTest {

    @Test
    void topLevel() {}

    @Nested
    @DisplayName("when stack is empty")
    class WhenEmpty {
        @BeforeEach
        void setUp() { /* stack is empty here */ }

        @Test
        void throwsOnPop() {
            assertThrows(Exception.class, () -> {});
        }

        @Nested
        @DisplayName("after pushing one element")
        class AfterPush {
            @BeforeEach
            void push() { /* push one */ }

            @Test
            void popReturnsElement() {
                // ...
            }
        }
    }

    // Conditional execution
    @Test
    @EnabledOnOs(OS.LINUX)
    void onlyOnLinux() {}

    @Test
    @EnabledIfSystemProperty(named = "env", matches = "ci")
    void onlyInCi() {}

    @Test
    @EnabledIfEnvironmentVariable(named = "DATABASE", matches = "postgres")
    void onlyWithPostgres() {}

    @Test
    @DisabledIf("customCondition")
    void conditional() {}
    static boolean customCondition() { return java.time.LocalTime.now().getHour() < 9; }

    // Tagging for selective runs
    @Test
    @Tag("slow")
    void slowIntegrationTest() {}

    @Test
    @Tag("fast")
    void fastUnitTest() {}

    // Run only fast: mvn test -Dgroups=fast
    import org.junit.jupiter.api.condition.OS;
    import java.util.concurrent.TimeUnit;

Mockito モック

Mockito は依存関係のテストダブルを作成します。@Mock はモックを作成し、@InjectMocks はモックが注入されたリアルオブジェクトを構築します。when(...).thenReturn(...) は戻り値をスタブし、verify(...) は相互作用をチェックします。引数マッチャー(any()、eq()、argThat())は呼び出しを柔軟にマッチします。Spy はリアルオブジェクトをラップします(部分的モック)。Arrange-Act-Assert パターンはテストを読みやすく保ちます。モックによりテスト対象を依存関係(データベース、ネットワーク、時間)から隔離します。

java
import org.junit.jupiter.api.*;
import org.mockito.*;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

class UserServiceTest {

    @Mock
    UserRepository repo;  // mock dependency

    @InjectMocks
    UserService service;  // real service with mocks injected

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    void returnsUserWhenFound() {
        // Arrange: stub the mock
        User alice = new User(1, "Alice");
        when(repo.findById(1)).thenReturn(alice);

        // Act
        String name = service.getUserName(1);

        // Assert
        assertEquals("Alice", name);
        verify(repo).findById(1);       // called once
        verify(repo, never()).findById(2);
    }

    @Test
    void throwsWhenNotFound() {
        when(repo.findById(99)).thenReturn(null);
        // or: when(repo.findById(99)).thenThrow(new RuntimeException());

        assertThrows(RuntimeException.class, () -> service.getUserName(99));
    }

    @Test
    void argumentMatchers() {
        when(repo.findById(anyInt())).thenReturn(new User(0, "default"));
        // matchers: eq(), any(), anyInt(), contains(), argThat()

        service.getUserName(42);
        verify(repo).findById(intThat(n -> n > 0));
    }

    @Test
    void verifyInteractionDetails() {
        service.getUserName(1);
        verify(repo, times(1)).findById(1);
        verify(repo, atLeastOnce()).findById(anyInt());
        verifyNoMoreInteractions(repo);
    }

    @Test
    void spy_partialMock() {
        List<String> spy = spy(new ArrayList<>());
        spy.add("real");
        when(spy.size()).thenReturn(100);  // stub one method
        assertEquals(100, spy.size());     // stubbed
        assertEquals(1, spy.size());       // wait, this would be 100 too
    }
}

interface UserRepository { User findById(int id); }
record User(int id, String name) {}
class UserService {
    UserRepository repo;
    UserService(UserRepository r) { repo = r; }
    String getUserName(int id) {
        User u = repo.findById(id);
        if (u == null) throw new RuntimeException("not found");
        return u.name();
    }
}
22

JUnit テスト

基本テスト

JUnit 5 は org.junit.jupiter.api の @Test を使用します。assertEquals は期待値と実際値が等しいことを検証します。他のアサーション:assertTrue、assertThrows、assertAll。

java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalcTest {
    @Test
    void testAdd() {
        assertEquals(5, calc.add(2, 3));
    }
}

パラメータ化テスト

パラメータ化テストは同じテストを異なる入力で実行します。@ValueSource は単一引数を提供します。@CsvSource は複数引数を提供します。テストの重複を削減します。

java
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4})
void testPositive(int n) { assertTrue(n > 0); }

@ParameterizedTest
@CsvSource({"1,2,3", "4,5,9"})
void testAdd(int a, int b, int expected) {
    assertEquals(expected, calc.add(a, b));
}

ライフサイクルメソッド

@BeforeAll/@AfterAll はクラスごとに1回実行(static でなければならない)。@BeforeEach/@AfterEach は各テストの前後に実行。データベース接続とモックセットアップに使用します。

java
class DbTest {
    @BeforeAll static void setupAll() { /* once before all */ }
    @AfterAll static void tearDownAll() { /* once after all */ }
    @BeforeEach void setup() { /* before each test */ }
    @AfterEach void tearDown() { /* after each test */ }
}

アサーション

assertAll は一部が失敗してもすべてのアサーションを実行します。assertThrows はコードが特定の例外をスローすることを検証します。時間制限テストには assertTimeout を使用します。

java
@Test
void testAll() {
    assertAll("person",
        () -> assertEquals("Alice", p.getName()),
        () -> assertEquals(30, p.getAge())
    );
}
@Test
void testException() {
    assertThrows(IllegalArgumentException.class, () -> service.process(-1));
}

Mockito

@Mock はモックオブジェクトを作成し、@InjectMocks はそれらを注入します。when().thenReturn() は呼び出しをスタブします。verify() はメソッドが呼び出されたかチェックします。Mockito は標準的なモッキングフレームワークです。

java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock UserRepository repo;
    @InjectMocks UserService service;
    @Test
    void testFind() {
        when(repo.findById(1)).thenReturn(Optional.of(new User("Alice")));
        assertEquals("Alice", service.findUser(1).getName());
        verify(repo).findById(1);
    }
}
23

Maven/Gradle

Maven POM

Maven は pom.xml を使用します。groupId/artifactId/version がプロジェクトを識別します。依存関係にはスコープがあります(compile、test、provided)。Maven は標準ディレクトリ構造を強制します。

java
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>myapp</artifactId>
  <version>1.0.0</version>
  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>5.10.0</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Maven コマンド

Maven ライフサイクル:clean、compile、test、package、install、deploy。各フェーズは先行フェーズを実行します。テストをスキップするには -DskipTests を使用します。

java
mvn clean          # Remove target/
mvn compile        # Compile sources
mvn test           # Run tests
mvn package        # Build JAR
mvn install        # Install to local repo
mvn dependency:tree  # Show dependency tree

Gradle ビルド

Gradle は build.gradle(Groovy)または build.gradle.kts(Kotlin)を使用します。コンパイル依存には implementation、テストには testImplementation。Gradle は Maven より高速です。

java
plugins { id 'java'; id 'application' }
repositories { mavenCentral() }
dependencies {
    implementation 'com.google.guava:guava:32.1.3-jre'
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
application { mainClass = 'com.example.App' }

Gradle コマンド

Gradle タスク:build、test、run、clean。ラッパー(gradlew)は一貫したバージョンを保証します。並列モジュールビルドには --parallel を使用します。

java
gradle clean       # Clean build
gradle build       # Build + test
gradle test        # Run tests
gradle run         # Run application
gradle bootRun     # Run Spring Boot
gradle dependencies  # Show dependency tree

マルチモジュールプロジェクト

マルチモジュールプロジェクトは大きなアプリを分割します。settings.gradle がモジュールをリストします。project(:core) がモジュール間依存関係を作成します。各モジュールに独自の build.gradle があります。

java
// settings.gradle
include 'core', 'web', 'api'
// build.gradle (root)
subprojects {
    apply plugin: 'java'
    repositories { mavenCentral() }
}
// In web/build.gradle
dependencies { implementation project(':core') }
24

Spring の基礎

Spring Boot アプリ

@SpringBootApplication は自動設定、コンポーネントスキャン、設定を有効にします。SpringApplication.run が組み込みサーバーを起動します。XML 設定を排除します。

java
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

REST コントローラ

@RestController は @Controller と @ResponseBody を組み合わせます。@GetMapping、@PostMapping はショートカットです。@PathVariable は URL パラメータを抽出し、@RequestBody は JSON をバインドします。

java
@RestController
@RequestMapping("/api/users")
public class UserController {
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
    @PostMapping
    public User create(@RequestBody User user) {
        return userService.save(user);
    }
}

依存性注入

@Autowired は依存関係を注入します。コンストラクタ注入が推奨されます(テスト可能、不変)。@Service、@Repository、@Component は注入用のステレオタイプです。

java
@Service
public class UserService {
    private final UserRepository repo;
    @Autowired  // Constructor injection (recommended)
    public UserService(UserRepository repo) {
        this.repo = repo;
    }
}

設定

@Configuration は設定クラスをマークします。@Bean は Spring 管理の Bean を宣言します。@Primary は Bean を優先させます。サードパーティクラスに使用します。

java
@Configuration
public class AppConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    @Bean @Primary
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
}

アプリケーションプロパティ

application.properties は Spring Boot を設定します。プロファイルは環境固有の設定を可能にします。spring.profiles.active=dev で有効化します。@Value または @ConfigurationProperties を使用します。

java
# application.properties
server.port=8080
spring.datasource.url=jdbc:postgresql://localhost/mydb
spring.jpa.hibernate.ddl-auto=update
# Profile-specific
# application-dev.properties
server.port=9090
25

JDBC ディープダイブ

Connection と Statement

DriverManager.getConnection が接続を確立します。Statement は静的 SQL を実行します。ResultSet は結果を反復します。常にリソースを閉じるか try-with-resources を使用してください。

java
Connection conn = DriverManager.getConnection(url, user, pass);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
while (rs.next()) {
    System.out.println(rs.getString("name"));
}
rs.close(); stmt.close(); conn.close();

PreparedStatement

PreparedStatement はクエリをパラメータ化することで SQL インジェクションを防ぎます。インデックス(1ベース)で値を設定します。try-with-resources が自動クローズします。事前コンパイルでパフォーマンスを向上します。

java
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setString(1, "Alice");
    ps.setString(2, "[email protected]");
    ps.executeUpdate();
}

トランザクション管理

setAutoCommit(false) がトランザクションを開始します。commit で永続化、rollback で取り消し。文が失敗した場合は整合性を維持するためロールバックします。Spring では @Transactional がこれを処理します。

java
conn.setAutoCommit(false);
try {
    stmt.executeUpdate("UPDATE accounts SET bal = bal - 100 WHERE id = 1");
    stmt.executeUpdate("UPDATE accounts SET bal = bal + 100 WHERE id = 2");
    conn.commit();
} catch (SQLException e) {
    conn.rollback();
}

接続プーリング

接続プーリングは接続を再利用します。HikariCP が最速のプールです。maximumPoolSize が同時接続を制限します。常に閉じる必要があります(プールに返却)。Spring Boot は HikariCP を自動設定します。

java
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setMaximumPoolSize(10);
HikariDataSource ds = new HikariDataSource(config);
Connection conn = ds.getConnection();

ResultSet メタデータ

ResultSetMetaData は結果構造を記述します:列名、型、プロパティ。ジェネリックデータアクセスに便利です。列インデックスは1ベースです。

java
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
ResultSetMetaData meta = rs.getMetaData();
int cols = meta.getColumnCount();
for (int i = 1; i <= cols; i++)
    System.out.println(meta.getColumnName(i) + ": " + meta.getColumnTypeName(i));
26

並行性ユーティリティ

ExecutorService

ExecutorService はスレッドプールを管理します。submit は非同期結果の Future を返します。get は完了までブロックします(タイムアウト付き)。常に executor をシャットダウンしてください。

java
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<String> future = executor.submit(() -> {
    Thread.sleep(1000);
    return "Result";
});
String result = future.get(5, TimeUnit.SECONDS);
executor.shutdown();

CompletableFuture

CompletableFuture は関数型非同期プログラミングを可能にします。supplyAsync は ForkJoinPool で実行します。thenApply は変換、thenAccept は消費、exceptionally はエラー処理します。

java
CompletableFuture.supplyAsync(() -> fetchData())
    .thenApply(data -> process(data))
    .thenAccept(result -> System.out.println(result))
    .exceptionally(ex -> { ex.printStackTrace(); return null; });

並行コレクション

ConcurrentHashMap は完全ロックなしでスレッドセーフです。CopyOnWriteArrayList は書き込み時にコピーします(読み取り多い)。BlockingQueue はプロデューサ-コンシューマパターンをサポートします。

java
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.computeIfAbsent("b", k -> k.length());
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
BlockingQueue<Task> queue = new LinkedBlockingQueue<>(100);

CountDownLatch と CyclicBarrier

CountDownLatch は N スレッドを待機(ワンショット)。CyclicBarrier は N スレッドを待機してからリセット(再利用可能)。スタートアップに latch を、フェーズ計算に barrier を使用します。

java
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++)
    new Thread(() -> { work(); latch.countDown(); }).start();
latch.await();  // Wait for all

CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All ready"));

Atomic 変数

Atomic 変数はロックフリーのスレッドセーフ操作を提供します。compareAndSet は楽観的ロックを可能にします。LongAdder は高競合カウンタで AtomicLong より高速です。

java
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
counter.compareAndSet(0, 1);
LongAdder adder = new LongAdder();
adder.increment();
27

JVM 内部

メモリ領域

JVM メモリ:ヒープ(オブジェクト、GC 管理対象)、スタック(メソッド呼び出し、スレッドごと)、Metaspace(クラスメタデータ)。Young Gen はコピー GC を使用し、Old Gen はマークスイープコンパクトを使用します。

java
// Heap: objects and arrays (shared)
// - Young Gen: Eden, S0, S1
// - Old Gen: long-lived objects
// Stack: method frames (per thread)
// Metaspace: class metadata (Java 8+)
// JVM flags: -Xms512m -Xmx2g

クラスロード

クラスロードは遅延です。Bootstrap はコア Java をロードし、Extension は拡張をロードし、Application はクラスパスをロードします。静的初期化子は1回実行されます。カスタムクラスローダでホットリロードが可能です。

java
// Bootstrap -> Extension -> Application classloaders
class MyClass {
    static { System.out.println("Static init"); }
}
// Class.forName("MyClass") triggers loading
// -verbose:class shows class loading

バイトコード

Java はバイトコード(スタックベース)にコンパイルします。javap -c がクラスファイルを逆アセンブルします。各命令はオペランドスタックをプッシュ/ポップします。Java エージェントはロード時にバイトコードを変更できます。

java
// javap -c MyClass.class
// Method int add(int, int):
//   iload_1      // Load local var 1
//   iload_2      // Load local var 2
//   iadd         // Add
//   ireturn      // Return int

JIT コンパイル

JIT は頻繁に実行されるバイトコードをネイティブコードにコンパイルします。階層型コンパイルは起動とピークパフォーマンスのバランスを取ります。ホットメソッドはインライン化および最適化されます。

java
// JIT compiles hot methods to native code
// -XX:+PrintCompilation  // Show JIT activity
// -XX:CompileThreshold=10000  // Method call count
// Tiered: Interpreter -> C1 -> C2

スレッドダンプ

スレッドダンプはすべてのスレッド状態とスタックトレースを表示します。デッドロックとハングのデバッグに不可欠です。jstack がコマンドラインツールです。BLOCKED と WAITING スレッドを探してください。

java
// Get thread dump
jstack <pid>
// Or: kill -3 <pid>
// Deadlock detection
jstack -l <pid> | grep -A 20 "Found deadlock"
28

ガベージコレクション

GC アルゴリズム

Serial GC は小さなアプリ用。Parallel GC はスループットを最大化。G1 GC はスループットとレイテンシのバランス(デフォルト)。ZGC は大きなヒープでサブミリ秒の一時停止を提供します。

java
# Serial GC (single-threaded)
-XX:+UseSerialGC
# Parallel GC (throughput)
-XX:+UseParallelGC
# G1 GC (balanced, default in Java 9+)
-XX:+UseG1GC
# ZGC (low-latency)
-XX:+UseZGC

G1 GC チューニング

G1 はヒープをリージョンに分割します。MaxGCPauseMillis がソフト一時停止目標を設定します。G1 は最もガベージの多いリージョンを優先します。ログ分析には GCViewer または GCEasy を使用します。

java
# Set heap
-Xms4g -Xmx4g
# Max GC pause target
-XX:MaxGCPauseMillis=200
# Region size (1-32MB)
-XX:G1HeapRegionSize=16m
# Enable GC logging
-Xlog:gc*:file=gc.log:time,uptime

メモリリーク

メモリリークは意図しないオブジェクト保持により発生します。静的コレクション、未クローズリソース、リスナー登録が一般的です。jmap がオブジェクト数を表示します。hprof を MAT で分析します。

java
// Common leak: static collections
static Map<String, Object> cache = new HashMap<>();
// Objects never removed -> leak
// Detect with:
jmap -histo <pid> | head -20
// Heap dump:
jmap -dump:format=b,file=heap.hprof <pid>

弱参照

WeakReference は強参照が存在しない場合に GC を許可します。SoftReference はメモリ圧力まで生存します。WeakHashMap のキーは弱参照です。GC を妨げないキャッシュに使用します。

java
WeakReference<Object> weakRef = new WeakReference<>(new Object());
SoftReference<byte[]> softRef = new SoftReference<>(new byte[1024]);
WeakHashMap<Object, String> map = new WeakHashMap<>();
map.put(key, "value");  // Entry removed when key is GC'd

ファイナライゼーション

finalize() は非推奨です(予測不能、遅い)。Cleaner API(Java 9+)はより良いクリーンアップを提供します。決定的クリーンアップには try-with-resources が推奨されます。

java
// Cleaner API (Java 9+)
class Resource implements AutoCloseable {
    private final Cleaner.Cleanable cleanable;
    Resource() {
        cleanable = Cleaner.create().register(this, () -> cleanup());
    }
    public void close() { cleanable.clean(); }
}
29

ストリームコレクタ

Grouping By

groupingBy は分類子で要素を分割します。第2引数は集計用のダウンストリームコレクタです。counting、averaging、summing が一般的なダウンストリームコレクタです。

java
Map<String, List<Person>> byCity =
    people.stream().collect(Collectors.groupingBy(Person::getCity));
Map<String, Long> countByCity =
    people.stream().collect(Collectors.groupingBy(
        Person::getCity, Collectors.counting()));

Partitioning

partitioningBy は2つのグループ(true/false)に分割します。boolean キーには groupingBy より効率的です。結果は常に両方のキーを持ちます。ダウンストリームコレクタが各パーティションを集計します。

java
Map<Boolean, List<Person>> partition =
    people.stream().collect(Collectors.partitioningBy(p -> p.getAge() >= 18));
// {false=[minors], true=[adults]}
Map<Boolean, Long> count =
    people.stream().collect(Collectors.partitioningBy(
        p -> p.getAge() >= 18, Collectors.counting()));

Joining

joining はオプションのデリミタ、プレフィックス、サフィックスで文字列を連結します。要素は文字列でなければなりません。最初に map を使用します。内部的に StringBuilder を使用します。

java
String names = people.stream()
    .map(Person::getName)
    .collect(Collectors.joining(", "));
// "Alice, Bob, Charlie"
String csv = people.stream()
    .map(p -> p.getName() + "=" + p.getAge())
    .collect(Collectors.joining("\n", "[", "]"));

Reducing

reducing はフォールド操作を実行します。3引数版はアイデンティティ、マッパー、リデューサを取ります。2引数版は Optional を返します。標準コレクタが不十分な場合に使用します。

java
int totalAge = people.stream()
    .collect(Collectors.reducing(0, Person::getAge, Integer::sum));
Optional<Person> oldest = people.stream()
    .collect(Collectors.reducing((p1, p2) ->
        p1.getAge() > p2.getAge() ? p1 : p2));

カスタムコレクタ

Collector.of はカスタムコレクタを作成します:supplier、accumulator、combiner、finisher。combiner は並列ストリームの部分結果をマージします。特殊な出力フォーマットに便利です。

java
Collector<Person, ?, String> toJson = Collector.of(
    StringBuilder::new,
    (sb, p) -> sb.append(`{"name":"${p.getName()}"}`),
    StringBuilder::append,
    StringBuilder::toString
);
30

よくある落とし穴

Integer キャッシュ

Java は -128 から 127 までの Integer 値をキャッシュします。== は値ではなく参照を比較します。キャッシュ範囲外の Integer では == は false を返します。Integer には常に .equals() を使用してください。

java
Integer a = 127; Integer b = 127;
System.out.println(a == b);  // true (cached)
Integer c = 128; Integer d = 128;
System.out.println(c == d);  // false (not cached)
System.out.println(c.equals(d));  // true

String の不変性

文字列は不変です:concat のようなメソッドは新しい文字列を返します。結果を代入し忘れるのが一般的なバグです。繰り返し連結には StringBuilder を使用します。

java
String s = "Hello";
s.concat(" World");  // Returns new string, s unchanged
System.out.println(s);  // "Hello"
// Use StringBuilder for mutation
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");  // Mutates in place

Equals と HashCode

等しいオブジェクトは等しいハッシュコードを持たなければなりません。HashMap と HashSet はバケット分けに hashCode を、比較に equals を使用します。equals をオーバーライドする場合、hashCode もオーバーライドしなければなりません。

java
class Person {
    String name;
    public boolean equals(Object o) {
        if (!(o instanceof Person)) return false;
        return name.equals(((Person)o).name);
    }
    public int hashCode() { return name.hashCode(); }
}

チェック例外と非チェック例外

チェック例外は宣言またはキャッチする必要があります(IOException、SQLException)。非チェック例外(RuntimeException)は処理を必要としません。広範な Exception のキャッチは避けてください。

java
// Checked: must catch or declare
try { Thread.sleep(1000); }
catch (InterruptedException e) { /* handle */ }
// Unchecked: RuntimeException
throw new IllegalArgumentException("bad input");

リソースリーク

リソースは閉じなければなりません。try-with-resources(Java 7+)は AutoCloseable を自動クローズします。これがないと例外がリークを引き起こします。リソースを未クローズのままにしないでください。

java
// BAD: resource leak
FileInputStream fis = new FileInputStream("file.txt");
// If exception here, fis never closed
// GOOD: try-with-resources
try (FileInputStream fis = new FileInputStream("file.txt")) {
    // Use resource
}  // Auto-closed even on exception
31

デザインパターン

Singleton

volatile 付きダブルチェックロッキングでスレッドセーフな遅延初期化を実現します。volatile は命令の並び替えを防ぎます。Enum シングルトンがよりシンプルです:public enum Singleton { INSTANCE; }。

java
public class Singleton {
    private static volatile Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) instance = new Singleton();
            }
        }
        return instance;
    }
}

Builder

Builder パターンは多くのオプションパラメータを持つオブジェクトを処理します。望遠鏡コンストラクタを回避します。内部 Builder がパラメータを流暢に収集します。build() が不変オブジェクトを作成します。

java
public class Pizza {
    private final String size;
    private final boolean cheese;
    private Pizza(Builder b) { size = b.size; cheese = b.cheese; }
    public static class Builder {
        private String size; private boolean cheese;
        public Builder size(String s) { size = s; return this; }
        public Builder cheese(boolean c) { cheese = c; return this; }
        public Pizza build() { return new Pizza(this); }
    }
}

Strategy

Strategy パターンは交換可能なアルゴリズムをカプセル化します。コンテキストが strategy インターフェースに委譲します。大きな if-else チェーンを回避します。開放閉鎖原則に従います。

java
interface PaymentStrategy { void pay(double amount); }
class CreditCard implements PaymentStrategy {
    public void pay(double amount) { System.out.println("Card: " + amount); }
}
class Cart { private PaymentStrategy strategy; void checkout() { strategy.pay(100); } }

Observer

Observer パターンは1対多の依存関係を定義します。サブジェクトが変更されると、すべてのオブザーバに通知されます。MVC、イベントシステム、リアクティブプログラミングで使用されます。

java
interface Observer { void update(String event); }
class Subject {
    private List<Observer> observers = new ArrayList<>();
    void subscribe(Observer o) { observers.add(o); }
    void notify(String event) { observers.forEach(o -> o.update(event)); }
}

Factory Method

Factory Method はオブジェクト作成のインターフェースを定義しますが、どのクラスをインスタンス化するかはサブクラスに決定させます。クライアントコードを具象クラスから疎結合にします。

java
abstract class Document { abstract void open(); }
class PDF extends Document { void open() { /* ... */ } }
abstract class DocFactory { abstract Document create(); }
class PDFFactory extends DocFactory { Document create() { return new PDF(); } }

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.