Java: カプセル化

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

1. カプセル化とは

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

(1) カプセル化の利点

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

2. アクセス修飾子

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

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

(1) サンプル:アクセス修飾子

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


3. getterとsetter

(1) 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("無効な年齢");
        }
    }
}

(2) サンプル:getter/setterの使用

TEXT 📖 参照専用
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);  // 出力: 無効な年齢
    }
}

4. JavaBean規約

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

(1) JavaBeanルール

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

(2) サンプル:標準的な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 + "}";
    }
}
論理コード 42 行(40 行制限超過、参照専用)

5. カプセル化の実践

(1) サンプル:銀行口座

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); // 残高不足
    }
}
論理コード 42 行(40 行制限超過、参照専用)

(2) サンプル:商品価格のバリデーション

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 + ")";
    }
}
論理コード 45 行(40 行制限超過、参照専用)

6. getter/setterの命名規則

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

▶ サンプル

JAVA
class BankAccount {
    private double balance;
    public void deposit(double amount) { balance += amount; }
    public double getBalance() { return balance; }
}
▶ 試してみよう

❓ よくある質問

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

📖 まとめ

📝 練習問題

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

7. 次のレッスン

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

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%