Javaアクセス修飾子の種類と使い分けをわかりやすく徹底解説

Mirai

Javaのアクセス修飾子ってなんだろう?

Zetto

アクセス修飾子は、クラスやフィールド、メソッドなどに「どこからアクセスできるか」を指定するキーワードのことだね。

この記事では、Javaのアクセス修飾子の種類・違い・使い分けの判断基準を解説します。

Zettoのライタープロフィール

アクセス修飾子を曖昧なまま進めると、クラス設計が崩れて後から修正が大変になります。実務でも資格試験でも、必ず問われる知識です。

この記事を読めば、4種類のアクセス修飾子の違い・使い分けの判断基準・カプセル化との関係まで一通り理解できます。

ぜひ参考にしてみてください。

目次

Javaのアクセス修飾子とは?基本を押さえよう

Javaのアクセス修飾子基本

まずは基本を解説します。

  • アクセス修飾子が必要な理由
  • 4種類のアクセス修飾子を比較表で整理する

アクセス修飾子が必要な理由

アクセス修飾子(access modifier)とは、クラス・フィールド・メソッドに「どこからアクセスできるか」を指定するキーワードです。

アクセス修飾子を使うメリットは以下の通りです。

  • 意図しない操作を防げる:外部から書き換えてほしくない変数を保護できる
  • 変更の影響範囲を限定できる:公開範囲を最小化すると、修正時の影響が小さくなる
  • 設計の意図が伝わる:「privateにしてある=外から触らないで」というメッセージになる

4種類のアクセス修飾子を比較表で整理する

Javaのアクセス修飾子は全部で4種類あります。

修飾子同クラス同パッケージサブクラス(別パッケージ)別パッケージ
public
protected×
デフォルト(指定なし)××
private×××

「privateが一番制限が強く、publicが一番開かれている」と覚えると整理しやすいと思います。

ここで「デフォルト修飾子」という言葉に注目してください。これは何も書かないことで適用される修飾子で、「パッケージプライベート」とも呼ばれます。何も書かなければpublicになるわけではないので、注意が必要です。

Javaのアクセス制御についての詳細は Oracle公式チュートリアル(Controlling Access to Members of a Class) でも確認できます。

public・protected・デフォルト・privateの違いと使い方

4つのアクセス修飾子の違い

アクセス修飾子の具体的な使い方をみていきましょう。

  • public:どこからでもアクセスできる
  • private:クラス内のみアクセスできる
  • protected:サブクラスと同パッケージからアクセスできる
  • デフォルト(指定なし):同パッケージ内のみアクセスできる

それぞれ解説していきます。

public:どこからでもアクセスできる

`public`は最も開かれた修飾子で、どのクラスからでもアクセスできます。

public class BankAccount {
    public String ownerName; // どこからでもアクセス可能

    public void printOwner() {
        System.out.println(ownerName);
    }
}

別のパッケージにあるクラスからも、インスタンスを生成すれば直接アクセスできます。

ただし、「publicだからどこにでも使っていい」というわけではありません。publicにする範囲が広くなるほど、仕様変更の影響が広がります。

Zetto

本当に外部に公開が必要なものだけpublicにするのが設計の基本です。

private:クラス内のみアクセスできる

`private`は最も制限の強い修飾子で、同じクラスの中からしかアクセスできません。

public class BankAccount {
    private int balance; // このクラスの内部からのみアクセス可能

    public void deposit(int amount) {
        balance += amount; // クラス内からはアクセスOK
    }
}

`balance`を`private`にすることで、外部から直接書き換えられる心配がなくなります。

僕がJavaのフリーランス案件に参加したとき、既存コードのフィールドはほぼ全て`private`で統一されていました。「フィールドはprivateにする」というのは現場での共通認識です。

protected:サブクラスと同パッケージからアクセスできる

`protected`は少し特殊で、「同パッケージ内のクラス」と「別パッケージでも継承(extends)しているサブクラス」からアクセスできます。

public class Animal {
    protected String name; // サブクラスからもアクセス可能

    protected void breathe() {
        System.out.println(name + "が呼吸しています");
    }
}

public class Dog extends Animal {
    public void bark() {
        System.out.println(name + "がワンと鳴きます"); // 継承先からアクセスOK
    }
}

継承(inheritance)を使った設計では`protected`が活躍します。「親クラスのフィールドを子クラスで使いたい、でも外部には見せたくない」というケースに向いています。

デフォルト(指定なし):同パッケージ内のみアクセスできる

修飾子を何も書かない場合、デフォルト(パッケージプライベート)として扱われます。

class Helper {
    int count; // デフォルト修飾子(何も書かない状態)

    void execute() {
        System.out.println("処理を実行します");
    }
}

デフォルトは「同じパッケージ内のクラスからはアクセスできるが、外のパッケージからはアクセスできない」という状態です。

パッケージ内部だけで使うユーティリティクラスに使うケースが典型例かなと思います。意図的に使うことで「このクラスはこのパッケージ専用」という設計の意図を伝えられます。

4種類の動作の違いが整理できたと思います。次のセクションでは「どれをいつ使うか」の判断基準を解説します。

Zetto

最初はpublicとprivateの2択で考えるだけで十分です。「外から使ってほしい→public」「外から隠したい→private」この感覚が身につけば、あとは自然と使い分けられるようになります。

アクセス修飾子の使い分け判断基準

アクセス修飾子の使い分け

アクセス修飾子の使い分けについて解説します。

  • フィールドはprivate・メソッドはpublicが基本の考え方
  • クラス・メソッド・フィールド別の選び方まとめ

フィールドはprivate・メソッドはpublicが基本の考え方

迷ったときの基本ルールは「フィールドはprivate、外部に提供するメソッドはpublic」です。

この考え方の背景にあるのが「カプセル化」(encapsulation)という設計の原則です。データ(フィールド)を外から直接触れなくして、操作(メソッド)を通じてのみアクセスさせる。この仕組みがバグの少ない設計につながります。

具体的には、こういった方針になります。

  • フィールド(変数)はprivate:外部から直接値を変えられると困るため
  • 外部に提供するメソッドはpublic:使ってほしい操作だけ公開する
  • 内部処理用メソッドはprivate:外部から呼ばれる必要がなければ隠す

「最初はなんでもpublicにしてしまう」という方は多いですが、後から公開範囲を広げるのは簡単です。最初にprivateにしておいて、必要に応じてpublicに変えるほうが安全な設計ができます。

クラス・メソッド・フィールド別の選び方まとめ

対象ごとの選び方をまとめます。

クラスのアクセス修飾子

クラスに使えるのは`public`とデフォルトの2種類だけです。`private`や`protected`はトップレベルクラスには使えません。

  • public:他のパッケージからも使われるクラスに使う(主要なモデルクラス・サービスクラスなど)
  • デフォルト:パッケージ内だけで使うクラスに使う(内部のユーティリティクラスなど)

フィールドのアクセス修飾子

  • private:基本これ一択。getter/setterを通じてアクセスさせる
  • protected:継承を意図しているときに使う。ただし慎重に判断する
  • public:定数(`static final`)以外は使わないのが安全

メソッドのアクセス修飾子

  • public:外部に提供する操作。インターフェースで定義するメソッドなど
  • private:クラス内部の処理で、外から呼ばれる必要がないもの
  • protected:サブクラスでオーバーライドを想定しているメソッド
  • デフォルト:パッケージ内でテスト目的などに使うケースがある
Zetto

使い分けの判断基準を押さえれば、設計時の迷いが減るでしょう。

カプセル化・実務・資格試験で役立つ応用知識

カプセル化と応用知識
  • カプセル化とアクセス修飾子の関係:なぜprivateにするのか
  • getter/setterパターンの設計思想
  • Spring Bootの実務コードでの使われ方
  • Java Silver・Gold試験での頻出ポイント

カプセル化とアクセス修飾子の関係:なぜprivateにするのか

カプセル化(encapsulation)とは、データと操作を1つのクラスにまとめ、外部から直接アクセスできないようにする設計の考え方です。

フィールドをpublicにして外から直接書き換えられる状態にすると、次のような問題が起きます。

// NG例:publicにしたケース
public class User {
    public int age;
}

User user = new User();
user.age = -5; // マイナスの年齢が入ってしまう

年齢にマイナスの値が入るのは明らかにおかしい状態です。でもpublicにしているとこれを防ぐ手段がありません。

// OK例:privateにしたケース
public class User {
    private int age;

    public void setAge(int age) {
        if (age >= 0) { // 値の検証ができる
            this.age = age;
        }
    }

    public int getAge() {
        return age;
    }
}

privateにしてsetterを通じてアクセスさせることで、値の検証(バリデーション)ができます。これがカプセル化の本質的なメリットです。

getter/setterパターンの設計思想

getter(ゲッター)はフィールドの値を返すメソッド、setter(セッター)は値をセットするメソッドです。

public class Product {
    private String name;
    private int price;

    // getter
    public String getName() {
        return name;
    }

    // setter
    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        if (price >= 0) {
            this.price = price;
        }
    }
}

命名ルールは決まっています。

  • getter:`get` + フィールド名(先頭大文字)。例:`getName()`
  • setter:`set` + フィールド名(先頭大文字)。例:`setName()`
  • booleanのgetter:`is` + フィールド名。例:`isActive()`

ただし、getter/setterを全フィールドに機械的につければいいわけではありません。本当に外部から読み書きが必要なフィールドにだけつけるのが理想です。

意味のないsetterをつけてしまうとpublicフィールドと変わらなくなります。「getter/setterをつければカプセル化できた」と思いがちですが、setterが必要かどうかを一度考える癖がつくと設計の質が上がります。

Spring Bootの実務コードでの使われ方

Spring Boot(JavaのWebフレームワーク)の実務コードでは、アクセス修飾子の使い方に一定のパターンがあります。

@Service
public class UserService {

    private final UserRepository userRepository; // privateで依存を保持

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User findById(Long id) { // 外部に提供するメソッドはpublic
        return userRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("User not found"));
    }

    private void validateUser(User user) { // 内部処理はprivate
        // バリデーションロジック
    }
}

実務ではこのパターンを覚えておけば大半の場面に対応できます。

  • クラスはpublic:Springのコンテナが管理できるよう公開する
  • フィールド(依存関係)はprivate final:外部から変更されないように固定する
  • 外部に公開するメソッドはpublic
  • 内部処理メソッドはprivate

僕が参加したSpring Bootの案件でも、この設計パターンはほぼ統一されていました。入社直後にこのパターンを把握しておくと、既存コードの読解がスムーズになります。

Java Silver・Gold試験での頻出ポイント

Java SilverとGoldの試験では、アクセス修飾子は確実に出題されます。

Java Silverで押さえるべきポイントは以下の通りです。

  • 4種類のアクセス修飾子とそのアクセス範囲(比較表の暗記)
  • privateはサブクラスからもアクセスできない(継承しても引き継がれない)
  • トップレベルクラスに使えるのはpublicとデフォルトのみ

Java Goldで出やすいポイントは以下の通りです。

  • protectedの動作:別パッケージのサブクラスからは「継承経由」のみアクセス可
  • インターフェースのメソッド:デフォルトでpublic abstract
  • sealedクラス(Java 17以降)とアクセス制御の組み合わせ
Zetto

Java SilverとGoldを受験したとき、どちらでもアクセス修飾子の問題は出ていました。特に`protected`の動作は引っかかりやすいので、コードを書いて実際に確かめるのが一番の対策かなと。

Javaアクセス修飾子の演習問題

アクセス修飾子の演習問題

理解を深めるために、演習問題を3つ用意しました。

演習問題1:アクセス修飾子のアクセス範囲を判定する

問題

以下のコードを見て、コンパイルエラーになる行をすべて答えてください。

// パッケージ: com.example.main
package com.example.main;

import com.example.sub.Car;

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        System.out.println(car.maker);    // ①
        System.out.println(car.model);    // ②
        System.out.println(car.price);    // ③
        System.out.println(car.getId());  // ④
    }
}
// パッケージ: com.example.sub
package com.example.sub;

public class Car {
    public String maker = "Toyota";
    String model = "Corolla";      // デフォルト修飾子
    protected int price = 200000;
    private int id = 1;

    public int getId() {
        return id;
    }
}

解答例

コンパイルエラーになるのは ②と③ です。

解説

`Main`クラスは`com.example.main`パッケージにあり、`Car`クラスとは別パッケージです。また`Main`は`Car`を継承していません。

  • ①(public):どこからでもアクセス可 → エラーなし
  • ②(デフォルト):同パッケージのみアクセス可。別パッケージのMainからはアクセス不可 → エラー
  • ③(protected):同パッケージ or 継承先のみ。別パッケージで継承もしていないMainからはアクセス不可 → エラー
  • ④(getId()はpublic):どこからでもアクセス可 → エラーなし

`protected`は「別パッケージの継承先クラスからならアクセスできる」点が試験でも引っかかりやすいポイントです。

演習問題2:カプセル化を意識したクラスを実装する

問題

以下の`Account`クラスを完成させてください。条件は4つあります。

  • フィールド`balance`(残高)は外部から直接変更できないようにする
  • 残高を取得する`getBalance()`メソッドを実装する
  • 残高を増やす`deposit(int amount)`メソッドを実装する。amountが0以下の場合は何もしない
  • 残高を減らす`withdraw(int amount)`メソッドを実装する。amountが残高を超える場合は何もしない
public class Account {
    // ここにフィールドを書く

    // ここにメソッドを書く
}

解答例

public class Account {
    private int balance;

    public int getBalance() {
        return balance;
    }

    public void deposit(int amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    public void withdraw(int amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
        }
    }
}

解説

ポイントは3つです。

  • balanceはprivate:外部から直接書き換えられないように`private`にする
  • getterはpublic:残高を読み取るだけのメソッドは`public`で提供する
  • setterは作らない:残高を直接セットするsetterは作らず、`deposit`と`withdraw`という意味のある操作だけを公開する

「setter代わりにdeposit/withdrawを使う」という設計がカプセル化の本質です。外部からは「入金する」「出金する」という操作しかできない状態を作ることで、不正な値が入るリスクをなくせます。

演習問題3:protectedの動作を理解する

問題

以下のコードはコンパイルエラーになります。エラーになっている理由と、修正方法を2つ答えてください。

// パッケージ: com.example.animal
package com.example.animal;

public class Animal {
    protected String name = "動物";
}
// パッケージ: com.example.zoo
package com.example.zoo;

import com.example.animal.Animal;

public class Zoo {
    public void showAnimal() {
        Animal a = new Animal();
        System.out.println(a.name); // ここでエラー
    }
}

解答例

エラーの理由:`Zoo`クラスは`Animal`クラスとは別パッケージにあり、かつ`Animal`を継承していないため、`protected`フィールドの`name`にアクセスできません。

修正方法①:ZooをAnimalのサブクラスにする

public class Zoo extends Animal {
    public void showAnimal() {
        System.out.println(this.name); // 継承経由でアクセス可
    }
}

修正方法②:AnimalにpublicなgetterをAnimalに追加する

// Animalクラス側にgetterを追加
public class Animal {
    protected String name = "動物";

    public String getName() {
        return name;
    }
}

// Zooからはgetterでアクセスする
public class Zoo {
    public void showAnimal() {
        Animal a = new Animal();
        System.out.println(a.getName()); // getterはpublicなのでアクセス可
    }
}

解説

`protected`は「同パッケージ」または「継承している別パッケージのサブクラス」からアクセスできます。継承なしに別パッケージから直接アクセスしようとするとエラーになります。

実務・試験どちらでも「protectedはサブクラスからならOK、でも継承していない別パッケージクラスからはNG」という点を確実に頭に入れておきましょう。

Javaアクセス修飾子をマスターして設計力を高めよう

アクセス修飾子で設計力向上

この記事では、Javaのアクセス修飾子について解説しました。

重要なポイントをまとめます。

  • 4種類の範囲:public(全体)→ protected(パッケージ+継承先)→ デフォルト(同パッケージ)→ private(クラス内のみ)
  • 基本方針:フィールドはprivate、外部に提供するメソッドはpublic
  • カプセル化:privateにしてgetter/setterで操作させることで、値の保護とバリデーションが実現できる
  • 試験対策:protectedの動作(継承なし別パッケージからはアクセス不可)は必ず手を動かして確認する

アクセス修飾子は「ルールを覚える」だけでなく、「なぜそうするのか」の設計思想を理解することが大切です。

思想が身につくと、コードを読んだときに「なぜここをprivateにしているのか」が読み取れるようになります。コードを読む力が上がると、現場でのキャッチアップスピードも変わってくるかなと思います。

プログラミング学習でつまずきやすいポイントや、挫折しない進め方については以下の記事も参考にしてみてください。

Mirai

protectedって、継承していないと別パッケージから使えないんだね。演習問題で手を動かして確かめてみます!

Zetto

そうそう、protectedは試験でも実務でも引っかかりやすいポイントだよ。手を動かして確かめると記憶に定着しやすいからね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次