1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

オブジェクト指向って何?RPGで理解する超入門 (カプセル化)

Last updated at Posted at 2025-12-17

title_banner_v2.png


はじめに

こんにちは。このシリーズでは、RPGゲームを作りながらJavaのオブジェクト指向を学んでいます。

前回までのおさらい:

第4回では、コンストラクタを使ってインスタンスの初期化を学びました。
職業システムを実装し、作成時に適切な初期値を設定する方法を理解しましたね。

しかし、大きな問題が残っています。

現在のコードでは、こんなことができてしまいます:

Player hero = new Player("勇者", "Warrior");
hero.hp = -100;        // ❌ マイナスのHPを設定できてしまう!
hero.name = null;      // ❌ nullを設定できてしまう!
hero.attack = 999999;  // ❌ チート級の攻撃力を設定できてしまう!

実際に、私のスクールでも受講生がこういった不正な値を設定してしまい、
プログラムがバグだらけになることがよくあります。

今回のテーマは「カプセル化(Encapsulation)」です。

データを外部から勝手に変更できないように「守る」技術を学びます。

この記事で学ぶこと:

  • カプセル化とは何か
  • アクセス修飾子(private, public, protected, default)
  • getter/setterメソッド
  • バリデーション(値のチェック)
  • 不正な値からデータを守る実践的な方法

所要時間: 約20〜25分


今回のクエスト:装備管理システムを安全にしよう

equipment_system_v2.png

今回は、RPGの「装備管理システム」を安全に実装します。

現在の問題:

  • HPをマイナスにできてしまう
  • 装備を勝手にnullにできてしまう
  • 最大値を超える値を設定できてしまう
  • ステータスの整合性が保てない

これらを「カプセル化」で解決します!


カプセル化とは?

encapsulation_concept_v2.png

定義

カプセル化(Encapsulation) とは、オブジェクトのデータ(フィールド)を外部から直接アクセスできないように隠蔽し、専用のメソッドを通してのみアクセスできるようにする技術です。

現実世界の例:銀行口座

銀行口座を考えてみましょう。

カプセル化なし(危険):

あなたの口座残高: 10万円
↓
誰でも勝手に書き換えられる
↓
残高: -100万円(誰かが勝手に変更!)

カプセル化あり(安全):

あなたの口座残高: 10万円(非公開)
↓
ATM(専用の窓口)経由でのみアクセス可能
↓
出金リクエスト: 150万円
→ ❌ 残高不足で拒否される
↓
出金リクエスト: 5万円
→ ✅ 承認される
→ 残高: 5万円

カプセル化の目的:

  1. データの保護: 外部から勝手に変更されないようにする
  2. 妥当性の保証: 不正な値を防ぐ
  3. 安全性の向上: バグを減らす
  4. 内部実装の隠蔽: 使う側は内部構造を知らなくていい

カプセル化なしの問題点

まず、これまで作成してきたPlayerクラスの問題点を見てみましょう。

問題のあるコード

/**
 * カプセル化されていないPlayerクラス(危険!)
 */
public class Player {
    
    // すべてのフィールドがpublic(誰でもアクセス可能)
    public String name;
    public int hp;
    public int maxHp;
    public int mp;
    public int maxMp;
    public int attack;

    // コンストラクタ
    public Player(String name, int hp, int mp, int attack) {
        this.name = name;
        this.hp = hp;
        this.maxHp = hp;
        this.mp = mp;
        this.maxMp = mp;
        this.attack = attack;
    }
}

何が起きるか?

public class GameMain {
    public static void main(String[] args) {
        
        Player hero = new Player("勇者", 100, 50, 15);
        
        // ========================================
        // 問題1:マイナスの値を設定できてしまう
        // ========================================
        hero.hp = -100;
        System.out.println("HP: " + hero.hp);  // HP: -100(おかしい!)
        
        // ========================================
        // 問題2:最大値を超える値を設定できてしまう
        // ========================================
        hero.hp = 99999;
        System.out.println("HP: " + hero.hp + " / " + hero.maxHp);  
        // HP: 99999 / 100(最大HPを超えている!)
        
        // ========================================
        // 問題3:nullを設定できてしまう
        // ========================================
        hero.name = null;
        System.out.println("名前: " + hero.name);  // 名前: null(バグの原因!)
        
        // ========================================
        // 問題4:型が合っていれば何でも設定できる
        // ========================================
        hero.attack = -50;  // マイナスの攻撃力?
        hero.maxMp = 0;     // 最大MPが0?
        
        // プログラムは動くが、データがめちゃくちゃ...
    }
}

問題点のまとめ:

  • データの整合性が保証されない
  • バグの温床になる
  • デバッグが困難になる
  • チーム開発で他の人が誤った使い方をする可能性

解決策:カプセル化!


アクセス修飾子

access_modifiers_v2.png

カプセル化を実現するために、まず「アクセス修飾子」を理解する必要があります。

アクセス修飾子とは?

アクセス修飾子(Access Modifier) は、フィールドやメソッドへのアクセス範囲を制限するキーワードです。

Javaには4種類のアクセス修飾子があります:

修飾子 同じクラス 同じパッケージ 子クラス 他のパッケージ 用途
public どこからでもアクセス可能。公開メソッド、定数など
protected × 継承用。子クラスからアクセス可能
(default) × × パッケージ内でのみアクセス可能
private × × × 同じクラス内でのみアクセス可能。推奨

privateの使い方

基本ルール:フィールドはprivateにする

public class Player {
    
    // ❌ 悪い例:publicフィールド
    public int hp;
    
    // ✅ 良い例:privateフィールド
    private int hp;
}

privateにすると、外部から直接アクセスできなくなります:

Player hero = new Player();
hero.hp = -100;  // ❌ コンパイルエラー!
                 // エラー: hpはprivateアクセスです

これが第一歩です!


getter/setterメソッド

getter_setter_v2.png

フィールドをprivateにすると、外部からアクセスできなくなりました。
でも、それではプログラムが書けません。

解決策:専用のメソッドを用意する

それが「getter」と「setter」です。

getterメソッド(取得用)

getterは、フィールドの値を取得するためのメソッドです。

命名規則:

  • メソッド名:get + フィールド名(先頭大文字)
  • 戻り値:フィールドと同じ型
  • 引数:なし
/**
 * HPを取得するgetter
 * 
 * @return 現在のHP
 */
public int getHp() {
    return this.hp;
}

使い方:

Player hero = new Player("勇者", 100, 50, 15);
int currentHp = hero.getHp();  // HPを取得
System.out.println("HP: " + currentHp);

setterメソッド(設定用)

setterは、フィールドに値を設定するためのメソッドです。

命名規則:

  • メソッド名:set + フィールド名(先頭大文字)
  • 戻り値:void
  • 引数:設定する値
/**
 * HPを設定するsetter
 * 
 * @param hp 設定するHP値
 */
public void setHp(int hp) {
    this.hp = hp;
}

使い方:

Player hero = new Player("勇者", 100, 50, 15);
hero.setHp(80);  // HPを80に設定
System.out.println("HP: " + hero.getHp());  // HP: 80

なぜわざわざメソッドを経由するのか?

理由1:値の検証(バリデーション)ができる

public void setHp(int hp) {
    // 不正な値をチェック
    if (hp < 0) {
        System.out.println("❌ HPはマイナスにできません");
        return;  // 設定しない
    }
    if (hp > this.maxHp) {
        System.out.println("⚠️ HPが最大値を超えています。最大値に設定します");
        this.hp = this.maxHp;
        return;
    }
    
    // 正常な値のみ設定
    this.hp = hp;
}

理由2:内部処理を追加できる

public void setHp(int hp) {
    this.hp = hp;
    
    // HPが0になったら戦闘不能処理
    if (this.hp == 0) {
        System.out.println(this.name + "は倒れた...");
        this.isAlive = false;
    }
}

理由3:後から変更しやすい

将来、HPの計算方法が変わっても、getterだけ修正すればOK。


バリデーション(値のチェック)

validation_example_v2.png

setterメソッドの最も重要な役割は、不正な値を防ぐことです。

バリデーションの種類

1. 範囲チェック

/**
 * HPを設定(範囲チェック付き)
 * 
 * @param hp 設定するHP(0〜maxHpの範囲)
 */
public void setHp(int hp) {
    // 下限チェック
    if (hp < 0) {
        System.out.println("❌ HPは0以上である必要があります");
        System.out.println("   設定しようとした値: " + hp);
        return;  // 設定せずに終了
    }
    
    // 上限チェック
    if (hp > this.maxHp) {
        System.out.println("⚠️ HPが最大値(" + this.maxHp + ")を超えています");
        System.out.println("   最大値に調整します");
        this.hp = this.maxHp;  // 最大値で制限
        return;
    }
    
    // 正常な範囲内の値
    this.hp = hp;
    System.out.println("✅ HPを " + this.hp + " に設定しました");
}

テスト:

Player hero = new Player("勇者", 100, 50, 15);

hero.setHp(80);    // ✅ HPを 80 に設定しました
hero.setHp(-50);   // ❌ HPは0以上である必要があります
hero.setHp(150);   // ⚠️ HPが最大値(100)を超えています。最大値に調整します

2. nullチェック

/**
 * 名前を設定(nullチェック付き)
 * 
 * @param name プレイヤーの名前(nullや空文字は不可)
 */
public void setName(String name) {
    // nullチェック
    if (name == null) {
        System.out.println("❌ 名前がnullです。設定できません");
        return;
    }
    
    // 空文字チェック
    if (name.isEmpty()) {
        System.out.println("❌ 名前が空です。設定できません");
        return;
    }
    
    // 正常な名前
    this.name = name;
    System.out.println("✅ 名前を「" + this.name + "」に設定しました");
}

3. 論理チェック

/**
 * 現在のMPを設定(論理チェック付き)
 * 現在のMPは最大MPを超えることはできない
 * 
 * @param mp 設定するMP
 */
public void setMp(int mp) {
    if (mp < 0) {
        System.out.println("❌ MPは0以上である必要があります");
        return;
    }
    
    // 論理チェック:現在MPは最大MPを超えられない
    if (mp > this.maxMp) {
        System.out.println("⚠️ MPが最大MP(" + this.maxMp + ")を超えています");
        this.mp = this.maxMp;
        return;
    }
    
    this.mp = mp;
}

4. 相互依存のチェック

/**
 * 最大HPを設定(相互依存チェック付き)
 * 最大HPを変更した場合、現在のHPも調整する必要がある
 * 
 * @param maxHp 新しい最大HP
 */
public void setMaxHp(int maxHp) {
    if (maxHp <= 0) {
        System.out.println("❌ 最大HPは1以上である必要があります");
        return;
    }
    
    this.maxHp = maxHp;
    
    // 現在のHPが新しい最大HPを超えている場合、調整する
    if (this.hp > this.maxHp) {
        System.out.println("⚠️ 現在のHPが最大HPを超えているため、調整します");
        this.hp = this.maxHp;
    }
    
    System.out.println("✅ 最大HPを " + this.maxHp + " に設定しました");
}

カプセル化されたPlayerクラスの完全実装

それでは、これまで学んだことを全て組み込んだPlayerクラスを実装してみましょう。

/**
 * プレイヤークラス(カプセル化完全版)
 * 
 * すべてのフィールドをprivateで保護し、
 * getter/setterメソッドでバリデーション付きアクセスを提供します。
 */
public class Player {
    
    // ========================================
    // フィールド(すべてprivate)
    // ========================================
    
    /** プレイヤーの名前(nullや空文字は不可) */
    private String name;
    
    /** 職業 */
    private String job;
    
    /** 現在のHP(0〜maxHpの範囲) */
    private int hp;
    
    /** 最大HP(1以上) */
    private int maxHp;
    
    /** 現在のMP(0〜maxMpの範囲) */
    private int mp;
    
    /** 最大MP(0以上) */
    private int maxMp;
    
    /** 攻撃力(0以上) */
    private int attack;
    
    /** 防御力(0以上) */
    private int defense;
    
    /** 生存フラグ */
    private boolean alive;
    
    // ========================================
    // コンストラクタ
    // ========================================
    
    /**
     * プレイヤーを作成します
     * 
     * @param name 名前(nullや空文字は不可)
     * @param job 職業
     * @param maxHp 最大HP(1以上)
     * @param maxMp 最大MP(0以上)
     * @param attack 攻撃力(0以上)
     * @param defense 防御力(0以上)
     */
    public Player(String name, String job, int maxHp, int maxMp, int attack, int defense) {
        // コンストラクタでもsetterを使って検証
        setName(name);
        setJob(job);
        setMaxHp(maxHp);
        setMaxMp(maxMp);
        setAttack(attack);
        setDefense(defense);
        
        // 現在値を最大値に設定
        this.hp = this.maxHp;
        this.mp = this.maxMp;
        this.alive = true;
        
        System.out.println("✅ " + this.name + "(" + this.job + ")が冒険者として登録されました");
    }
    
    // ========================================
    // getter メソッド群
    // ========================================
    
    /**
     * プレイヤーの名前を取得します
     * 
     * @return 名前
     */
    public String getName() {
        return this.name;
    }
    
    /**
     * 職業を取得します
     * 
     * @return 職業
     */
    public String getJob() {
        return this.job;
    }
    
    /**
     * 現在のHPを取得します
     * 
     * @return 現在のHP
     */
    public int getHp() {
        return this.hp;
    }
    
    /**
     * 最大HPを取得します
     * 
     * @return 最大HP
     */
    public int getMaxHp() {
        return this.maxHp;
    }
    
    /**
     * 現在のMPを取得します
     * 
     * @return 現在のMP
     */
    public int getMp() {
        return this.mp;
    }
    
    /**
     * 最大MPを取得します
     * 
     * @return 最大MP
     */
    public int getMaxMp() {
        return this.maxMp;
    }
    
    /**
     * 攻撃力を取得します
     * 
     * @return 攻撃力
     */
    public int getAttack() {
        return this.attack;
    }
    
    /**
     * 防御力を取得します
     * 
     * @return 防御力
     */
    public int getDefense() {
        return this.defense;
    }
    
    /**
     * 生存状態を取得します
     * 
     * @return 生存している場合true、倒れている場合false
     */
    public boolean isAlive() {
        return this.alive;
    }
    
    // ========================================
    // setter メソッド群(バリデーション付き)
    // ========================================
    
    /**
     * プレイヤーの名前を設定します
     * 
     * @param name 名前(nullや空文字は不可)
     */
    public void setName(String name) {
        if (name == null || name.isEmpty()) {
            System.out.println("❌ 名前が不正です。設定できません");
            this.name = "名無し";  // デフォルト値
            return;
        }
        this.name = name;
    }
    
    /**
     * 職業を設定します
     * 
     * @param job 職業(nullや空文字は不可)
     */
    public void setJob(String job) {
        if (job == null || job.isEmpty()) {
            System.out.println("❌ 職業が不正です。デフォルト値を設定します");
            this.job = "Beginner";
            return;
        }
        this.job = job;
    }
    
    /**
     * 現在のHPを設定します
     * 
     * @param hp 設定するHP(0〜maxHpの範囲)
     */
    public void setHp(int hp) {
        if (hp < 0) {
            System.out.println("❌ HPは0以上である必要があります(入力値: " + hp + ")");
            return;
        }
        
        if (hp > this.maxHp) {
            System.out.println("⚠️ HPが最大値を超えています。最大値(" + this.maxHp + ")に調整します");
            this.hp = this.maxHp;
            return;
        }
        
        this.hp = hp;
        
        // HPが0になったら倒れる
        if (this.hp == 0) {
            this.alive = false;
            System.out.println("💀 " + this.name + "は倒れた...");
        }
    }
    
    /**
     * 最大HPを設定します
     * 
     * @param maxHp 最大HP(1以上)
     */
    public void setMaxHp(int maxHp) {
        if (maxHp <= 0) {
            System.out.println("❌ 最大HPは1以上である必要があります(入力値: " + maxHp + ")");
            this.maxHp = 1;  // 最小値
            return;
        }
        
        this.maxHp = maxHp;
        
        // 現在のHPが新しい最大HPを超えている場合、調整
        if (this.hp > this.maxHp) {
            this.hp = this.maxHp;
        }
    }
    
    /**
     * 現在のMPを設定します
     * 
     * @param mp 設定するMP(0〜maxMpの範囲)
     */
    public void setMp(int mp) {
    
        // 引数の mp が0未満の場合、調整
        if (mp < 0) {
            System.out.println("❌ MPは0以上である必要があります(入力値: " + mp + ")");
            return;
        }

        // 引数の mp が this.maxMp を超える場合、調整
        if (mp > this.maxMp) {
            System.out.println("⚠️ MPが最大値を超えています。最大値(" + this.maxMp + ")に調整します");
            this.mp = this.maxMp;
            return;
        }
        
        this.mp = mp;
    }
    
    /**
     * 最大MPを設定します
     * 
     * @param maxMp 最大MP(0以上)
     */
    public void setMaxMp(int maxMp) {
    
        // 引数の maxMp が0未満の場合、調整
        if (maxMp < 0) {
            System.out.println("❌ 最大MPは0以上である必要があります(入力値: " + maxMp + ")");
            this.maxMp = 0;
            return;
        }
        
        this.maxMp = maxMp;
        
        // 現在のMPが新しい最大MPを超えている場合、調整
        if (this.mp > this.maxMp) {
            this.mp = this.maxMp;
        }
    }
    
    /**
     * 攻撃力を設定します
     * 
     * @param attack 攻撃力(0以上)
     */
    public void setAttack(int attack) {
        if (attack < 0) {
            System.out.println("❌ 攻撃力は0以上である必要があります(入力値: " + attack + ")");
            this.attack = 0;
            return;
        }
        this.attack = attack;
    }
    
    /**
     * 防御力を設定します
     * 
     * @param defense 防御力(0以上)
     */
    public void setDefense(int defense) {
        if (defense < 0) {
            System.out.println("❌ 防御力は0以上である必要があります(入力値: " + defense + ")");
            this.defense = 0;
            return;
        }
        this.defense = defense;
    }
    
    // ========================================
    // ビジネスロジックメソッド
    // ========================================
    
    /**
     * ダメージを受けます
     * 
     * @param damage ダメージ量(0以上)
     */
    public void takeDamage(int damage) {
        if (damage < 0) {
            System.out.println("❌ ダメージは0以上である必要があります");
            return;
        }
        
        // 防御力を考慮したダメージ計算
        int actualDamage = Math.max(damage - this.defense, 0);
        
        System.out.println(this.name + "は " + actualDamage + " のダメージを受けた!");
        
        // setHp()を使うことでバリデーションが自動適用される
        setHp(this.hp - actualDamage);
        
        System.out.println("残りHP: " + this.hp + " / " + this.maxHp);
    }
    
    /**
     * HPを回復します
     * 
     * @param amount 回復量(0以上)
     */
    public void heal(int amount) {
        if (amount < 0) {
            System.out.println("❌ 回復量は0以上である必要があります");
            return;
        }
        
        if (!this.alive) {
            System.out.println("💀 " + this.name + "は倒れているため回復できません");
            return;
        }
        
        int beforeHp = this.hp;
        
        // setHp()を使うことで最大HP超過が自動防止される
        setHp(this.hp + amount);
        
        int actualHealed = this.hp - beforeHp;
        System.out.println(this.name + "のHPが " + actualHealed + " 回復した!");
        System.out.println("HP: " + this.hp + " / " + this.maxHp);
    }
    
    /**
     * MPを消費します
     * 
     * @param cost 消費MP(0以上)
     * @return MP消費に成功した場合true、失敗した場合false
     */
    public boolean consumeMp(int cost) {
        if (cost < 0) {
            System.out.println("❌ 消費MPは0以上である必要があります");
            return false;
        }
        
        if (this.mp < cost) {
            System.out.println("❌ MPが足りません(必要: " + cost + " / 現在: " + this.mp + ")");
            return false;
        }
        
        setMp(this.mp - cost);
        System.out.println("MPを " + cost + " 消費しました(残り: " + this.mp + ")");
        return true;
    }
    
    /**
     * プレイヤーの詳細情報を表示します
     */
    public void showStatus() {
        System.out.println("========================================");
        System.out.println("名前: " + this.name + " (" + this.job + ")");
        System.out.println("状態: " + (this.alive ? "生存" : "戦闘不能"));
        System.out.println("----------------------------------------");
        System.out.println("HP: " + this.hp + " / " + this.maxHp);
        System.out.println("MP: " + this.mp + " / " + this.maxMp);
        System.out.println("----------------------------------------");
        System.out.println("攻撃力: " + this.attack);
        System.out.println("防御力: " + this.defense);
        System.out.println("========================================");
    }
}

カプセル化の効果を確認

それでは、カプセル化されたPlayerクラスを実際に使ってみましょう。

/**
 * カプセル化のテストクラス
 */
public class EncapsulationTest {
    
    public static void main(String[] args) {
        
        System.out.println("=== カプセル化のテスト ===\n");
        
        // プレイヤー作成
        Player hero = new Player("勇者アレン", "Warrior", 100, 50, 20, 10);
        hero.showStatus();
        
        System.out.println("\n--- 不正な値の設定テスト ---\n");
        
        // ========================================
        // テスト1:マイナスのHPを設定しようとする
        // ========================================
        System.out.println("【テスト1】マイナスのHP設定");
        hero.setHp(-50);  // ❌ 拒否される
        System.out.println("実際のHP: " + hero.getHp());  // 変更されていない
        
        System.out.println();
        
        // ========================================
        // テスト2:最大値を超えるHPを設定しようとする
        // ========================================
        System.out.println("【テスト2】最大値超過HP設定");
        hero.setHp(150);  // ⚠️ 最大値に調整される
        System.out.println("実際のHP: " + hero.getHp());  // 100(最大値)
        
        System.out.println();
        
        // ========================================
        // テスト3:正常なダメージ処理
        // ========================================
        System.out.println("【テスト3】ダメージ処理");
        hero.takeDamage(30);  // 防御力10を考慮して20ダメージ
        
        System.out.println();
        
        // ========================================
        // テスト4:回復処理(最大値超過の自動調整)
        // ========================================
        System.out.println("【テスト4】回復処理");
        hero.heal(200);  // 大量回復を試みる → 最大値で制限される
        
        System.out.println();
        
        // ========================================
        // テスト5:MP消費
        // ========================================
        System.out.println("【テスト5】MP消費");
        boolean success1 = hero.consumeMp(20);  // ✅ 成功
        System.out.println("結果: " + (success1 ? "成功" : "失敗"));
        
        System.out.println();
        
        boolean success2 = hero.consumeMp(100);  // ❌ MP不足で失敗
        System.out.println("結果: " + (success2 ? "成功" : "失敗"));
        
        System.out.println();
        
        // ========================================
        // テスト6:致命的なダメージ
        // ========================================
        System.out.println("【テスト6】致命的ダメージ");
        hero.takeDamage(200);  // HPが0になる
        System.out.println("生存状態: " + hero.isAlive());
        
        System.out.println();
        
        // ========================================
        // 最終状態
        // ========================================
        System.out.println("=== 最終状態 ===");
        hero.showStatus();
    }
}

実行結果

=== カプセル化のテスト ===

✅ 勇者アレン(Warrior)が冒険者として登録されました
========================================
名前: 勇者アレン (Warrior)
状態: 生存
----------------------------------------
HP: 100 / 100
MP: 50 / 50
----------------------------------------
攻撃力: 20
防御力: 10
========================================

--- 不正な値の設定テスト ---

【テスト1】マイナスのHP設定
❌ HPは0以上である必要があります(入力値: -50)
実際のHP: 100

【テスト2】最大値超過HP設定
⚠️ HPが最大値を超えています。最大値(100)に調整します
実際のHP: 100

【テスト3】ダメージ処理
勇者アレンは 20 のダメージを受けた!
残りHP: 80 / 100

【テスト4】回復処理
勇者アレンのHPが 20 回復した!
HP: 100 / 100

【テスト5】MP消費
MPを 20 消費しました(残り: 30)
結果: 成功

❌ MPが足りません(必要: 100 / 現在: 30)
結果: 失敗

【テスト6】致命的ダメージ
勇者アレンは 190 のダメージを受けた!
💀 勇者アレンは倒れた...
残りHP: 0 / 100
生存状態: false

=== 最終状態 ===
========================================
名前: 勇者アレン (Warrior)
状態: 戦闘不能
----------------------------------------
HP: 0 / 100
MP: 30 / 50
----------------------------------------
攻撃力: 20
防御力: 10
========================================

カプセル化の効果が明確に分かりますね!


初学者がよくハマるポイント

❌ 間違い1:privateなのにpublicメソッドがない

public class Player {
    private int hp;  // private
    
    // getter/setterがない!
}

問題:
外部から一切アクセスできない。

解決:
getter/setterメソッドを追加する。


❌ 間違い2:setterでthisを付け忘れる

public void setHp(int hp) {
    hp = hp;  // ❌ 引数に引数を代入している
}

正しい書き方:

public void setHp(int hp) {
    this.hp = hp;  // ✅ フィールドに代入
}

❌ 間違い3:バリデーションがない

public void setHp(int hp) {
    this.hp = hp;  // バリデーションなし
}

問題:
これでは直接アクセスと変わらない。

正しい書き方:

public void setHp(int hp) {
    if (hp < 0 || hp > this.maxHp) {
        // エラー処理
        return;
    }
    this.hp = hp;
}

❌ 間違い4:getter名の間違い

// ❌ 間違い
public int hp() {  // getが付いていない
    return hp;
}

// ❌ 間違い
public int gethp() {  // 小文字のまま
    return hp;
}

// ✅ 正しい
public int getHp() {  // get + Hp(先頭大文字)
    return hp;
}

❌ 間違い5:booleanのgetterの命名

boolean型のgetterはgetではなくisを使います:

private boolean alive;

// ❌ 間違い
public boolean getAlive() {
    return alive;
}

// ✅ 正しい
public boolean isAlive() {
    return this.alive;
}

practice_problems_banner.png

実際に手を動かして、理解を深めましょう!

基本問題

問題1:基本的なカプセル化

以下の要件を満たすItemクラスを作成してください。

要件:

  • privateフィールド:String name, int price
  • コンストラクタ:名前と価格を受け取る
  • getter:getName(), getPrice()
  • setter:setName(), setPrice()(priceは0以上のみ設定可能)

問題2:バリデーション付きsetter

問題1のItemクラスのsetPrice()メソッドに、以下のバリデーションを追加してください。

バリデーション:

  • 価格が0未満の場合、エラーメッセージを表示して0を設定
  • 価格が100万を超える場合、警告メッセージを表示して100万に制限

応用問題

問題3:装備クラスの作成

以下の要件を満たすWeapon(武器)クラスを作成してください。

要件:

  • privateフィールド:
    • String name(武器名)
    • int attack(攻撃力:0〜100の範囲)
    • int durability(耐久度:0〜100の範囲)
    • boolean broken(破損フラグ)
  • コンストラクタ:名前と攻撃力を受け取る(耐久度は100で初期化)
  • getter/setter(すべてバリデーション付き)
  • メソッド:
    • use():使用時に耐久度を1減らす。0になったら破損フラグをtrue
    • repair(int amount):耐久度を回復する(最大100)
    • showStatus():武器の状態を表示

使用例:

Weapon sword = new Weapon("鉄の剣", 30);
sword.use();  // 耐久度99
sword.use();  // 耐久度98
sword.repair(50);  // 耐久度100(最大値)

問題4:銀行口座クラスの作成

カプセル化を使ってBankAccount(銀行口座)クラスを作成してください。

要件:

  • privateフィールド:
    • String accountNumber(口座番号)
    • String ownerName(口座名義)
    • int balance(残高)
  • コンストラクタ:口座番号と名義を受け取る(残高は0で初期化)
  • getter:すべてのフィールド
  • setter:なし(残高は直接変更できないようにする)
  • メソッド:
    • deposit(int amount):入金(0より大きい金額のみ)
    • withdraw(int amount):出金(残高不足の場合は失敗)
    • showAccountInfo():口座情報を表示

ポイント:
残高はsetBalance()メソッドを作らず、deposit()withdraw()でのみ変更できるようにする。


問題5:Playerクラスの拡張

本文のPlayerクラスに、以下の機能を追加してください。

追加機能:

  1. levelフィールド(レベル:1〜100)
  2. expフィールド(経験値:0以上)
  3. addExp(int amount)メソッド:経験値を追加し、一定量で自動レベルアップ
  4. レベルアップ時にHP、MPの最大値を自動増加

仕様:

  • 経験値100でレベルアップ
  • レベルアップごとに最大HPが10、最大MPが5増加
  • レベル100が上限

summary_banner.png

お疲れ様でした!第5回では、カプセル化について詳しく学びました。

重要ポイント:

  1. カプセル化とは

    • データを外部から保護する技術
    • 不正な値を防ぎ、整合性を保証する
  2. フィールドはprivateにする

    • 外部から直接アクセスできないようにする
    • これがカプセル化の第一歩
  3. getter/setterで安全にアクセス

    • getter:値を取得する(getXxx()
    • setter:値を設定する(setXxx()
    • boolean型はisXxx()
  4. setterでバリデーション

    • 範囲チェック、nullチェック、論理チェック
    • 不正な値を設定前にブロック
  5. カプセル化のメリット

    • データの保護
    • バグの防止
    • 保守性の向上
    • 使いやすいAPI

カプセル化は、安全なプログラムを作るための基本中の基本です。
必ずマスターしましょう!


next_episode_preview_banner-1.png

第6回:参照型を深く理解する

次回は、第2回で学んだ「参照型」をさらに深掘りします。

  • 参照とは何か(より詳細に)
  • シャローコピーとディープコピー
  • nullの扱い方
  • 不変オブジェクト(Immutable)

参照型の理解は、オブジェクト指向を使いこなす上で非常に重要です。

次回もお楽しみに!


reference_materials_banner-1.png


シリーズ一覧:


© 2025 Java Quest - オブジェクト指向って何?RPGで理解する超入門
All Rights Reserved.

本記事は教育目的で作成されています。RPGの例を通じてプログラミング概念を楽しく学習できることを目指しています。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?