クラスとメソッド

要約

  • クラスは**データ(フィールド)振る舞い(メソッド)**の集合です。
  • カプセル化private+公開メソッド)、不変設計継承/抽象化/インターフェースを正しく使い分けます。
  • equals/hashCode/toStringの契約、ジェネリクス可変長引数なども実務で必須です。

1. クラスの基本構成

public class Account {
    // フィールド(状態)
    private String id;
    private int balance;

    // コンストラクタ
    public Account(String id, int initial) {
        this.id = id;
        this.balance = initial;
    }

    // メソッド(振る舞い)
    public void deposit(int amount) {
        if (amount <= 0) throw new IllegalArgumentException("amount > 0");
        this.balance += amount;
    }

    public int getBalance() { return balance; }
}
  • フィールドは原則private、外部公開はメソッド経由にします。
  • thisインスタンス自身を指します。

2. アクセス修飾子とカプセル化

  • public:どこからでもアクセス
  • (指定なし):同一パッケージ内
  • protected:同一パッケージ+サブクラス
  • private:同一クラス内のみ
    方針:最小権限の原則。まずprivate、必要時のみ公開します。

3. コンストラクタと不変オブジェクト

public class User {
    private final String id;   // 再代入不可
    private final String name;

    public User(String id, String name) {
        if (id == null || name == null) throw new IllegalArgumentException();
        this.id = id;
        this.name = name;
    }

    public String id() { return id; }
    public String name() { return name; }
}
  • フィールドをfinalにし**不変(immutable)**にすると安全・シンプル。
  • 代替として**record(Java 16+)**も有効:record User(String id, String name){}

4. メソッドの基本:インスタンス/static/オーバーロード/オーバーライド

class MathUtil {
    // staticメソッド:状態を持たない汎用処理
    public static int clamp(int v, int min, int max) {
        return Math.max(min, Math.min(max, v));
    }
}

class Greeter {
    private final String prefix;
    public Greeter(String prefix) { this.prefix = prefix; }

    // オーバーロード(同名・引数違い)
    public String greet(String name) { return prefix + ", " + name; }
    public String greet(String first, String last) { return greet(first + " " + last); }

    @Override // オーバーライド(継承時に振る舞いを差し替え)
    public String toString() { return "Greeter(" + prefix + ")"; }
}
  • staticインスタンスに依存しないユーティリティへ限定。
  • オーバーロイドは引数の型/個数で区別、戻り値だけでは区別不可。

5. 継承・抽象クラス・インターフェース

abstract class Shape {
    // 具体実装はサブクラスへ委譲
    public abstract double area();
}

class Circle extends Shape {
    private final double r;
    public Circle(double r){ this.r = r; }
    @Override public double area(){ return Math.PI * r * r; }
}

interface Printable {
    String print();                // 抽象メソッド
    default String header(){ return "[PRINT]"; } // 既定実装
}
  • 抽象クラス:共通実装+抽象メソッドを混在。
  • インターフェース:契約(API)の提示。多重実装可。defaultで共通実装も可。
  • is-aでない継承は避け、委譲を優先します。

6. equals / hashCode / toStringComparable

import java.util.Objects;

public final class Product implements Comparable<Product> {
    private final String code;
    private final int price;

    public Product(String code, int price) {
        this.code = Objects.requireNonNull(code);
        this.price = price;
    }

    @Override public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Product p)) return false;
        return price == p.price && code.equals(p.code);
    }

    @Override public int hashCode() { return Objects.hash(code, price); }

    @Override public String toString() { return "Product[" + code + "," + price + "]"; }

    @Override public int compareTo(Product o) {
        int c = this.code.compareTo(o.code);
        return (c != 0) ? c : Integer.compare(this.price, o.price);
    }
}
  • equalsを実装したら、hashCodeも整合させます(同値→同ハッシュ)。
  • ソートが必要ならComparableまたはComparatorを使用します。

7. ネスト/内部クラス

public class Outer {
    private int base = 10;

    // staticネスト:Outerと独立(Outerのインスタンス不要)
    public static class Nested {
        public int twice(int x){ return x * 2; }
    }

    // 非static内部クラス:Outerのインスタンスに結びつく
    public class Inner {
        public int addBase(int x){ return x + base; } // Outer.this.baseへアクセス可
    }
}
  • 依存がないなら**staticネスト**。依存が強いときのみ内部クラスを使います。

8. ジェネリクス(型パラメータ)

class Box<T> {
    private final T value;
    public Box(T value){ this.value = value; }
    public T get(){ return value; }
}

// メソッドのジェネリクス
class Util {
    public static <T> T first(java.util.List<T> list){ return list.get(0); }
}
  • ジェネリクスで型安全キャスト削減を実現します。
  • ワイルドカード概念:読み取り中心は? extends T、書き込み中心は? super T

9. 可変長引数(varargs)

static int sum(int... xs) {    // 0個以上のintを受け取る
    int s = 0;
    for (int x : xs) s += x;
    return s;
}
  • 呼び出し側はsum(1,2,3)や配列sum(arr)のどちらも可。
  • 実引数が多すぎる設計は避け、ビルダー値オブジェクトを検討します。

10. パッケージと可視性・import

  • 先頭にpackage com.example.app;を宣言(1ファイル=1公開クラスが原則)。
  • 別パッケージの型はimportで参照。
  • 大規模では**モジュール(module-info.java)**で依存を明示します。

11. 1ファイル実用サンプル(要点総合)

import java.util.Objects;
import java.util.List;

public class ClassMethodDemo {

    // --- 不変値オブジェクト ---
    public static final class Point {
        private final int x, y;
        public Point(int x, int y){ this.x = x; this.y = y; }
        public int x(){ return x; }
        public int y(){ return y; }
        @Override public boolean equals(Object o){
            if (this == o) return true;
            if (!(o instanceof Point p)) return false;
            return x == p.x && y == p.y;
        }
        @Override public int hashCode(){ return Objects.hash(x, y); }
        @Override public String toString(){ return "Point(" + x + "," + y + ")"; }
    }

    // --- 抽象/具象 ---
    static abstract class Shape { public abstract double area(); }

    static final class Rect extends Shape implements Printable {
        private final int w, h;
        public Rect(int w, int h){
            if (w <= 0 || h <= 0) throw new IllegalArgumentException();
            this.w = w; this.h = h;
        }
        @Override public double area(){ return (double) w * h; }
        @Override public String print(){ return header() + " Rect " + w + "x" + h + " area=" + area(); }
    }

    static final class Circle extends Shape implements Printable {
        private final int r;
        public Circle(int r){ if (r <= 0) throw new IllegalArgumentException(); this.r = r; }
        @Override public double area(){ return Math.PI * r * r; }
        @Override public String print(){ return header() + " Circle r=" + r + " area=" + area(); }
    }

    interface Printable {
        String print();
        default String header(){ return "[PRINT]"; }
    }

    // --- ユーティリティ(static) ---
    static class Mathx {
        static int clamp(int v, int min, int max){ return Math.max(min, Math.min(max, v)); }
        static <T> T first(List<T> list){ return list.get(0); } // ジェネリックメソッド
    }

    // --- varargs ---
    static int sum(int... xs){ int s=0; for(int x:xs) s+=x; return s; }

    public static void main(String[] args) {
        Point p = new Point(3, 5);
        Rect  r = new Rect(4, 6);
        Circle c = new Circle(5);

        System.out.println(p);         // Point(3,5)
        System.out.println(r.print()); // [PRINT] Rect 4x6 area=24.0
        System.out.println(c.print()); // [PRINT] Circle r=5 area=78.5398...

        System.out.println(Mathx.clamp(120, 0, 100)); // 100
        System.out.println(sum(1,2,3,4));             // 10

        List<String> names = List.of("Alice","Bob");
        System.out.println(Mathx.first(names));       // Alice
    }
}

ベストプラクティス要点

  • 最小権限private中心)と不変設計を基本にします。
  • ビジネスルールはメソッドへ集約し、データと振る舞いを同居させます。
  • 継承は慎重に、まずインターフェース+委譲を検討します。
  • equals/hashCode同時実装し、toStringで診断容易性を高めます。
  • ユーティリティはstaticに限定、状態を持たせない方針で統一します。