クラス定義の全体像
クラスは「状態(フィールド)と振る舞い(メソッド)」をひとまとめにする設計図です。基本構文は「パッケージ宣言→import→クラス宣言→フィールド→コンストラクタ→メソッド→ネスト型・初期化子・定数」の順で書くと読みやすく、IDEの補完とも相性が良いです。重要なのは「可視性(アクセス修飾子)」「不変性と初期化」「static とインスタンスの違い」を正しく押さえることです。
クラス宣言の基本構文
パッケージ・import・クラスヘッダ
package com.example.app; // 所属パッケージ
import java.util.List; // 使う型の import
public final class User { // アクセス+修飾子+class+名前
// フィールド・コンストラクタ・メソッドが続く
}
Java- アクセス修飾子: public(どこからでも)、なし(同一パッケージ内のみに公開)
- 修飾子: final(継承不可)、abstract(抽象クラス)、sealed(継承を限定)
- 継承・実装:
extends 親クラス、implements インターフェース, ...
public class Employee extends User implements Comparable<Employee> { /* ... */ }
Javaフィールド・メソッド・定数の定義
フィールド(状態)と可視性
public final class User {
private final String id; // 不変フィールド(コンストラクタで確定)
private String name; // 可変フィールド(検証付きで変更)
// 定数(共有の不変値)
public static final int MAX_NAME_LEN = 100;
}
Java- private にする: 外から直接いじらせず、整合性を守る
- final を好む: 不変にするとバグと並行性の問題が減る
- 定数は public static final: マジックナンバー排除、意味ある名前で再利用
コンストラクタ(初期化の入口)
public User(String id, String name) {
if (id == null || id.isBlank()) throw new IllegalArgumentException("id");
this.id = id;
this.name = normalize(name);
}
// コンストラクタのオーバーロード
public User(String id) { this(id, ""); }
Java- 検証はここで: 不正な初期状態を拒否して、以後の安全を高める
- this(…) で共通化: 似た初期化は一箇所にまとめる
- super(…): 親クラスがある場合は最初に呼ぶ
メソッド(振る舞い)
public String id() { return id; } // ゲッター(不変の公開)
public String name() { return name; }
public void rename(String newName) { // 変更は検証付き
var x = normalize(newName);
if (x.isEmpty() || x.length() > MAX_NAME_LEN) return;
this.name = x;
}
private static String normalize(String s) { // 共通処理は private static で
return s == null ? "" : s.trim().replaceAll("\\s+", " ");
}
Java- 公開最小: 必要な操作だけ public。内部は private で隠す
- static とインスタンス: その個体に依存しない処理は static にすると明確
初期化の流れと static/インスタンスの違い(重要ポイントの深掘り)
初期化の順序
- static 初期化: クラスロード時に一度だけ実行(static フィールド、static ブロック)
- インスタンス初期化: new 時に「フィールド初期化→インスタンス初期化子→コンストラクタ」
public final class Config {
public static final java.util.Locale DEFAULT_LOCALE; // static フィールド
static { DEFAULT_LOCALE = java.util.Locale.JAPAN; } // static 初期化ブロック
private final java.time.Instant createdAt = java.time.Instant.now(); // インスタンス初期化
{ /* インスタンス初期化ブロック(滅多に使わない) */ }
}
Java- 指針: 複雑な初期化はコンストラクタに集約。static ブロックは最小限に
static メンバーとインスタンスメンバー
public final class Counter {
private int value; // 個体ごとに異なる
private static int created = 0; // クラス全体で共有
public Counter() { created++; }
public void inc() { value++; }
public int value() { return value; }
public static int createdCount() { return created; }
}
Java- インスタンス: それぞれのオブジェクトの状態に紐づく
- static: クラス全体の共有(設定、定数、工場メソッドなど)。多用しすぎるとテスト困難になるので節度を
継承・インターフェース・特殊なクラス表現
継承とメソッドオーバーライド
public abstract class Shape {
public abstract double area();
public String label() { return "shape"; } // 共通の振る舞い
}
public final class Circle extends Shape {
private final double r;
public Circle(double r) { this.r = r; }
@Override public double area() { return Math.PI * r * r; }
@Override public String label() { return "circle"; }
}
Java- abstract: 抽象メソッドで「契約」を提示、子クラスが実装
- @Override: 上書きの意図を明示し、ミスを防ぐ
インターフェース(できることの契約)
public interface Discount { int apply(int subtotal); }
public final class RateDiscount implements Discount {
private final double rate;
public RateDiscount(double rate) { this.rate = rate; }
@Override public int apply(int subtotal) { return (int) Math.round(subtotal * (1 - rate)); }
}
Java- 複数実装が可能: 柔軟な差し替えとテストがしやすい
record・enum(特化構文)
public record Money(int amount, String currency) { // 不変の値クラス
public Money add(int delta) { return new Money(amount + delta, currency); }
}
public enum UserRole { ADMIN, EDITOR, VIEWER } // 限定された列挙
Java- record: equals/hashCode/toString が自動。値オブジェクトに最適
- enum: 取りうる値を型で限定し、switch などで安全に扱える
例題で身につける「フル装備のクラス」
完成度の高いクラス定義例
package com.example.domain;
import java.util.Objects;
public final class Email {
private final String value;
private Email(String value) { this.value = value; }
public static Email of(String raw) {
var v = normalize(raw);
if (!isValid(v)) throw new IllegalArgumentException("invalid email: " + raw);
return new Email(v);
}
public String value() { return value; }
@Override public String toString() { return value; }
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Email e)) return false;
return value.equals(e.value);
}
@Override public int hashCode() { return Objects.hash(value); }
private static String normalize(String s) {
return s == null ? "" : s.trim().toLowerCase(java.util.Locale.ROOT);
}
private static boolean isValid(String s) {
int at = s.indexOf('@');
return at > 0 && at == s.lastIndexOf('@') && at < s.length() - 1 && !s.contains(" ");
}
}
Java- ファクトリメソッド(of): 生成ルールを一箇所に集約、整合性を担保
- 不変: 外へは値だけ公開、内部状態は変更不可
- equals/hashCode: 値オブジェクトとして正しく比較・ハッシュ化
よくあるつまずきと回避
可視性を甘くする(public フィールド)
- 問題: 直接代入で整合性が崩れる
- 回避: 必ず private+検証付きメソッドで操作。必要な値だけゲッターで公開
コンストラクタで検証しない
- 問題: 不正な初期状態が後でバグになる
- 回避: コンストラクタ(または of)で必ずチェックし、例外で拒否
static を乱用する
- 問題: 共有状態が増えてテスト困難に
- 回避: 共有すべきもの(定数・設定)に限定。振る舞いはインスタンスへ
継承で無理に共通化
- 問題: 階層が複雑化、変更がつらい
- 回避: まずインターフェース+委譲で設計できないか検討。抽象化は「自然な共通点」に限定
仕上げのアドバイス(重要部分のまとめ)
クラスは「アクセス修飾子+適切な修飾子」で定義し、フィールドは private/可能なら不変、生成はコンストラクタ(または of)で検証、振る舞いは小さく明確に。static とインスタンスの責務を分け、初期化の順序を理解して安全に設計する。継承は慎重に、インターフェースと record/enum を使い分ける——この型を守るだけで、読みやすく壊れにくいクラス定義が手に入ります。
