読み込み中...

プログラミング初心者でも簡単!Javaカプセル化の23ステップ完全ガイド

初心者がJavaのカプセル化を学ぶステップバイステップガイドのカバーイメージ Java
この記事は約24分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Javaのカプセル化は、クラスの状態を外部から直接触らせず、決められたメソッドを通じて安全に扱う設計です。プログラミング初心者が最初につまずきやすいのは、変数を隠すだけで十分だと考えてしまう点で、その変数にどんな値を許すかまで含めて考えると理解しやすくなります。

そのため、Javaを学ぶときはprivateで隠す、publicな操作口を置く、ifで値を検査する、という流れをひとまとまりで押さえるのが現実的です。こうした基礎はオブジェクト指向の考え方に直結し、プログラミング学習で扱うクラス設計の土台にもなります。

動作確認環境
  • Java SE 21
  • OpenJDK 21.0.2
  • JShell 21相当の構文で読める標準Javaコード
📖 この記事で学べること
  • Javaのクラス、フィールド、メソッドの関係
  • カプセル化で状態を守る基本設計
  • ゲッター、セッター、コンストラクタの使い分け
  • バリデーションや例外処理を組み込む考え方
  • 顧客管理、在庫管理、認証処理への応用

Javaとは

Javaは、型を持つクラスベースのプログラミング言語で、アプリケーションを.javaファイルに書き、javac.classへ変換してからjavaコマンドで動かす。公式のJava Language Specificationでは、クラス、インターフェース、型、式、例外などの言語仕様が定義されています。

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, Java");
    }
}

結果: 期待される出力は「Hello, Java」です。

この最小例では、classが型のまとまりを作り、mainが開始位置になり、System.out.printlnが文字列を標準出力へ渡す。コーディング技術は、この小さな構造を大きなクラス群へ広げながら、責任範囲を崩さない書き方として身につきます。

一般に、intdoublebooleanStringなどの型を明示して扱います。その型情報によって、プログラミング初心者でも代入ミスや戻り値の違いを早い段階で見つけやすくなるでしょう。

一方、オブジェクト指向は、すべてを無理にクラスへ詰め込む考え方ではありません。状態を持つものをfieldとして表し、その状態を変える処理をmethodに置くことで、読み手が変更点を追いやすい形に整理する技法だと言えますし、ここがポイントです。

分類用語役割カプセル化での見方
基礎.javaソースコードを置くクラス単位で責任を分ける.java
基礎.classコンパイル後の形式JVMが読み込む.class
基礎javac変換する構文や型の誤りを検出するjavac
基礎java起動する公開された入口から動くjava
基礎class型を定義する状態と操作をまとめるclass
基礎interface契約を定義する実装の差を隠せるinterface
基礎private同じクラス内に閉じる直接変更を防ぐprivate
基礎public外部へ公開する操作口を限定するpublic
基礎protected継承先などに開く範囲を広げすぎないprotected
基礎final再代入や継承を抑える変わらない値を示すfinal
基礎int整数を扱う範囲検査と相性がよいint
基礎String文字列を扱う空文字や長さを検査するString
基礎boolean真偽値を扱う状態フラグを明確にするboolean
基礎if条件で分岐する不正値を止めるif
基礎else条件外を扱う失敗時の動きを置くelse
基礎for繰り返す一覧検索に使うfor
基礎throw例外を投げる不正な更新を止めるthrow
基礎IllegalArgumentException引数不正を表すセッターの失敗に合うIllegalArgumentException
基礎getName値を読む読み取り口を作るgetName
基礎setName値を変える検査を挟めるsetName
基礎this自分自身を示すフィールドと引数を区別するthis
基礎extends親クラスを継ぐ公開範囲の設計が要るextends
基礎@Override上書きを示す定義ミスを減らす@Override
基礎List順序付き一覧内部管理を隠すList
基礎ArrayList可変長配列追加処理をメソッド化するArrayList
基礎null参照なしを示す戻り値設計に注意するnull
基礎equals文字列などを比較する検索条件に使うequals
基礎Objectsnull安全な処理を助ける検査を読みやすくするObjects
基礎java.util標準ユーティリティ一覧管理で使うjava.util
基礎main開始メソッドクラス利用例を置くmain

具体的には、一覧を扱うならJava List型完全ガイド、注釈で意図を補うならJavaアノテーションの12選も合わせて読むと、標準的な書き方を広く整理できます。これらの理解はコーディング技術の幅を広げ、カプセル化したクラスを周辺機能とつなぐ判断にも役立つでしょう。

Javaカプセル化の基本

Javaカプセル化の中心は、データをprivateフィールドとして閉じ、外部には意味のあるpublicメソッドだけを見せることです。その形にすると、クラスの利用者は内部構造を知らなくても安全に値を読み書きでき、作る側は条件チェックを一箇所へ集められます。

その考え方は、オブジェクト指向の「責任を持つ単位を分ける」という発想と重なる。たとえば年齢を扱うクラスなら、負の値を入れない責任は呼び出し側ではなく、年齢を管理するクラスが引き受けるほうが自然です。

public class Person {
    private String name;
    private int age;
}

結果: 期待される状態は、nameageへクラス外から直接アクセスできない形です。

この短い例だけでも、カプセル化は「値を隠す」処理として見えます。ただし、隠した値を使う道を完全に閉じるとクラスを利用できないため、読み取りや更新のためのメソッドを必要な分だけ公開します。

public class Person {
    private String name;
    private int age;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

結果: 期待される動きは、getNamesetAgeを経由して値を扱えることです。

一方、すべてのフィールドに機械的なゲッターとセッターを作るだけでは、設計として弱くなる場合があります。プログラミング初心者は「直接触れない」点に目が向きがちですが、コーディング技術として特に押さえたいのは、値の変更ルールをメソッド内部に閉じ込めることだ。

public class Person {
    private int age;

    public int getAge() { return age; }

    public void setAge(int age) {
        if (age < 0) {
            throw new IllegalArgumentException("年齢は0以上にしてください");
        }
        this.age = age;
    }
}

結果: 期待される動きは、setAge(-1)のような不正な更新がIllegalArgumentExceptionで止まることです。

そのため、カプセル化のメリットはアクセス制御だけではありません。値の整合性、クラスの読みやすさ、変更時の影響範囲をまとめて扱える点が、プログラミング学習で繰り返し出てくる理由になります。

💡 Tips: 公式ドキュメントによれば、アクセス制御はクラス、パッケージ、サブクラスからの見え方に関わります。詳しい規則はOracleのAccess Controlで確認できるのが基本です。

ただし、カプセル化を過度に細かくすると、クラスの責任が見えにくくなる場合もあるのが基本です。たとえば一つのクラスへ入力検査、保存、表示、通信を集めた場合、どの変更がどの処理へ影響するか追いにくくなるでしょう。

Javaカプセル化の具体的な作り方

Javaでカプセル化を形にする流れは、フィールドを閉じ、コンストラクタで初期状態を整え、メソッドで変更条件を管理する順序が扱いやすい。プログラミング学習では、最初から大きな設計へ進むより、小さなクラスで値の入口を制御すると理解が進みます。

このとき、thisはフィールドと引数の名前が同じ場合に役立ちますが、これは押さえたい点です。this.nameはオブジェクト自身のnameを指し、単なるnameはメソッド引数を指すため、代入の向きが読みやすくなる。

public class Account {
    private String owner;
    private int balance;

    public Account(String owner, int balance) {
        this.owner = owner;
        this.balance = balance;
    }

    public String getOwner() { return owner; }
    public int getBalance() { return balance; }
}

結果: 期待される状態は、生成時に所有者と残高が入り、外部からは読み取りだけできる形です。

その設計では、残高の値を外部から自由に変えられません。逆に、入金や出金という意味のある操作を用意すると、数値の増減が業務上の言葉と結びつき、オブジェクト指向らしい読み方になります。

public class Account {
    private String owner;
    private int balance;

    public Account(String owner, int balance) {
        if (balance < 0) { throw new IllegalArgumentException("初期残高は0以上にしてください"); }
        this.owner = owner;
        this.balance = balance;
    }

    public void deposit(int amount) {
        if (amount <= 0) { throw new IllegalArgumentException("入金額は正の値にしてください"); }
        balance += amount;
    }

    public boolean withdraw(int amount) {
        if (amount <= 0 || amount > balance) { return false; }
        balance -= amount;
        return true;
    }

    public int getBalance() { return balance; }
}

結果: 期待される動きは、負の入金を拒否し、残高を超える出金ではfalseが返ることです。

基本的に、セッターは値をそのまま入れる操作、業務メソッドは意味を持つ操作として分けると判断しやすいです。たとえばsetBalanceよりdepositwithdrawのほうが、何を許して何を拒むかをコード上で表せます。

public class Main {
    public static void main(String[] args) {
        Account account = new Account("Yamada", 1000);
        account.deposit(500);
        boolean success = account.withdraw(300);
        System.out.println(account.getBalance());
        System.out.println(success);
    }
}

結果: 期待される出力は、残高を表す1200と、出金成功を表すtrueです。

一方、外部へ返す値が参照型の場合は、内部状態が間接的に変えられないか注意が必要になります。Listをそのまま返すと呼び出し側が追加や削除を行えるため、必要に応じてコピーや読み取り専用ビューを返す判断が必要だ。

import java.util.ArrayList;
import java.util.List;

public class Team {
    private final List<String> members = new ArrayList<>();

    public void addMember(String name) {
        if (name == null || name.isBlank()) { throw new IllegalArgumentException("名前を入力してください"); }
        members.add(name);
    }

    public List<String> getMembers() {
        return List.copyOf(members);
    }
}

結果: 期待される動きは、取得した一覧へ外部から追加できず、内部のmembersが守られることです。

こうした防御的な返し方は、標準APIを知るほど自然に使えるようになります。関連する例として、日付判定の条件分岐はJavaでうるう年を判定する解説、文字列の扱いはJavaエスケープ処理の解説でも確認できます。

⚠️ 注意: サンプルで扱うパスワードや認証処理は学習用の単純化したコードです。実サービスではハッシュ化、ソルト、HTTPS、入力制限、監査ログなどを別途設計するのが目安です。

具体的には、例外を投げる処理と真偽値で失敗を返す処理は、呼び出し側の扱いやすさで選びますし、ここがポイントです。不正な引数はIllegalArgumentException、通常起こり得る失敗はfalseや結果オブジェクトで返す、という整理がよく使われます。

Javaカプセル化の応用例とサンプルコード

Javaカプセル化を応用すると、顧客管理、在庫管理、認証のような状態を持つ処理を読みやすく分解できるのがポイントです。どの例でも、外部から直接フィールドを変更させず、登録、検索、更新、認証といった操作名で状態を扱う点が共通します。

そのため、プログラミング初心者が応用例を読むときは、クラス名よりも「どの状態を守り、どのメソッドだけ公開しているか」に注目するとよい。カプセル化は単体の文法ではなく、オブジェクト指向の設計判断として働きますが、これは押さえたい点です。

顧客情報を登録する

顧客情報では、名前、住所、電話番号のような値を一つのCustomerにまとめますし、これが一つの目安です。ただし、電話番号や住所の検査を後から追加する可能性があるため、最初からprivateフィールドとメソッド経由の更新にしておくと変更しやすい構造になります。

public class Customer {
    private String name;
    private String address;
    private String phoneNumber;

    public String getName() { return name; }
    public void setName(String name) {
        if (name == null || name.isBlank()) { throw new IllegalArgumentException("名前を入力してください"); }
        this.name = name;
    }
    public String getAddress() { return address; }
    public void setAddress(String address) { this.address = address; }
    public String getPhoneNumber() { return phoneNumber; }
    public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; }
}

結果: 期待される状態は、空の名前を拒否し、顧客データをメソッド経由で保持できることです。

public class Main {
    public static void main(String[] args) {
        Customer customer = new Customer();
        customer.setName("山田太郎");
        System.out.println(customer.getName());
    }
}

結果: 期待される出力は「山田太郎」です。

顧客情報を検索する

これを一覧で扱う場合、CustomerManagerList<Customer>を内部に持ちます。外部には追加と検索のメソッドだけを公開し、一覧そのものを直接操作させないことで、コーディング技術としての責任分離が明確になる。

import java.util.ArrayList;
import java.util.List;

public class CustomerManager {
    private final List<Customer> customers = new ArrayList<>();

    public void addCustomer(Customer customer) {
        if (customer == null) { throw new IllegalArgumentException("顧客を指定してください"); }
        customers.add(customer);
    }

    public Customer findCustomerByName(String name) {
        for (Customer customer : customers) {
            if (customer.getName().equals(name)) { return customer; }
        }
        return null;
    }
}

結果: 期待される動きは、登録済みの顧客から名前が一致するCustomerを返すことです。

public class Main {
    public static void main(String[] args) {
        CustomerManager manager = new CustomerManager();
        Customer customer1 = new Customer();
        customer1.setName("山田太郎");
        manager.addCustomer(customer1);
        Customer customer2 = new Customer();
        customer2.setName("佐藤花子");
        manager.addCustomer(customer2);
        Customer found = manager.findCustomerByName("山田太郎");
        if (found != null) { System.out.println("顧客が見つかりました: " + found.getName()); }
    }
}

結果: 期待される出力は「顧客が見つかりました: 山田太郎」です。

商品在庫を管理する

商品在庫では、商品名と在庫数をProductに閉じ込めます。このとき、在庫数は外部から自由に代入させず、追加と減少の操作だけを公開すると、負の在庫を防ぐルールをクラス内へ集められます。

public class Product {
    private final String productName;
    private int stockQuantity;

    public Product(String productName, int stockQuantity) {
        if (stockQuantity < 0) { throw new IllegalArgumentException("在庫数量は0以上にしてください"); }
        this.productName = productName;
        this.stockQuantity = stockQuantity;
    }

    public String getProductName() { return productName; }
    public int getStockQuantity() { return stockQuantity; }
}

結果: 期待される状態は、商品名が生成後に変わらず、在庫数量を読み取れることです。

public class Product {
    private final String productName;
    private int stockQuantity;

    public Product(String productName, int stockQuantity) {
        if (stockQuantity < 0) { throw new IllegalArgumentException("在庫数量は0以上にしてください"); }
        this.productName = productName;
        this.stockQuantity = stockQuantity;
    }

    public void addStock(int quantity) {
        if (quantity <= 0) { throw new IllegalArgumentException("追加数は正の値にしてください"); }
        stockQuantity += quantity;
    }

    public boolean reduceStock(int quantity) {
        if (quantity <= 0 || quantity > stockQuantity) { return false; }
        stockQuantity -= quantity;
        return true;
    }

    public int getStockQuantity() { return stockQuantity; }
}

結果: 期待される動きは、追加数を検査し、在庫不足の減少ではfalseを返すことです。

public class Main {
    public static void main(String[] args) {
        Product product = new Product("リンゴ", 100);
        product.addStock(50);
        System.out.println(product.getStockQuantity());
        boolean reduced = product.reduceStock(30);
        System.out.println(product.getStockQuantity());
        System.out.println(reduced);
    }
}

結果: 期待される出力は、150120trueの順です。

同様に、親クラスの振る舞いを変える場面では、Javaのオーバーライド解説にあるように@Overrideを使うと意図が明確になります。カプセル化と継承を組み合わせると公開範囲が広がるため、どのメソッドを外へ見せるかを慎重に決める必要がある。

認証処理を組み立てる

認証の例では、ユーザー名とパスワードを持つUserを用意します。ただし、学習用コードであってもパスワードを外へ返すゲッターは避け、照合用のメソッドを通じて一致確認だけを返すほうが安全な設計に近いです。

public class User {
    private String username;
    private String password;

    public void register(String username, String password) {
        if (username == null || username.isBlank()) { throw new IllegalArgumentException("ユーザー名を入力してください"); }
        if (password == null || password.length() < 5) { throw new IllegalArgumentException("パスワードは5文字以上にしてください"); }
        this.username = username;
        this.password = password;
    }

    public String getUsername() { return username; }
    public boolean matchesPassword(String password) { return this.password.equals(password); }
}

結果: 期待される動きは、短すぎるパスワードを拒否し、照合結果だけを返すことです。

public class Authentication {
    private final User user;

    public Authentication(User user) {
        this.user = user;
    }

    public boolean login(String username, String password) {
        return user.getUsername().equals(username)
                && user.matchesPassword(password);
    }
}

結果: 期待される動きは、ユーザー名とパスワードが一致した場合だけtrueを返すことです。

public class Main {
    public static void main(String[] args) {
        User user = new User();
        user.register("testUser", "12345");
        Authentication auth = new Authentication(user);
        System.out.println(auth.login("testUser", "12345"));
        System.out.println(auth.login("testUser", "wrong"));
    }
}

結果: 期待される出力は、認証成功のtrueと認証失敗のfalseです。

ただし、この認証例はプログラミング学習向けの単純な構造です。一般的に、実サービスでは平文パスワードを保持せず、標準APIや認証基盤、フレームワークの機能を使って保護する設計になります。

ℹ️ 補足: 標準ライブラリのクラスやメソッドを確認する場合は、Java SE 21 API Documentationが一次情報になります。

まとめ

Javaのカプセル化は、privateフィールド、publicメソッド、コンストラクタ、バリデーションを組み合わせ、状態を安全に扱うための設計です。値を隠すだけでなく、変更してよい条件をクラス内部に集めることで、呼び出し側のコードが読みやすくなるのが一般的です。

その考え方は、顧客管理の検索、在庫管理の増減、認証処理の照合など、状態を持つ処理にそのまま応用できるのが目安です。オブジェクト指向のプログラミング学習では、どのフィールドを隠し、どの操作名を公開するかを考える習慣がコーディング技術の精度を上げるでしょう。

これからJavaを学ぶプログラミング初心者は、サンプルコードを大きくする前に、小さなクラスで不正値を止める練習を重ねるとよい。カプセル化を設計として扱えるようになると、Javaのコードは単なる文法の集合ではなく、変更に耐えやすい部品の組み合わせとして見えてきます。

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。

関連記事