カプセル化

カプセル化はOOPのコア機能の一つです。このレッスンでは、オブジェクトデータを保護する方法を学びます。

カプセル化とは

カプセル化はデータ(属性)とそのデータを操作するメソッドを一緒にバンドルし、内部実装の詳細を隠します。

カプセル化の利点

利点 説明
データ保護 外部からの直接アクセスと変更を防止
柔軟性 setterにバリデーションロジックを追加可能
保守性 内部の変更が外部の呼び出しに影響しない

アクセス修飾子

Javaには4つのアクセス修飾子があり、クラス、属性、メソッドの可視性を制御します。

修飾子 同じクラス 同じパッケージ サブクラス 異なるパッケージ
public
protected
デフォルト(修飾子なし)
private

例:アクセス修飾子

JAVA
public class Person {
    public String name;      // パブリック、どこからでもアクセス可能
    protected int age;       // 保護付き、同じパッケージとサブクラスからアクセス可能
    String address;          // デフォルト、同じパッケージからアクセス可能
    private double salary;   // プライベート、このクラスからだけアクセス可能
}
▶ 試してみよう
💡 推奨: 属性にはprivateを使用し、getter/setterメソッドでアクセスしてください。

getterとsetter

getter/setterが必要な理由

JAVA
public class Person {
    private String name;
    private int age;
    
    // getter
    public String getName() {
        return name;
    }
    
    // setter
    public void setName(String name) {
        this.name = name;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        // setterにバリデーションロジックを追加
        if (age >= 0 && age <= 150) {
            this.age = age;
        } else {
            System.out.println("無効な年齢");
        }
    }
}

例:getter/setterの使用

JAVA
public class PersonTest {
    public static void main(String[] args) {
        Person p = new Person();
        
        // setterで値を設定
        p.setName("Alice");
        p.setAge(25);
        
        // getterで値を取得
        System.out.println("名前: " + p.getName());
        System.out.println("年齢: " + p.getAge());
        
        // setterのバリデーション
        p.setAge(-5);  // 出力: 無効な年齢
    }
}
▶ 試してみよう

JavaBean規約

JavaBeanは特定の規約に従う特別なタイプのJavaクラスです。

JavaBeanルール

ルール 説明
プライベート属性 privateで修飾
getter/setterを提供 getXxx()setXxx()
引数なしコンストラクタ 必須
Serializableを実装 オプション、シリアライズ用

例:標準的なJavaBean

JAVA
import java.io.Serializable;

public class User implements Serializable {
    // プライベート属性
    private String username;
    private String password;
    private int age;
    
    // 引数なしコンストラクタ
    public User() {}
    
    // 引数付きコンストラクタ
    public User(String username, String password, int age) {
        this.username = username;
        this.password = password;
        this.age = age;
    }
    
    // getter/setter
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username = username;
    }
    
    public String getPassword() {
        return password;
    }
    
    public void setPassword(String password) {
        if (password.length() >= 6) {
            this.password = password;
        } else {
            System.out.println("パスワードは6文字以上でなければなりません");
        }
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        if (age >= 0 && age <= 150) {
            this.age = age;
        } else {
            System.out.println("無効な年齢");
        }
    }
    
    @Override
    public String toString() {
        return "User{username='" + username + "', age=" + age + "}";
    }
}
▶ 試してみよう

カプセル化の実践

例:銀行口座

JAVA
public class BankAccount {
    private String accountId;
    private double balance;
    private String owner;
    
    public BankAccount(String accountId, String owner) {
        this.accountId = accountId;
        this.owner = owner;
        this.balance = 0;
    }
    
    // 入金
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println("入金: " + amount + ", 残高: " + balance);
        } else {
            System.out.println("入金額は0より大きくなければなりません");
        }
    }
    
    // 出金
    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            System.out.println("出金: " + amount + ", 残高: " + balance);
        } else if (amount > balance) {
            System.out.println("残高不足");
        } else {
            System.out.println("出金額は0より大きくなければなりません");
        }
    }
    
    // 残高確認
    public double getBalance() {
        return balance;
    }
    
    public void showInfo() {
        System.out.println("口座: " + accountId + ", 所有者: " + owner + ", 残高: " + balance);
    }
    
    public static void main(String[] args) {
        BankAccount account = new BankAccount("10001", "Alice");
        account.showInfo();  // 口座: 10001, 所有者: Alice, 残高: 0.0
        
        account.deposit(1000);  // 入金: 1000.0, 残高: 1000.0
        account.deposit(500);   // 入金: 500.0, 残高: 1500.0
        account.withdraw(200);  // 出金: 200.0, 残高: 1300.0
        account.withdraw(2000); // 残高不足
    }
}
▶ 試してみよう

例:商品価格のバリデーション

JAVA
public class Product {
    private String name;
    private double price;
    private int stock;
    
    public Product(String name, double price, int stock) {
        this.name = name;
        setPrice(price);
        setStock(stock);
    }
    
    public String getName() {
        return name;
    }
    
    public double getPrice() {
        return price;
    }
    
    public void setPrice(double price) {
        if (price >= 0) {
            this.price = price;
        } else {
            System.out.println("価格はマイナスにできません");
        }
    }
    
    public int getStock() {
        return stock;
    }
    
    public void setStock(int stock) {
        if (stock >= 0) {
            this.stock = stock;
        } else {
            System.out.println("在庫はマイナスにできません");
        }
    }
    
    // 合計価格を計算
    public double getTotalPrice(int quantity) {
        if (quantity > 0 && quantity <= stock) {
            return price * quantity;
        } else {
            System.out.println("無効な数量");
            return 0;
        }
    }
    
    @Override
    public String toString() {
        return name + " - $" + price + " (在庫: " + stock + ")";
    }
}
▶ 試してみよう

getter/setterの命名規則

属性型 getter setter
boolean married isMarried() setMarried()
String name getName() setName()
int age getAge() setAge()
⚠️ 特殊ケース: boolean型の場合、getterはisプレフィックスを使用し、getではありません。

❓ よくある質問

Q なぜpublic属性を使わないのですか?
A public属性はアクセス制御を提供しません。setterはデータの整合性を保護するためのバリデーションロジックを追加できます。
Q すべての属性にgetter/setterが必要ですか?
A いいえ。読み取り専用属性にはgetterだけが必要です。書き込み専用属性にはsetterだけが必要です。
Q コンストラクタとsetterの違いは何ですか?
A コンストラクタはオブジェクト作成時に呼び出され、setterはオブジェクト作成後に属性を変更します。

📖 まとめ

📝 演習

  1. Studentクラス: カプセル化された属性を持つStudentクラスを定義し、スコアを0-100の間でバリデーション
  2. Dateクラス: カプセル化された年/月/日を持つ日付クラスを定義し、日付のバリデーションを提供
  3. BankAccountの改良: パスワードバリデーションを追加し、出金時にパスワードを要求

次のレッスン

次のレッスンでは、継承を学びます — コード再利用のための重要なメカニズム。

100%