本ツールで使用しているオープンソースライブラリ

本ツールのコードには 1 個のオープンソースライブラリが含まれています。

Java チートシート — クイックリファレンス

Java 17+ の構文、OOP、コレクション、頻出標準ライブラリをまとめたクイックリファレンスで、日常の約 80% をカバーします。

J

Java Java 17 (LTS)

JDK (OpenJDK / Oracle) · OOP・ジェネリクス・関数型 · 静的・強い型付け・名前的型付け

おすすめの学習パス

まず「Hello World とビルド環境」(javac/java または Maven)を通す → 変数、型、制御フローに慣れる → コレクションと Stream API でデータを処理 → オブジェクト指向(継承/インターフェース/ジェネリクス)に踏み込む → 例外処理とラムダをマスター → 最後に必要に応じてファイル、ネットワーク、時間、ビルド/デバッグを查べます。FAQ セクションは後から落とし穴回避に読み返すのがおすすめです。

1.Hello World とビルド環境

Java プログラムをゼロから作成し、コンパイルし、実行する:main メソッド、javac/java、パッケージ、ビルドツール。

最小プログラム

すべての Java プログラムは main メソッドから始まります:public static void main(String[] args)。System.out.println で 1 行出力します。

1
2
3
4
5
6
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
// ファイル名は public クラス名と同じにする:Hello.java

コンパイルと実行

javac は .java を .class バイトコードにコンパイルし、java が JVM を起動して実行します。javap で逆アセンブルしてバイトコードを確認できます。

1
2
3
4
// $ javac Hello.java # Hello.class を生成
// $ java Hello # 実行(.class 拡張子なし)
// $ javap -c Hello # 逆コンパイルしてバイトコードを確認
// $ java -version # JDK のバージョン確認

コマンドライン引数

main(String[] args) はコマンドライン引数を受け取り、args[0] が最初のユーザー引数です。args.length で個数を判定します。

1
2
3
4
5
6
7
8
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("No args");
return;
}
System.out.println("Hello, " + args[0] + "!");
}
// $ java Hello Nick

終了コード

System.exit(code) は JVM を終了させ、終了コードを返します:0 が成功、0 以外が失敗。main 以外のスレッドが動作中でも強制終了できます。

1
2
3
4
5
if (error) {
System.err.println("failed");
System.exit(1); // 0 以外は失敗を表す
}
// 正常終了なら呼ばなくても 0 を返す

パッケージ宣言

package は所属パッケージを宣言し、ディレクトリ構造と対応し、完全修飾名で利用されます。省略すると無名パッケージになります。

1
2
3
4
5
package com.example.app;
// ファイルは com/example/app/ ディレクトリに置く
public class Main {
// 完全修飾名:com.example.app.Main
}

import 文

import は他パッケージのクラスや static メンバーを取り込み、完全修飾名を不要にします。java.lang は既定でインポートされます。

1
2
3
4
5
import java.util.List;
import java.util.ArrayList;
import static java.lang.Math.PI; // 静的インポート
import java.util.*; // ワイルドカードインポート(多用は非推奨)
List<String> list = new ArrayList<>();

クラスとファイル名

public クラス名はファイル名と一致する必要があります。1 つの .java ファイルに複数の非 public クラスを含めることはできますが、public クラスは 1 つだけです。

1
2
3
4
5
// FileName.java の中:
public class FileName { // ファイル名と同じ名前
public static void main(String[] args) { }
}
class Helper { } // 非 public なら同ファイル可

Maven / Gradle

大規模プロジェクトではビルドツールを使います:Maven(pom.xml)と Gradle(build.gradle)が依存関係、コンパイル、テスト、パッケージを管理します。

1
2
3
4
5
6
7
8
// pom.xml (Maven)
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
// $ mvn compile / mvn test / mvn package
// $ gradle build / gradle run

2.変数と定数

変数の宣言、型推論、final 定数、スコープ、型変換。

変数宣言

宣言 = 型 + 名前 + 任意の初期化。Java のローカル変数は使う前に初期化が必要で、フィールドには既定値があります。

1
2
3
4
5
6
int count = 42;
double price = 19.99;
String name = "Nick";
boolean ok = true;
char c = 'A';
// 複数同時宣言:int a = 1, b = 2;

var 型推論

var(Java 10+)はコンパイラが型を推論します。ローカル変数にのみ使えます。型が曖昧なときは明示的な宣言が読みやすくなります。

1
2
3
4
5
var count = 42; // int
var name = "Nick"; // String
var list = new ArrayList<String>(); // ArrayList<String>
// var はフィールド・メソッド引数・戻り値には使えない
// var は単なる推論、実行時型は変わらない

final 定数

final ローカル変数は 1 回だけ代入できます。final フィールドは宣言時かコンストラクタで初期化が必要で、static final は定数です。

1
2
3
4
5
6
final int MAX = 100; // 読み取り専用のローカル変数
class Config {
final String name = "app"; // final フィールド
static final double PI = 3.14; // 定数
}
// final 変数への再代入はコンパイルエラー

スコープ

ブロックスコープ:波括弧内の変数はそのブロック内でのみ見えます。入れ子のブロックでは外側と同じ名前の変数を覆い隠せますが、おすすめしません。

1
2
3
4
5
6
7
int x = 1;
{
int y = 2; // このブロック内でのみ見える
System.out.println(x + y);
}
// y はブロック外ではアクセス不可
// System.out.println(y); // コンパイルエラー

命名規則

クラス名は UpperCamelCase、変数/メソッドは lowerCamelCase、定数は UPPER_SNAKE_CASE、パッケージ名は全て小文字です。

1
2
3
4
5
class UserProfile { }
int userId = 1;
String getUserName() { return "nick"; }
static final int MAX_RETRY = 3;
package com.example.app;

型変換

暗黙変換は狭い型から広い型へ(int→long→double)。明示的なキャストは精度を失う(切り捨て)可能性があります。

1
2
3
4
5
6
int i = 42;
long l = i; // 暗黙:int → long
int back = (int) l; // 明示的なキャスト
long big = 3000000000L;
int overflow = (int) big; // オーバーフロー:負の値に切り詰め
String s = String.valueOf(i); // 数値 → 文字列

null と NPE

null は参照型の空の値です。null に対してメソッド呼び出しやフィールドアクセスを行うと NullPointerException(NPE)が投げられます。Objects ユーティリティで安全に扱えます。

1
2
3
4
5
6
7
8
String s = null;
// s.length() // NPE!
if (s != null) {
System.out.println(s.length());
}
// Java 8+ では Optional で null 許容を表現:
Optional<String> opt = Optional.ofNullable(s);
opt.ifPresent(System.out::println);

リテラル

整数リテラルは _ で区切れて(可読性)、0x で 16 進、0b で 2 進表記できます。long には L、float には f を付けます。

1
2
3
4
5
6
7
int million = 1_000_000; // アンダースコア区切り
int hex = 0xFF; // 255
int bin = 0b1101; // 13
long big = 42L;
float f = 3.14f;
double d = 3.14;
char ch = '\u0041'; // 'A'

3.データ型

プリミティブ型とラッパークラス、文字列、配列、列挙型、ジェネリクス、record。

プリミティブ型

Java には 8 種類の基本型がある。byte / short / int / long(整数)、float / double(浮動小数点数)、char、boolean。値はそのまま格納される。

1
2
3
4
5
6
7
8
byte b = 1;
short s = 2;
int i = 42;
long l = 42L;
float f = 3.14f;
double d = 3.14;
char c = 'A';
boolean flag = true;

ラッパークラス

Integer/Double/Boolean などのラッパークラスはプリミティブをオブジェクトに格納します。必要に応じてオートボクシング/アンボクシングが暗黙的に行われます。

1
2
3
4
5
Integer num = 42; // 自動ボクシング int → Integer
int value = num; // 自動アンボクシング
Integer parsed = Integer.parseInt("42");
// ボックス化比較の注意:
// new Integer(42) == new Integer(42) は false

文字列

String は不変の文字シーケンスで、equals は内容比較、== は参照比較です。変更のたびに新しいオブジェクトが生成されます。

1
2
3
4
5
String s = "hello";
String t = new String("hello");
boolean eq = s.equals(t); // true(内容)
boolean sameRef = s == t; // false(参照)
String upper = s.toUpperCase(); // "HELLO"

配列

配列は固定長で、インデックスでアクセスし、length プロパティを持ちます。int[] で宣言、new int[n] で生成、{} で初期化します。

1
2
3
4
5
6
int[] arr = new int[5]; // デフォルト 0
int[] nums = { 1, 2, 3 };
int first = nums[0];
nums[2] = 99;
int len = nums.length; // 3(プロパティ、メソッドではない)
int[][] grid = new int[3][3]; // 二次元

列挙型

enum は定数の集合を定義し、フィールドやメソッドを持てる。switch で列挙値を直接扱える。コンパイル時に型安全が保証される。

1
2
3
4
5
6
enum Color { RED, GREEN, BLUE }
Color c = Color.RED;
String name = c.name(); // "RED"
int ord = c.ordinal(); // 0(宣言順)
Color parsed = Color.valueOf("BLUE");
// enum はコンストラクタとフィールドを持てる:enum Status { OK(200), ERR(500); ... }

ジェネリクス

ジェネリクスは型をパラメータ化します:List<T>、Map<K,V>。コンパイル時に型チェックし、実行時は消去されます(type erasure)。

1
2
3
4
5
6
7
List<String> names = new ArrayList<>();
Map<String, Integer> ages = new HashMap<>();
// ジェネリックメソッド:
static <T> T first(List<T> list) {
return list.get(0);
}
// ワイルドカード:List<? extends Number>

record 型

record(Java 14 でプレビュー、16 で正式)は不変のデータキャリアを 1 行で宣言します:コンストラクタ、equals、hashCode、toString が自動生成されます。

1
2
3
4
5
record Person(String name, int age) { }
var p = new Person("Nick", 30);
String n = p.name(); // アクセサ(getName ではない)
String s = p.toString(); // Person[name=Nick, age=30]
// 不変:フィールドは暗黙的に final

オートボクシングの落とし穴

ボクシング/アンボクシングは == や演算で落とし穴を隠します:-128〜127 の Integer はキャッシュされ、その範囲外では == が false になり得ます。

1
2
3
4
5
Integer a = 100, b = 100;
boolean same = a == b; // true(キャッシュプール)
Integer c = 200, d = 200;
boolean diff = c == d; // false!equals を使うべき
boolean ok = c.equals(d); // true

Object ルートクラス

すべてのクラスは暗黙的に Object を継承します:toString、equals、hashCode、clone、finalize。equals をオーバーライドしたら必ず hashCode もオーバーライドします。

1
2
3
4
5
6
7
8
9
10
11
class User {
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User u)) return false;
return name.equals(u.name);
}
@Override
public int hashCode() { return name.hashCode(); }
}

4.参照とメモリ

参照のセマンティクス、null、オブジェクトのコピー、ヒープ/スタック、GC、文字列プール。

参照のセマンティクス

Java にはポインタはなく、オブジェクト変数は参照を保持します(ポインタに似ています)。代入は同じオブジェクトを共有し、変異はすべての別名から見えます。

1
2
3
4
5
StringBuilder a = new StringBuilder("a");
StringBuilder b = a; // 参照コピー、オブジェクトを共有
b.append("b");
System.out.println(a); // "ab"(a も変わった)
// 基本型は値コピー:int x = y は互いに影響しない

null 参照

null は参照がどのオブジェクトも指していないことを意味します。null 比較には == / != を使い、メソッドへの null 引き渡しは NPE を起こし得ます。防御的に null チェックしましょう。

1
2
3
4
5
6
String s = getMaybe();
if (s != null) {
System.out.println(s.length());
}
// Objects.requireNonNull(s, "s must not be null");
// Objects.toString(s, ""); 安全な変換

Object のメソッド

toString はオブジェクトの説明、equals は内容の比較、hashCode はハッシュ値を返す。デフォルト実装は参照比較であり、内容による同値判定を行うにはこれらをオーバーライドする必要がある。

1
2
3
4
5
6
7
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override
public String toString() { return "(" + x + "," + y + ")"; }
}
System.out.println(new Point(1, 2)); // "(1,2)"

ヒープとスタック

オブジェクトはヒープに割り当てられ(GC が管理)、ローカル変数と参照はスタックに置かれます。new したオブジェクトの参照はスタック、本体はヒープにあります。

1
2
3
4
void method() {
int x = 1; // スタック:基本型
Person p = new Person(); // p の参照はスタック、オブジェクトはヒープ
} // メソッド終了、スタック解放、オブジェクトは GC 待ち

GC ガベージコレクション

JVM は到達不能なオブジェクトを自動的に回収するため、手動の free は不要です。System.gc() は単なるヒントで、即時実行は保証されません。

1
2
3
4
5
// 参照されなくなったオブジェクトは回収対象
Person p = new Person();
p = null; // 元オブジェクトは参照を失い GC 待ち
// System.gc(); // 推奨のみ、本番では呼ばない
// 世代別 GC:新生代 / 老年代

コピーのセマンティクス

配列/オブジェクトの代入は参照を共有します。独立したコピーが必要なら clone、Arrays.copyOf、手動コピーを使います。ディープコピーは層ごとにコピーします。

1
2
3
4
5
6
int[] a = { 1, 2, 3 };
int[] shallow = a; // 共有
int[] copy = a.clone(); // 独立したコピー
copy[0] = 99;
System.out.println(a[0]); // 1(copy は独立)
// オブジェクト配列の clone は浅いコピー、要素は共有のまま

文字列コンスタントプール

リテラル文字列はコンスタントプールにキャッシュされ、同じリテラルは同一オブジェクトを共有します。new String(...) はプール外の新しいオブジェクトを作ります。intern() でプールに入れます。

1
2
3
4
5
6
String a = "hello";
String b = "hello";
boolean same = a == b; // true(プール内の同一オブジェクト)
String c = new String("hello");
boolean diff = a == c; // false(ヒープ上の新オブジェクト)
String d = c.intern(); // プール登録後 a == d は true

finalize と Cleaner

finalize は GC 前に呼ばれますがタイミングは保証されません(廃止)。外部リソースの解放には AutoCloseable + try-with-resources を使います。

1
2
3
4
5
6
7
@Override
protected void finalize() { } // 非推奨、依存しない
// 正しい方法:
class Conn implements AutoCloseable {
public void close() { /* リソース解放 */ }
}
try (var c = new Conn()) { } // 自動で close

5.制御フロー

if/else、switch、for/while ループ、break/continue、三項演算子。

if / else

if/else は条件で分岐します。条件は boolean 式でなければなりません。else-if の連鎖で複数分岐できます。

1
2
3
4
5
6
7
8
9
10
int score = 85;
String grade;
if (score >= 90) {
grade = "A";
} else if (score >= 60) {
grade = "B";
} else {
grade = "F";
}
System.out.println(grade);

switch 式

switch(Java 14+)は -> で値を返したり case をまとめたりできます。従来の switch 文もアロー構文を使えます。

1
2
3
4
5
6
7
8
int day = 3;
String name = switch (day) {
case 1 -> "Monday";
case 2, 3 -> "Tue/Wed"; // 複数の値の合併
default -> "other";
};
// 従来の switch でも -> が使える:
// switch (day) { case 1 -> System.out.println("Mon"); }

for ループ

古典的 for:初期化、条件、増分。インデックスや逆順が必要ならこちら。コレクションの走査は拡張 for を優先します。

1
2
3
4
5
6
for (int i = 0; i < 10; i++) {
System.out.println(i); // 0..9
}
for (int i = arr.length - 1; i >= 0; i--) {
System.out.println(arr[i]); // 逆順
}

拡張 for

for-each は配列と Iterable コレクションをインデックスなしで走査します。コレクションの構造を破壊的に変更してはいけません(例外が投げられます)。

1
2
3
4
5
for (String name : names) {
System.out.println(name);
}
for (int n : nums) { /* 読み取り専用の走査 */ }
// 添字/削除が必要なら Iterator か通常の for

while / do-while

while は条件判定してから実行、do-while は少なくとも 1 回は実行します。回数が不定な場面(ストリーム読み、ポーリング)には while を使います。

1
2
3
4
5
6
int i = 0;
while (i < 5) { i++; } // 先に判定
int x = 0;
do {
x++; // 少なくとも 1 回は実行
} while (x < 3);

break と continue

continue は現在の周回をスキップし、break はループを抜け、ラベル付き break/continue は入れ子のループを制御します。

1
2
3
4
5
6
7
8
outer:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (j == 1) continue; // 今回をスキップ
if (i == 2) break outer; // すべてを抜ける
}
}
// ラベルのスコープ:outer: は外側ループをマーク

三項演算子

cond ? a : b は if/else を 1 行で書きます。両側の型は互換性が必要。入れ子の三項演算子は可読性が悪いので避けましょう。

1
2
3
4
5
int age = 20;
String type = age >= 18 ? "adult" : "minor";
// 等価:
String t2;
if (age >= 18) t2 = "adult"; else t2 = "minor";

return と早期 return

return はメソッドを終了し値を返します(void は return; のみ)。早期 return はメソッドを読みやすくします(ガード節)。

1
2
3
4
5
6
boolean isValid(String s) {
if (s == null || s.isEmpty()) return false;
if (s.length() > 10) return false;
return true;
}
// 早期リターンで深いネストを避ける

6.メソッドとラムダ

メソッドシグネチャ、パラメータ、オーバーロード、再帰、ラムダ、関数型プログラミング。

メソッド定義

メソッド = アクセス修飾子 + 戻り型 + 名前 + 仮引数リスト + 本体。void は戻り値なしを意味します。

1
2
3
4
5
6
7
public int add(int a, int b) {
return a + b;
}
public void say(String msg) {
System.out.println(msg); // void は戻り値なし
}
static int timesTwo(int x) { return x * 2; }

引数渡し

Java は常に値渡しです:プリミティブは値のコピー、参照型は参照のコピー(メンバー変更は呼び出し元に影響、参照の再代入は影響しない)。

1
2
3
4
5
6
void set(int x) { x = 99; } // 基本型のコピー
void mutate(List<String> l) { l.add("x"); }
int n = 1;
set(n); // n は依然 1
var list = new ArrayList<String>();
mutate(list); // list に "x" が追加された

可変長引数

...(可変長引数)は任意個数の同型引数を受け取り、本質的には配列です。引数リストの最後に 1 つだけ置けます。

1
2
3
4
5
6
7
8
int sum(int... nums) {
int total = 0;
for (int n : nums) total += n;
return total;
}
int s = sum(1, 2, 3); // 6
int s2 = sum(); // 0(空配列)
int s3 = sum(new int[]{1,2}); // 配列も渡せる

メソッドオーバーロード

同じ名前で仮引数リスト(個数/型)が異なるメソッドはオーバーロードです。実引数の型に応じて最も一致する版が選ばれます。戻り型は判別に含まれません。

1
2
3
4
5
int parse(String s) { return Integer.parseInt(s); }
int parse(int x) { return x; } // 型が異なる
int parse(String s, int radix) { // 数が異なる
return Integer.parseInt(s, radix);
}

再帰

メソッドが自身を呼び出すのが再帰です。基底条件がないと呼び出しスタックがオーバーフローします。

1
2
3
4
5
int factorial(int n) {
if (n <= 1) return 1; // ベースケース
return n * factorial(n - 1);
}
// 深い再帰はスタックオーバーフロー注意:呼び出しごとにコールスタックを消費

ラムダ式

ラムダは (引数) -> 式 で関数型インターフェースのインスタンスです。型推論の省略や単一引数の括弧省略もできます。

1
2
3
4
5
6
7
8
Runnable r = () -> System.out.println("run");
BiFunction<Integer, Integer, Integer> add =
(a, b) -> a + b;
// 引数 1 つなら括弧省略可:x -> x * 2
// 複数行の本体は波括弧 + return:
Comparator<Integer> c = (x, y) -> {
return x.compareTo(y);
};

メソッド参照

:: メソッド参照はラムダの短縮形です:ClassName::staticMethod、obj::instanceMethod、ClassName::new。

1
2
3
4
5
6
7
List<String> names = List.of("b", "a");
names.stream().sorted(String::compareTo);
// Class::staticMethod
names.forEach(System.out::println);
// obj::instanceMethod
names.forEach(s -> System.out.println(s));
// Class::new コンストラクタ参照:Supplier<Person> s = Person::new;

関数型インターフェース

抽象メソッドを 1 つだけ持つインターフェースはラムダのターゲットになれます。主なもの:Runnable、Function、Consumer、Predicate、Supplier。

1
2
3
4
5
Predicate<Integer> isEven = n -> n % 2 == 0;
Function<String, Integer> len = String::length;
Consumer<String> print = System.out::println;
Supplier<Double> rand = Math::random;
// @FunctionalInterface アノテーションで関数型インターフェースを宣言

Stream API

Stream はコレクションをチェーンで処理します:filter、map、collect。Stream は遅延評価で、ソースを変更しません。

1
2
3
4
5
6
7
List<Integer> nums = List.of(1, 2, 3, 4);
var evens = nums.stream()
.filter(n -> n % 2 == 0) // [2, 4]
.map(n -> n * 10) // [20, 40]
.toList(); // Java 16+
// 集計:sum/max/count/anyMatch/allMatch
int sum = nums.stream().mapToInt(Integer::intValue).sum();

7.文字列

文字列リテラル、連結、StringBuilder、よく使うメソッド、フォーマット。

文字列リテラル

ダブルクォート文字列、\n エスケープ、テキストブロック(Java 15+)は 3 つのダブルクォートで複数行を保持します。

1
2
3
4
5
6
7
8
String s = "Line\nTab\tindent";
String path = "C:\\Program Files\\";
// テキストブロック(Java 15+):3 つのダブルクォートで複数行テキストを包み、自動インデント
// String html = """
// <div>
// <p>Hello</p>
// </div>
// """.stripIndent();

連結

+ で文字列を連結し、他型と連結すると自動的に文字列へ変換されます。数回の連結なら十分、ループ内では StringBuilder を使いましょう。

1
2
3
4
5
String a = "foo" + "bar"; // "foobar"
int age = 30;
String msg = "Age: " + age; // 自動で文字列に変換
String joined = String.join(", ", "a", "b", "c");
// ループでの連結は StringBuilder を使う(mem 節参照)

よく使うメソッド

よく使うメソッド:length、substring、indexOf、replace、split、trim、大文字小文字変換。

1
2
3
4
5
6
7
String s = " Hello, World ";
int len = s.length(); // 14
char c = s.charAt(0); // ' '
String sub = s.substring(7, 12); // "World"
boolean has = s.contains("World"); // true
String rep = s.replace("World", "Java");
String[] words = s.trim().split(",");

StringBuilder

大量の連結には StringBuilder(スレッドセーフでない)または StringBuffer(スレッドセーフ)を使います:append して toString。

1
2
3
4
5
6
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i).append(",");
}
String result = sb.toString(); // 一度に変換
// 可変:削除/挿入は sb.deleteCharAt / sb.insert

フォーマット

String.format:%d 整数、%s 文字列、%.2f 小数、%n 改行。幅やフラグも指定できます。

1
2
3
4
5
double d = 1234.567;
String s = String.format("%.2f", d); // "1234.57"
String t = String.format("%d%%", 50); // "50%"
String pad = String.format("%5d", 42); // " 42"
String n = String.format("%,d", 1234567); // "1,234,567"

正規表現マッチ

String.matches は全体マッチ、replaceAll は正規表現で全置換、split は正規表現で分割。正規表現のメタ文字はエスケープに注意。

1
2
3
boolean m = "123-456".matches("\\d{3}-\\d{3}"); // true
String masked = "123456".replaceAll("(\\d{3})(\\d+)", "$1***");
String[] parts = "a,b;c".split("[,;]"); // [a, b, c]

文字処理

charAt で文字を取り出し、Character.isDigit/isLetter/isWhitespace で分類します。文字列は不変ですが文字は走査できます。

1
2
3
4
5
6
String s = "Java 17";
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (Character.isUpperCase(c)) { }
}
// あるいは:for (char c : s.toCharArray())

文字列変換

Integer.parseInt は文字列を数値に、valueOf/toString は数値を文字列に変換します。失敗時は NumberFormatException が投げられます。

1
2
3
4
5
6
7
int n = Integer.parseInt("42");
long l = Long.parseLong("42");
double d = Double.parseDouble("3.14");
String s = String.valueOf(42);
String hex = Integer.toHexString(255); // "ff"
// 安全な解析:
// try { ... } catch (NumberFormatException e) { }

8.コレクションと Stream

List/Set/Map/Queue の主な実装、繰り返し、Stream、ソート。

List

List は順序付きコレクション。ArrayList は配列実装(読み取りが速い)、LinkedList は連結リスト実装(挿入/削除が速い)。List.of は不変リスト。

1
2
3
4
5
6
7
8
List<String> list = new ArrayList<>();
list.add("a");
list.add(0, "first"); // 指定位置に挿入
String x = list.get(1);
list.remove("a");
int size = list.size();
boolean has = list.contains("b");
var fixed = List.of("a", "b"); // 不変

Set

Set は重複を除外します。HashSet は順序なしで O(1)、LinkedHashSet は挿入順を保持、TreeSet はソート済み。重複の add は false を返します。

1
2
3
4
5
6
Set<Integer> set = new HashSet<>();
set.add(1);
boolean added = set.add(1); // false(すでに存在)
boolean has = set.contains(1);
var ordered = new LinkedHashSet<String>(); // 順序保持
var sorted = new TreeSet<Integer>(); // 昇順

Map

Map はキー/値の対応。HashMap は O(1)、LinkedHashMap は挿入順を保持、TreeMap はキーでソート。getOrDefault で安全に取得できます。

1
2
3
4
5
6
7
8
Map<String, Integer> map = new HashMap<>();
map.put("Nick", 30);
Integer age = map.get("Nick");
int safe = map.getOrDefault("X", 0);
map.computeIfAbsent("k", k -> 1); // なければ計算
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}

Queue / Deque

Queue は FIFO(offer/poll/peek)、Deque は両端キュー(addFirst/addLast)。ArrayDeque は LinkedList より高速です。

1
2
3
4
5
6
7
Queue<Integer> q = new ArrayDeque<>();
q.offer(1); q.offer(2);
int head = q.peek(); // 1(削除しない)
int out = q.poll(); // 1(取り出す)
Deque<Integer> d = new ArrayDeque<>();
d.addFirst(1); d.addLast(2);
int first = d.pollFirst();

走査とイテレーション

拡張 for / Iterator / forEach でコレクションを走査します。走査中の削除は Iterator.remove か、集めてから削除します。

1
2
3
4
5
6
7
8
for (String s : list) { }
list.forEach(System.out::println);
// 走査しながら削除:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().isEmpty()) it.remove();
}
// 拡張 for では直接 remove できない(例外が投げられる)

ソート

List.sort または Collections.sort でソート、Comparator で順序をカスタマイズ、comparing で連結できます。

1
2
3
4
5
6
7
List<Integer> nums = new ArrayList<>(List.of(3, 1, 2));
nums.sort(null); // 自然順の昇順
nums.sort(Comparator.reverseOrder()); // 降順
// カスタムオブジェクトのソート:
people.sort(Comparator
.comparing(Person::age)
.thenComparing(Person::name));

Stream のチェーン操作

filter/map/sorted/distinct/limit でチェーンし、終端操作で実行がトリガされます。collect でコレクションに戻します。

1
2
3
4
5
6
7
8
var result = people.stream()
.filter(p -> p.age() >= 18)
.sorted(Comparator.comparing(Person::age).reversed())
.map(Person::name)
.distinct()
.limit(10)
.toList();
// 遅延:終端操作がなければ実際には実行されない

グループ化と集約

Collectors.groupingBy はキーでグループ化、partitioningBy はブールで分割、summarizingInt は統計を取ります。

1
2
3
4
5
6
7
Map<String, List<Order>> byRegion = orders.stream()
.collect(Collectors.groupingBy(Order::region));
Map<Boolean, List<Integer>> part = nums.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// 集計:Collectors.summingInt / averagingInt
int total = orders.stream()
.collect(Collectors.summingInt(Order::amount));

不変コレクション

List.of、Set.of、Map.of は不変コレクションを作成します。Collections.unmodifiableList は読み取り専用ビューをラップします。

1
2
3
4
5
var fixed = List.of(1, 2, 3); // 不変
// fixed.add(4); // UnsupportedOperationException
var mutable = new ArrayList<>(fixed); // 可変のコピー
var view = Collections.unmodifiableList(mutable);
// ラップ後はどんな変更も例外を投げる

9.メモリとパフォーマンス

GC、弱参照、メモリリーク、文字列連結のパフォーマンス、バッファ。

文字列連結のパフォーマンス

ループ内の + 連結は毎回新しい(不変の)String を生成します。StringBuilder で一度に組み立てるとパフォーマンスが大幅に向上します。

1
2
3
4
5
6
7
// 遅い:ループ連結で大量の中間オブジェクトを生成
String s = "";
for (int i = 0; i < 1000; i++) s += i;
// 速い:StringBuilder でその場に追記
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) sb.append(i);
String r = sb.toString();

弱参照とソフト参照

WeakReference は GC を妨げません(キャッシュ向き)、SoftReference はメモリ逼迫時に回収、PhantomReference は回収後にクリーンアップします。

1
2
3
4
5
WeakReference<BigObj> weak = new WeakReference<>(new BigObj());
System.gc();
BigObj obj = weak.get(); // すでに null の可能性
// ソフト参照:メモリ不足のときだけ回収(画像キャッシュに好適)
SoftReference<Image> soft = new SoftReference<>(image);

メモリリーク

よくあるリーク:静的コレクションがオブジェクトを保持、リソースが閉じられない、监听器未解除、ThreadLocal がクリーンアップされない。寿命の長い保有に短いオブジェクトを持たせないようにします。

1
2
3
4
5
// 静的 Map が伸び続ける = リーク
static Map<String, Session> sessions = new HashMap<>();
// 使い終わったら除去:sessions.remove(id)
// または WeakHashMap<String, Session> に変更
// リソースは使い終わったら close:try (var c = open()) { }

ThreadLocal

ThreadLocal はスレッドごとに独立した変数のコピーを持たせます。Web コンテナのスレッドプールでは remove() を忘れるとリクエスト間でリークします。

1
2
3
4
5
private static final ThreadLocal<SimpleDateFormat> fmt =
ThreadLocal.withInitial(SimpleDateFormat::new);
String d = fmt.get().format(date);
// 重要:使い終わったら必ず remove()、特にスレッドプール環境
// fmt.remove(); しないとスレッド再利用でオブジェクトリーク

ByteBuffer

ByteBuffer のダイレクトメモリ(allocateDirect)は GC 圧を減らし、NIO や大規模転送に適します。手動管理が必要です。

1
2
3
4
5
6
7
ByteBuffer buf = ByteBuffer.allocate(1024); // ヒープ
ByteBuffer direct = ByteBuffer.allocateDirect(1024); // 直接メモリ
buf.putInt(42).putDouble(3.14);
buf.flip(); // 読み取りモードに切替
int i = buf.getInt();
double d = buf.getDouble();
buf.clear(); // バッファ再利用

OutOfMemoryError

OOM はメモリ枯渇を意味します:Heap Space 満杯、Metaspace にクラス過多、Direct buffer 過多。JVM フラグを調整するかリークを突き止めます。

1
2
3
4
5
// JVM パラメータ:
// $ java -Xms512m -Xmx2g -XX:+HeapDumpOnOutOfMemoryError App
// -Xmx 最大ヒープ、-Xms 初期ヒープ
// ヒープオーバーフローでダンプ:.hprof を生成しツールで分析
// 調査:コレクションの無制限増加 / 静的保持 / リソースリークを探す

配列コピー

System.arraycopy は配列範囲を高速にコピーします。Arrays.copyOf はコピーして必要に応じて拡張します。手動ループは遅いです。

1
2
3
4
5
int[] src = { 1, 2, 3, 4 };
int[] dst = new int[4];
System.arraycopy(src, 1, dst, 0, 2); // [2, 3, 0, 0]
int[] copy = Arrays.copyOf(src, src.length);
int[] grown = Arrays.copyOf(src, 6); // 拡張して 0 を補う

オブジェクトプール再利用

大きなオブジェクトの頻繁な生成は GC 圧を高めます。オブジェクトプールで再利用インスタンスをキャッシュしますが、スレッドセーフと返却経路に注意。

1
2
3
4
5
6
7
8
9
10
11
// 簡易オブジェクトプール:大きなオブジェクトの頻繁な new を避ける
final class ConnectionPool {
private final ArrayDeque<Connection> idle = new ArrayDeque<>();
synchronized Connection acquire() {
return idle.isEmpty() ? new Connection() : idle.poll();
}
synchronized void release(Connection c) {
idle.push(c); // 返却して再利用
}
}
// プールが大きいほどメモリ使用も増える、再利用の利益とトレードオフ

10.オブジェクト指向

クラス、カプセル化、継承、多態、抽象クラス、インターフェース、アクセス修飾子。

クラスとオブジェクト

class はデータ型を定義します:フィールドが状態を保持、メソッドが振る舞いを定義、コンストラクタが初期化、new がインスタンスを生成。

1
2
3
4
5
6
7
8
9
10
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String greet() { return "Hi, " + name; }
}
var p = new Person("Nick", 30);

カプセル化

フィールドを private に、メソッドを public にしてアクセスを制御します。getter/setter に検証ロジックを追加して不変条件を守ります。

1
2
3
4
5
6
7
8
public class Account {
private double balance;
public double getBalance() { return balance; }
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException();
balance += amount;
}
}

継承

extends は基底クラスを継承します。Java は単一継承です。super で親のコンストラクタやメソッドを呼び出し、子クラスは親の一種(is-a)です。

1
2
3
4
5
6
7
8
9
public class Animal {
public void speak() { System.out.println("..."); }
}
public class Dog extends Animal {
@Override
public void speak() { System.out.println("Woof"); }
}
Animal a = new Dog();
a.speak(); // "Woof"(ポリモーフィズム)

多態

親クラス/インターフェース参照が子クラスのインスタンスを指し、仮想メソッドは実行時の型でディスパッチされます。@Override で明示します。

1
2
3
4
5
6
7
8
9
10
11
public class Shape {
public double area() { return 0; }
}
public class Circle extends Shape {
private final double r;
public Circle(double r) { this.r = r; }
@Override
public double area() { return Math.PI * r * r; }
}
Shape s = new Circle(2);
double a = s.area(); // 12.57(実際の型に応じる)

抽象クラス

abstract クラスはインスタンス化できず、abstract メソッドを持てます(子クラスが必ず実装)。テンプレート基底として状態を共有するのに使います。

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Shape {
public abstract double area(); // 実装なし
public void describe() {
System.out.println("Area: " + area());
}
}
public class Square extends Shape {
private final double side;
public Square(double s) { this.side = s; }
@Override
public double area() { return side * side; }
}

インターフェース

interface は契約を定義します:メソッドは既定で public abstract。Java 8+ で default/static メソッドが加わり、複数実装が可能です。

1
2
3
4
5
6
7
8
9
10
11
public interface Logger {
void log(String msg); // 抽象メソッド
default void warn(String m) { // default 実装
log("[WARN] " + m);
}
}
public class ConsoleLogger implements Logger {
@Override
public void log(String msg) { System.out.println(msg); }
}
Logger l = new ConsoleLogger(); // インターフェース指向

アクセス修飾子

public は全て公開、protected はパッケージ+サブクラス、既定はパッケージ、private は同クラス内のみ。クラスメンバーは既定でパッケージプライベート。

1
2
3
4
5
6
7
public class Demo {
public int pub; // どこからでも
protected int prot; // パッケージ内 + サブクラス
int def; // パッケージ内
private int priv; // このクラス内のみ
}
// トップレベルクラスは public かパッケージ内可視(デフォルト)のみ

static メンバー

static フィールド/メソッドはクラスに属し、インスタンスには属しません。static メソッドはインスタンスメンバーにアクセスできません。static ブロックで初期化します。

1
2
3
4
5
6
7
8
public class Counter {
private static int count; // クラスレベル
public static void inc() { count++; }
public static int get() { return count; }
}
Counter.inc(); // クラス経由でアクセス
Counter.inc();
System.out.println(Counter.get()); // 2

内部クラスと匿名クラス

内部クラス、匿名クラス、ラムダはコールバックを簡潔にします。static 内部クラスは外部インスタンスを保持しないため、メモリリークを避けられます。

1
2
3
4
5
6
7
8
// 匿名クラス(Java 8 より前の書き方):
Runnable r = new Runnable() {
@Override public void run() { }
};
// むしろ Lambda を推奨:
Runnable r2 = () -> System.out.println("hi");
// 静的ネストクラス:static class Builder { }
// 内部クラスは外側インスタンスの参照を自動保持(リーク注意)

11.例外処理

try/catch/finally、例外階層、検査例外/非検査例外、try-with-resources、カスタム例外。

try / catch / finally

try は失敗し得るコードを、catch は処理し、finally は成功失敗に関わらず実行されます(クリーンアップ用)。例外は上方向に伝播します。

1
2
3
4
5
6
7
try {
int n = Integer.parseInt("abc");
} catch (NumberFormatException e) {
System.out.println("bad number: " + e.getMessage());
} finally {
System.out.println("cleanup"); // 必ず実行される
}

複数 catch

複数の catch は型でマッチし、具体的な例外を先に汎用例外を後に置きます。マルチキャッチ | で関係のない型をまとめられます。

1
2
3
4
5
6
7
8
9
try {
process();
} catch (FileNotFoundException e) {
// 具体的な例外
} catch (IOException e) {
// 汎用の例外
} catch (IllegalArgumentException | IllegalStateException e) {
// multi-catch:| で結合(継承関係のないもの)
}

throw と再 throw

throw new は例外を投げます。catch 内の throw;(Java 7+)は元をそのまま再 throw し、スタックを保ちます。メソッドの throws で検査例外の可能性を宣言します。

1
2
3
4
5
6
7
throw new IllegalArgumentException("invalid value");
public void read() throws IOException {
throw new IOException("io error");
}
try { read(); } catch (IOException e) {
throw new RuntimeException(e); // ラップして再スロー
}

カスタム例外

カスタム例外は Exception(検査)または RuntimeException(非検査)を継承します。慣例としてクラス名は Exception で終えます。

1
2
3
4
5
6
7
8
9
public class ConfigException extends RuntimeException {
public ConfigException(String message) {
super(message);
}
public ConfigException(String message, Throwable cause) {
super(message, cause);
}
}
throw new ConfigException("bad config");

検査例外と非検査例外

検査例外(IOException など)は catch または宣言が必須、非検査例外(RuntimeException 系)はコンパイル時に強制されません。

1
2
3
4
5
6
7
// 検査例外:catch または throws が必須
public void read() throws IOException {
Files.readString(Path.of("a.txt"));
}
// 非検査例外:処理しなくてもよい
int x = Integer.parseInt("abc"); // コンパイルは通る、実行時に例外
// カスタム例外は RuntimeException のサブクラスが一般的

try-with-resources

try (リソース) { } は AutoCloseable.close() を自動的に呼び、例外時も閉じます。手動の finally 閉じを置き換えます。

1
2
3
4
5
try (var reader = new BufferedReader(
Files.newBufferedReader(Path.of("a.txt")))) {
String line = reader.readLine();
} // reader は自動で閉じられる
// 複数リソース:try (var a = ...; var b = ...) { }

例外のコスト

例外捕捉はコストが高い(スタックウォーキング)ので制御フローに使わないこと。予測可能なエラーは戻り値、null チェック、Optional を使います。

1
2
3
4
5
6
// 遅い:例外をフロー制御に使う
boolean ok1;
try { Integer.parseInt(s); ok1 = true; }
catch (NumberFormatException e) { ok1 = false; }
// 速い:空チェック/正規表現で先に判定
boolean ok2 = s != null && s.matches("\\d+");

ログと例外

例外は(スタック付きで)ログに出し、System.out で打ち捨てて続行してはいけません。ログフレームワーク(SLF4J + Logback)でレベル別に出力します。

1
2
3
4
5
6
7
// SLF4J + Logback:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger log =
LoggerFactory.getLogger(App.class);
// 例外スタックトレースを記録:
// log.error("failed", e); // e でスタックトレース保持

12.ファイルと IO

Files/Path を使ったモダン IO、Scanner、Reader/Writer、ストリーム読み込み。

Files 読み書き

java.nio.file.Files はモダンな API:readString/writeString、readAllLines、copy/move/delete。

1
2
3
4
5
String content = Files.readString(Path.of("in.txt"));
Files.writeString(Path.of("out.txt"), content);
List<String> lines = Files.readAllLines(Path.of("in.txt"));
Files.copy(Path.of("a"), Path.of("b"), StandardCopyOption.REPLACE_EXISTING);
Files.deleteIfExists(Path.of("tmp"));

Scanner 入力

Scanner はコンソールやファイルを読みます:nextLine で行、nextInt/nextDouble で型付き。hasNext で残入力を判定。

1
2
3
4
5
6
7
Scanner sc = new Scanner(System.in);
System.out.print("Name: ");
String name = sc.nextLine();
int age = sc.nextInt();
// ファイルから読む:
var file = new Scanner(Path.of("a.txt"));
while (file.hasNextLine()) System.out.println(file.nextLine());

Reader / Writer

文字ストリームでテキストを入出力:BufferedReader で行ごと読み、BufferedWriter で書き込み、try-with-resources で自動クローズ。

1
2
3
4
5
6
7
8
9
10
try (var reader = new BufferedReader(
new FileReader("a.txt"));
var writer = new BufferedWriter(
new FileWriter("b.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
} // 2 つのストリームは自動で閉じられる

バイトストリーム

InputStream/OutputStream はバイト単位:FileInputStream、BufferedInputStream でバッファリング。データ移動には transferTo。

1
2
3
4
5
6
7
8
9
try (var in = new FileInputStream("a.bin");
var out = new FileOutputStream("b.bin")) {
byte[] buf = new byte[8192];
int read;
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
}
}
// 簡易版:Files.copy(Path.of("a"), Path.of("b"))

コンソール IO

System.out が出力、System.in が入力、System.err がエラー。printf でフォーマット出力。

1
2
3
4
5
System.out.print("No newline");
System.out.println("With newline");
System.out.printf("%s is %d years old%n", "Nick", 30);
System.err.println("Error message"); // 標準エラー
// フォーマット:%n は改行(クロスプラットフォーム)、\n を使わない

Path パス

Path はパスを表します。resolve で連結、getFileName で名前、toAbsolutePath で絶対化、exists で存在確認、Files.walk で再帰列挙。

1
2
3
4
5
6
7
8
9
Path dir = Path.of("data");
Path file = dir.resolve("a.txt"); // data/a.txt
String name = file.getFileName().toString();
boolean exists = Files.exists(file);
// 再帰的に列挙:
try (var stream = Files.walk(dir)) {
stream.filter(p -> p.toString().endsWith(".txt"))
.forEach(System.out::println);
}

シリアライズ

ObjectOutputStream/ObjectInputStream でオブジェクトをシリアライズ(クラスは Serializable 実装が必要)。現代では JSON の方が一般的です。

1
2
3
4
5
6
7
8
9
10
class User implements Serializable {
private static final long serialVersionUID = 1L;
String name;
}
// 書き込み:
try (var out = new ObjectOutputStream(
new FileOutputStream("u.bin"))) {
out.writeObject(new User());
}
// 読み取り:ObjectInputStream in = ...; User u = (User) in.readObject();

一時ファイル

Files.createTempFile はシステムの一時ディレクトリにファイルを作ります。使い終わったら Files.deleteIfExists で削除し、残さないようにします。

1
2
3
4
5
6
Path tmp = Files.createTempFile("app-", ".log");
System.out.println(tmp); // システムの一時ディレクトリ
Files.writeString(tmp, "temp data");
// 使い終わったらすぐ削除、残骸を避ける:
Files.deleteIfExists(tmp);
// 一時ディレクトリ:Files.createTempDirectory("app");

13.よくある落とし穴(FAQ)

Java 開発者が最も踏む落とし穴:== と equals、ボクシングキャッシュ、並行処理、例外、コレクションの破壊的変更。

== vs equals

文字列/オブジェクトの内容比較には必ず equals を使い、== は参照比較です。リテラルプールで == が偶然 true になることはありますが、依存してはいけません。

1
2
3
4
5
6
7
// BAD:== は参照比較、リテラルプールが問題を隠す
String a = "nick";
String b = new String("nick");
boolean bad = a == b; // false
// GOOD:equals は内容を比較
boolean good = a.equals(b); // true

Integer キャッシュ

オートボクシングの -128〜127 はキャッシュされ、== が true になり得ますが、範囲外は false。オブジェクト比較は常に equals を使いましょう。

1
2
3
4
5
6
7
// BAD:== でボックス化した値を比較
Integer a = 200, b = 200;
boolean bad = a == b; // false
// GOOD:equals で比較
Integer c = 200, d = 200;
boolean good = c.equals(d); // true

null チェックと NPE

null に対するメソッド呼び出しは必ず NPE。null 許容値には Optional または事前 null チェックを使い、try-catch NPE を散らさない。

1
2
3
4
5
6
7
8
// BAD:null チェックなし
String s = find();
System.out.println(s.length()); // NPE の可能性
// GOOD:null チェックか Optional
String t = find();
System.out.println(t == null ? 0 : t.length());
// または Optional.ofNullable(find()).map(String::length).orElse(0)

例外を飲み込む

catch 後に何もしないと例外を静かに飲み込み、デバッグが困難になります。最低でもログを出すか、適切な例外に包んで再 throw します。

1
2
3
4
5
6
7
8
// BAD:静かに握りつぶす
catch (IOException e) { }
// GOOD:記録するか上位へ投げる
catch (IOException e) {
log.error("read failed", e);
throw new RuntimeException("read failed", e);
}

equals と hashCode

equals をオーバーライドしたら必ず hashCode もオーバーライドします。さもないと HashSet/HashMap の挙動が壊れます(論理的に等しいオブジェクトが別キーになる)。

1
2
3
4
5
6
7
8
// BAD:equals だけオーバーライド
class A { public boolean equals(Object o) { ... } } // hashCode が不一致
// GOOD:両方を同時にオーバーライド
class B {
public boolean equals(Object o) { /* 同じロジック */ }
public int hashCode() { return Objects.hash(fields); }
}

反復中の削除

拡張 for の中で List.remove を呼ぶと ConcurrentModificationException が投げられます。Iterator.remove か、集めてから削除します。

1
2
3
4
5
6
7
8
9
10
// BAD:拡張 for の中で削除
for (String s : list) {
if (s.isEmpty()) list.remove(s); // 例外を投げる
}
// GOOD:Iterator で削除
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().isEmpty()) it.remove();
}

日付の可変性

旧 Date/Calendar は可変でエラーになりやすいです。java.time(LocalDate、LocalDateTime)に統一すれば不変で安全。

1
2
3
4
5
6
7
// BAD:可変な Date
Date d = new Date();
d.setTime(0); // 状態が外部から変更される
// GOOD:java.time は不変
LocalDate today = LocalDate.now();
LocalDate next = today.plusDays(1); // 新しいオブジェクトを返す

スレッドセーフ

HashMap/ArrayList はスレッドセーフでないため、複数スレッドでの書き込みは壊れます。ConcurrentHashMap か同期ラッパーを使います。

1
2
3
4
5
6
7
8
// BAD:複数スレッドで HashMap に書き込む
Map<String, Integer> map = new HashMap<>();
// 並行 put は内部構造を壊す可能性
// GOOD:並行コレクション
Map<String, Integer> safe = new ConcurrentHashMap<>();
safe.put("k", 1);
// または Collections.synchronizedMap(new HashMap<>())

Stream の null

Stream の要素に null が混ざると filter/map で NPE になり得ます。先に filter(Objects::nonNull) で除外。

1
2
3
4
5
6
7
8
// BAD:null 要素を map に渡す
list.stream().map(String::toUpperCase) // null があると NPE
// GOOD:先に null を除去
list.stream()
.filter(Objects::nonNull)
.map(String::toUpperCase)
.toList();

ループ内連結

ループ内の += 連結は大量の中間 String を生成し、低性能です。StringBuilder を使いましょう。

1
2
3
4
5
6
7
8
// BAD:ループで連結
String s = "";
for (int i = 0; i < 10000; i++) s += i;
// GOOD:StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) sb.append(i);
String r = sb.toString();

14.並行処理とスレッド

Thread、synchronized、スレッドプール(ExecutorService)、CompletableFuture。

Thread

Thread または Runnable でスレッドを作成し、start で起動、join で待機、sleep で一時停止します。多くの場面ではスレッドプールが望ましいです。

1
2
3
4
5
6
7
8
9
10
// 方法 1:Thread を継承
Thread t = new Thread(() -> {
System.out.println("worker");
});
// 方法 2:Runnable
Runnable task = () -> System.out.println("run");
Thread t2 = new Thread(task);
t2.start(); // 起動
// t2.join(); // 完了を待つ
// Thread.sleep(100); // ミリ秒単位で停止

synchronized

synchronized でロックして相互排他を保証します。インスタンスメソッドは this、静的メソッドは Class をロックし、ブロックは任意オブジェクトをロックできます。

1
2
3
4
5
6
7
8
private int count;
public synchronized void increment() { count++; }
// 等価:synchronized(this) { count++; }
// 静的同期:
public static synchronized void inc2() { }
// ロックオブジェクトは専用の final フィールドが一般的:
private final Object lock = new Object();
synchronized (lock) { /* クリティカルセクション */ }

volatile

volatile は可視性(他スレッドから即座に見える)を保証しますが、原子性は保証しません。フラグには volatile、計数には AtomicInteger。

1
2
3
4
5
6
7
private volatile boolean running = true;
// 他のスレッドから即座に読み取り可能
// ただし volatile は原子操作を解決しない:
// volatile int n; n++; // 非アトミック!
// AtomicInteger を使う:
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();

スレッドプール

ExecutorService はスレッド再利用を管理します。newFixedThreadPool / newCachedThreadPool を使い、終わったら shutdown。

1
2
3
4
5
6
7
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
pool.submit(() -> System.out.println(Thread.currentThread().getName()));
}
pool.shutdown(); // 新規タスクを受け付けない
// pool.awaitTermination(5, TimeUnit.SECONDS);
// 推奨:Executors.newVirtualThreadPerTaskExecutor()(Java 21)

Future と Callable

Callable は戻り値を持つタスク、Future.get は結果をブロック取得。FutureTask は手動制御もできます。

1
2
3
4
5
6
7
8
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> f = pool.submit(() -> {
return compute(); // Callable は戻り値あり
});
int result = f.get(); // ブロックして結果を待つ
// f.get(5, TimeUnit.SECONDS); // タイムアウト
// f.cancel(true); // キャンセル
pool.shutdown();

CompletableFuture

非合成:thenApply で変換、thenCombine で結合、allOf で複数を待機。コールバックはチェーンで呼び出し元をブロックしません。

1
2
3
4
5
6
7
8
CompletableFuture.supplyAsync(() -> fetch())
.thenApply(data -> parse(data))
.thenAccept(result -> System.out.println(result))
.exceptionally(ex -> { log(ex); return null; });
// 複数を待つ:
var all = CompletableFuture.allOf(f1, f2);
all.join();
// join() はブロックして待ち、結果は get() で取り出す

並行コレクション

ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue はスレッドセーフ。手動ロックの通常コレクションよりこちらを優先。

1
2
3
4
5
6
7
Map<String, Integer> map = new ConcurrentHashMap<>();
map.put("k", 1);
map.compute("k", (k, v) -> v == null ? 1 : v + 1);
Queue<Task> q = new ArrayBlockingQueue<>(100);
q.offer(task); // 満杯なら false を返す
Task t = q.poll(); // 空なら null を返す
// CopyOnWriteArrayList は読み多く書き少ない場面向き

Lock インターフェース

ReentrantLock は synchronized より柔軟:タイムアウト、割り込み、公平。finally で必ず unlock します。

1
2
3
4
5
6
7
8
9
10
11
private final ReentrantLock lock = new ReentrantLock();
void work() {
lock.lock();
try {
/* クリティカルセクション */
} finally {
lock.unlock(); // 必ず finally で解放
}
}
// lock.tryLock(1, TimeUnit.SECONDS) タイムアウト付き
// ReadWriteLock は読み書き分離で並行性を向上

15.ネットワークと HTTP

HttpClient、URL リクエスト、JSON シリアライズ、WebSocket。

HttpClient 基本

java.net.http.HttpClient(Java 11+)で HTTP リクエストを送ります。send は同期、sendAsync は非同期。ビルダーで設定。

1
2
3
4
5
6
7
8
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.build();
HttpResponse<String> resp =
client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());

POST と JSON

JSON ボディ付き POST。Jackson か Gson で JSON を(デ)シリアライズ。

1
2
3
4
5
6
7
8
9
String json = "{\"name\":\"Nick\"}";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
// 解析:Jackson ObjectMapper om = new ObjectMapper();
// User u = om.readValue(resp.body(), User.class);

非同期リクエスト

sendAsync は CompletableFuture を返し、呼び出しスレッドをブロックしません。複数リクエストを thenApply で並行処理。

1
2
3
4
5
6
7
HttpClient client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://example.com")).build();
client.sendAsync(req, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join(); // ブロックして待つ(デモ)
// 本当の非同期環境では join を呼ばない

URL と URLConnection

旧 API の URL/HttpURLConnection は単純なリクエスト向け。たいていは HttpClient の方が簡潔。クエリパラメータは URL エンコードが必要。

1
2
3
4
5
6
7
URL url = new URL("https://example.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
String body = new String(conn.getInputStream().readAllBytes());
// パラメータのエンコード:
String q = URLEncoder.encode("中文", StandardCharsets.UTF_8);

JSON パース

Jackson と Gson は主要な JSON ライブラリ。@JsonProperty でフィールド名をマッピング、Java 17 の record と組み合わせられます。

1
2
3
4
5
6
7
8
9
// Gson:
Gson gson = new Gson();
User u = gson.fromJson(json, User.class);
String out = gson.toJson(u);
// Jackson:
ObjectMapper om = new ObjectMapper();
User u2 = om.readValue(json, User.class);
String s2 = om.writeValueAsString(u2);
// Jackson の依存は pom に追加

Web API 呼び出し

組み合わせ:リクエストを構築 → ステータスコードを確認 → デシリアライズ。404/500 などの非成功を処理。

1
2
3
4
5
6
7
8
9
10
var req = HttpRequest.newBuilder()
.uri(URI.create("https://api.github.com/users/" + login))
.header("User-Agent", "MyApp")
.GET().build();
var resp = client.send(req, BodyHandlers.ofString());
if (resp.statusCode() == 200) {
var user = om.readValue(resp.body(), User.class);
} else {
System.err.println("status: " + resp.statusCode());
}

WebSocket

java.net.http.WebSocket は双方向の常駐接続。Listener のコールバックで受信し、onOpen/onText で処理。

1
2
3
4
5
6
7
8
9
10
11
var ws = HttpClient.newHttpClient()
.newWebSocketBuilder()
.buildAsync(URI.create("wss://example.com/ws"),
new WebSocket.Listener() {
public void onOpen(WebSocket webSocket) {
webSocket.sendText("hello", true);
}
// onText/onError/onClose コールバック
})
.join();
ws.sendText("msg", true);

タイムアウトとリトライ

HttpRequest.timeout はリクエストのタイムアウト、HttpClient.connectTimeout は接続のタイムアウト。タイムアウト時は HttpTimeoutException。

1
2
3
4
5
6
7
8
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
var req = HttpRequest.newBuilder(uri)
.timeout(Duration.ofSeconds(10))
.build();
// タイムアウトは HttpTimeoutException を投げる、捕捉して処理
// 単純な再試行:catch をループして再試行(バックオフ付き)

16.日付と時刻

java.time のローカル日時、Instant、Duration/Period、フォーマット。

LocalDate

LocalDate は日付のみ(時刻なし):now、of、plusDays、minusMonths。不変でスレッドセーフ。

1
2
3
4
5
6
7
LocalDate today = LocalDate.now();
LocalDate date = LocalDate.of(2024, 1, 1);
LocalDate next = date.plusDays(7);
LocalDate prev = date.minusMonths(1);
int year = date.getYear();
boolean leap = date.isLeapYear();
DayOfWeek dow = date.getDayOfWeek();

LocalTime

LocalTime は時刻のみ(日付なし):now、of、plusMinutes。LocalDate と組み合わせて LocalDateTime に。

1
2
3
4
5
6
LocalTime time = LocalTime.now();
LocalTime t = LocalTime.of(14, 30, 0);
LocalTime later = t.plusHours(1);
int hour = t.getHour();
// 日付と組み合わせ:
LocalDateTime dt = LocalDateTime.of(date, t);

Instant タイムスタンプ

Instant は UTC 時点(エポック秒/ナノ秒)で、タイムゾーンを横断する保存に適します。システム時刻は System.currentTimeMillis。

1
2
3
4
5
6
7
Instant now = Instant.now();
long epochMilli = now.toEpochMilli();
Instant fromEpoch = Instant.ofEpochMilli(1_700_000_000_000L);
// システム時刻から:
long nowMs = System.currentTimeMillis();
// Instant と LocalDateTime の相互変換にはタイムゾーンが必要:
LocalDateTime ldt = LocalDateTime.ofInstant(now, ZoneId.systemDefault());

Duration と Period

Duration は時間ベースの量(秒/ナノ秒)、Period は日付ベースの量(年/月/日)。

1
2
3
4
5
6
Duration d = Duration.ofMinutes(90);
long seconds = d.toSeconds(); // 5400
Duration between = Duration.between(t1, t2);
Period p = Period.of(1, 2, 3); // 1年2か月3日
Period since = Period.between(birthday, today);
int years = since.getYears();

フォーマットとパース

DateTimeFormatter でフォーマットとパース。ISO などの組み込みや yyyy-MM-dd HH:mm:ss のようなカスタムパターンに対応。

1
2
3
4
5
6
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String s = LocalDateTime.now().format(fmt);
LocalDateTime parsed = LocalDateTime.parse("2024-01-01 12:30", fmt);
// 定義済みフォーマット:
String iso = LocalDate.now().format(DateTimeFormatter.ISO_DATE);
// 解析時の例外に注意:DateTimeParseException

タイムゾーン

ZonedDateTime はタイムゾーン付き、ZoneOffset は固定オフセット。ZoneId.systemDefault はローカル。保存は UTC で。

1
2
3
4
5
6
7
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
ZonedDateTime zdt = ZonedDateTime.now(shanghai);
Instant utc = zdt.toInstant();
ZonedDateTime back = utc.atZone(ZoneId.of("UTC"));
// 固定オフセット:
ZoneOffset offset = ZoneOffset.ofHours(8);
OffsetDateTime odt = OffsetDateTime.of(ldt, offset);

旧 API 変換

java.util.Date/Calendar と新 API の相互変換。Date は可変でエラーになりやすく、新規コードは java.time を使いましょう。

1
2
3
4
5
6
7
8
// Date → Instant:
Instant inst = new Date().toInstant();
// Instant → Date:
Date d = Date.from(Instant.now());
// 変換:
LocalDateTime ldt = LocalDateTime.ofInstant(
inst, ZoneId.systemDefault());
// Calendar はすでに時代遅れ、使わないこと

タイムスタンプユーティリティ

System.currentTimeMillis はミリ秒、nanoTime はナノ秒差(間隔計測専用、絶対時刻不可)。タイムスタンプとフォーマットの相互変換。

1
2
3
4
5
6
7
long start = System.nanoTime();
compute();
long elapsedNs = System.nanoTime() - start; // 間隔
// ミリ秒タイムスタンプ → LocalDateTime:
LocalDateTime t = LocalDateTime.ofInstant(
Instant.ofEpochMilli(System.currentTimeMillis()),
ZoneId.systemDefault());

17.プロセスとシステム

ProcessBuilder によるプロセス起動、システムプロパティ、環境変数、ランタイム情報。

ProcessBuilder

ProcessBuilder は外部プログラムを起動します。引数はリストで渡してインジェクション回避。redirectErrorStream で出力をマージ。

1
2
3
4
5
6
ProcessBuilder pb = new ProcessBuilder("git", "log", "-1");
pb.redirectErrorStream(true); // エラー出力をマージ
Process proc = pb.start();
String out = new String(proc.getInputStream().readAllBytes());
int code = proc.waitFor(); // 終了を待つ
System.out.println(out);

システムプロパティ

System.getProperty で JVM システムプロパティを読みます:user.home、java.version、os.name。setProperty で設定。

1
2
3
4
5
6
String home = System.getProperty("user.home");
String ver = System.getProperty("java.version");
String os = System.getProperty("os.name");
System.setProperty("my.prop", "value");
// すべて:System.getProperties().forEach(...)
// JVM パラメータ:-Dmy.prop=value

環境変数

System.getenv は環境変数を読みます(読み取り専用)、getenv() で全件。システムプロパティと環境変数は区別しましょう。

1
2
3
4
5
6
String path = System.getenv("PATH");
String home = System.getenv("HOME");
Map<String, String> all = System.getenv();
// 環境変数は読み取り専用;
// システムプロパティは -D か System.setProperty
// 設定読み取りは両方を確認推奨:getenv 優先かプロパティ優先

Runtime 情報

Runtime は JVM 情報を提供します:availableProcessors、maxMemory、totalMemory。gc() はヒント。

1
2
3
4
5
6
7
Runtime rt = Runtime.getRuntime();
int cores = rt.availableProcessors();
long maxMem = rt.maxMemory(); // 最大ヒープ
long used = rt.totalMemory() - rt.freeMemory();
// rt.gc() は回収を促すだけ(本番では使わない)
System.out.printf("cores=%d used=%dMB%n",
cores, used / 1024 / 1024);

コマンドライン引数の解析

main の args からオプションを解析: simple なケースは手動ループ、複雑なら picocli/JCommander ライブラリ。

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
String file = null;
boolean verbose = false;
for (String a : args) {
if (a.equals("-v")) verbose = true;
else if (a.equals("-f")) { /* 次の引数を取る */ }
else file = a;
}
}
// 複雑な CLI は picocli:@Command/@Option アノテーション

シャットダウンフック

Runtime.addShutdownHook で JVM 終了時のクリーンアップを登録。System.exit も発火。フックで重い処理は避けましょう。

1
2
3
4
5
6
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("shutting down...");
saveState();
}));
// JVM 正常終了/System.exit 時に実行
// フックスレッドは並行、重い処理をしない

作業ディレクトリ

user.dir システムプロパティはプロセスの現在の作業ディレクトリ。ProcessBuilder.directory で子プロセスの開始ディレクトリを指定。

1
2
3
4
5
6
7
String cwd = System.getProperty("user.dir");
System.out.println(cwd); // 現在の作業ディレクトリ
// 子プロセスの開始ディレクトリ(別ディレクトリで実行):
Path target = Path.of(System.getProperty("user.home"), "work");
ProcessBuilder pb = new ProcessBuilder("git", "status");
pb.directory(target.toFile());
Process p = pb.start();

システムプラットフォーム情報

システムプロパティはプラットフォーム情報を提供します:os.name(OS)、os.arch(アーキ)、file.separator(パス区切り)、line.separator(改行)。

1
2
3
4
5
6
String os = System.getProperty("os.name"); // Windows 11 / Linux
String arch = System.getProperty("os.arch"); // amd64
String sep = System.getProperty("file.separator"); // \ または /
String eol = System.getProperty("line.separator"); // 改行文字
String pathSep = System.getProperty("path.separator"); // ; または :
System.out.println(os + " " + arch);

18.正規表現

Pattern/Matcher、マッチ、キャプチャグループ、置換、よく使うパターン。

Pattern と Matcher

Pattern.compile で正規表現をコンパイル(キャッシュして再利用)、matcher でテキストにマッチ。find、matches、lookingAt の 3 種があります。

1
2
3
4
5
6
Pattern p = Pattern.compile("\\d{3}-\\d{4}");
Matcher m = p.matcher("call 123-4567");
boolean found = m.find(); // 部分マッチ
Matcher m2 = p.matcher("123-4567");
boolean full = m2.matches(); // 全体マッチ
// 直接判定:"123-4567".matches("\\d{3}-\\d{4}")

キャプチャグループ

丸括弧は部分文字列をキャプチャし、group(1) で 1 番目、名前付きグループ (?<name>...) は group("name")。find ループで全件取得。

1
2
3
4
5
6
7
8
9
Pattern p = Pattern.compile("(\\d{4})-(\\d{2})");
Matcher m = p.matcher("date: 2024-01");
if (m.find()) {
String year = m.group(1); // 2024
String month = m.group(2); // 01
}
// 名前付きグループ:
Pattern p2 = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})");
// m.group("year") で 2024 を取得

全件検索

find ループまたは matcher.results で全マッチ取得。全置換は replaceAll($1 などのグループ参照可)。

1
2
3
4
5
6
7
8
Matcher m = Pattern.compile("\\w+").matcher(text);
while (m.find()) {
System.out.println(m.group()); // 各単語
}
// Java 9+:
m.results().forEach(r -> System.out.println(r.group()));
// 置換:
String masked = "123456".replaceAll("(\\d{3})(\\d+)", "$1***");

置換

replaceAll / replaceFirst は正規表現で置換し、$1/$2 でグループを参照。Matcher.appendReplacement は逐次処理。

1
2
3
4
5
6
7
8
9
String s = "a1b2c3";
String all = s.replaceAll("\\d", "#"); // a#b#c#
String first = s.replaceFirst("\\d", "#"); // a#b2c3
// コールバック置換:
Matcher m = Pattern.compile("\\d").matcher(s);
StringBuffer sb = new StringBuffer();
while (m.find()) m.appendReplacement(sb, "{" + m.group() + "}");
m.appendTail(sb);
// sb = "a{1}b{2}c{3}"

Pattern フラグ

Pattern.CASE_INSENSITIVE は大小文字無視、MULTILINE は ^$ を行ごとに、DOTALL は . を改行にもマッチ。

1
2
3
4
5
6
Pattern p = Pattern.compile("^java",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher m = p.matcher("Java\nnot java\n");
// インラインフラグ:
Pattern p2 = Pattern.compile("(?i)java"); // 大文字小文字を無視
// (?s) DOTALL、(?m) MULTILINE

よく使うパターン

メール、URL、IP、電話番号のよく使う正規表現。本番品質の検証(実メール形式など)は専用ライブラリを使いましょう。

1
2
3
4
5
String email = "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$";
String ip = "^((?:\\d{1,3}\\.){3}\\d{1,3})$";
String phone = "^1[3-9]\\d{9}$"; // 中国本土の携帯
boolean ok = input.matches(phone);
// 注意:\\d は Java 文字列では正規表現の \d

量指定子とアンカー

* 0 回以上、+ 1 回以上、? 0 または 1 回、{n,m} 指定回数。^ 行頭、$ 行末、\b 単語境界。

1
2
3
4
5
6
"\\d+" // 1 個以上の数字
"colou?r" // color または colour
"\\d{2,4}" // 2 〜 4 桁の数字
"^start" // 行頭の start
"end$" // 行末の end
"\\bword\\b" // 独立した単語 word

先読みと後読み

先読み (?=...) は後続を検査、否定先読み (?!...) は後続を除外、後読み (?<=...) は先行を検査。いずれも文字を消費しません。

1
2
3
4
5
6
7
Pattern p = Pattern.compile("\\d+(?=\\s*元)"); // 元 が続く場合のみマッチ
Matcher m = p.matcher("价格 100 元");
if (m.find()) System.out.println(m.group()); // 100
// 否定先読み:数字の後に文字が続かない
Pattern p2 = Pattern.compile("\\d+(?!\\p{L})");
// 後読みは固定長が必要:
Pattern p3 = Pattern.compile("(?<=价格为)\\d+");

19.ビルドとデバッグ

Maven/Gradle、javac/jar、JUnit、JVM デバッグ。

Maven ライフサイクル

mvn compile/test/package/install は Maven のライフサイクルフェーズ。target ディレクトリに class と jar を出力。

1
2
3
4
5
6
// $ mvn clean compile # クリーンしてコンパイル
// $ mvn test # テストを実行
// $ mvn package # jar をパッケージング
// $ mvn install # ローカルリポジトリにインストール
// $ mvn dependency:tree # 依存ツリーを確認
// 出力は target/ ディレクトリ

pom.xml

pom.xml はプロジェクトを定義します:groupId/artifactId/version の座標、dependencies、properties。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>app</artifactId>
<version>1.0.0</version>
<properties><maven.compiler.release>17</maven.compiler.release></properties>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
</project>

Gradle

Gradle は Groovy/Kotlin DSL ベース。タスクがビルド手順を定義し、依存はセントラルリポジトリから解決。

1
2
3
4
5
6
7
8
9
// build.gradle:
plugins { id 'java' }
repositories { mavenCentral() }
dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
}
test { useJUnitPlatform() }
// $ gradle build / gradle test / gradle run

JUnit テスト

JUnit 5:@Test がテストメソッド、@BeforeEach が初期化、assert* がアサーション。mvn test で実行。

1
2
3
4
5
6
7
8
9
10
11
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void add_returns_sum() {
var calc = new Calculator();
assertEquals(5, calc.add(2, 3));
assertTrue(calc.add(1, 1) == 2);
}
}
// アサーション失敗は期待値と実測値を明示する

jar パッケージ

jar コマンドで class をパッケージ化。実行可能 jar には Main-Class のマニフェストが必要。java -jar で実行。

1
2
3
4
5
// $ jar cf app.jar com/ # ディレクトリをパッケージング
// $ jar cfe app.jar Main com/ # メインクラスを指定
// $ java -jar app.jar # 実行
// 内容確認:jar tf app.jar
// MANIFEST.MF の Main-Class: Main

JVM フラグ

-Xmx 最大ヒープ、-Xms 初期ヒープ、-XX:+PrintGCDetails GC ログ、-D システムプロパティ。

1
2
3
4
5
// $ java -Xms256m -Xmx2g -jar app.jar
// $ java -Dserver.port=8080 -jar app.jar
// $ java -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError app
// デフォルト値確認:java -XX:+PrintFlagsFinal -version
// デバッグ:java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

ログ設定

SLF4J をファサード、Logback を実装に。logback.xml でレベルと出力を設定。本番では System.out 禁止。

1
2
3
4
5
6
7
8
9
10
11
// logback.xml:
<configuration>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender" />
</configuration>
// コード:
private static final Logger log =
LoggerFactory.getLogger(App.class);
log.info("user {} logged in", id);

依存関係管理

Maven の依存はスコープで分類:compile/test/runtime。exclusions で推移的依存を除外、バージョン競合は dependencyManagement で統一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope> <!-- 仅测试期可用 -->
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
<exclusions> <!-- 排除传递依赖 -->
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
// バージョン競合は dependencyManagement でバージョンを統一

公式リンク

公式ドキュメントとリソースへの直接リンク。

このチートシートについて

このページは Java 17(LTS)の自己完結型クイックリファレンスで、言語コア、JDK の頻出ライブラリ、ビルドエコシステムを実プロジェクトの日常の約 80% をカバーします。内容はモダンなイディオムを重視:var によるローカル変数型推論、switch 式、テキストブロック、record、sealed クラス、Stream API。Java は 1995 年に Sun Microsystems がリリースし、「write once, run anywhere」の JVM エコシステムで知られ、エンタープライズバックエンド、Android 開発、ビッグデータで最も広く使われている言語の一つです。 19 のセクションはそれぞれ 1 つのテーマに焦点を当てます:基本構文、変数、型と参照、制御フロー、関数、文字列、コレクションフレームワーク(List/Set/Map)、メモリ管理(GC)、オブジェクト指向、例外処理、IO、よくある落とし穴、並行処理、ネットワーク、時間、プロセス、正規表現、ビルドツール(Maven/Gradle)。各サブセクションは「概念紹介 + そのままコピーできるコードスニペット」のペアで構成されています。 すべてのコードとテキストはブラウザ内でローカルにレンダリングされ、デバイスからデータは一切出ません。権威あるリファレンスは Oracle 公式 Java ドキュメントを参照してください。

バージョン 2.1.0