PR

Java:アクセス修飾子(public/protected/private)を3分でわかりやすく

Java

オブジェクト指向プログラミングで頻出する単語に「カプセル化」があります。ソースコードをその名の通り「カプセル」に入れるイメージで外部からデータを保護することを指す用語です。

Javaではカプセル化を実現するためにアクセス修飾子というものを使用します。アクセス修飾子とは、クラスのメンバー(フィールドやメソッド)に対するアクセスの範囲を制限するキーワードで、主なアクセス修飾子には、publicprotecteddefault(修飾子なし)、privateの4種類あります。

アクセス修飾子内容アクセス範囲
publicすべてのクラスからアクセス可能プロジェクト全体
protected同じパッケージおよびサブクラスからアクセス可能パッケージとサブクラス
default (なし)同じパッケージ内でアクセス可能パッケージ内
private同じクラス内でのみアクセス可能クラス内

このページでは、アクセス修飾子を理解するために必要となるカプセル化の概念から説明し、Javaにおけるpublic/protected/privateなどのアクセス修飾子の基本を1からわかりやすく解説します。

スポンサーリンク

前提:カプセル化とは?わかりやすく

カプセル化とは、簡単に言うとデータやメソッドを「カプセル」に入れて必要な部分だけを外に見せるという手法。データを直接外部からアクセスできる状態にしておくと、誤って変更されたり、悪意のある操作が行われたりするリスクがありうるため、カプセル化してそのデータを他からいじれないようにする!というのが基本的な考え方です。

例えば、銀行口座の残高を誰でも自由に変更できたら、大変なことになりますよね。このとき、「銀行口座の残高」は外からみえない(=操作できない)ようにするというのが基本的な考え方。

ただし、銀行口座の残高を直接操作することはできなくても、必要に応じて残高を知ったり、残高を増減させる必要があるシーンは出てきます。このため、カプセル化したデータは専用のメソッド(データ参照用のメソッドデータ操作用のメソッド)を用いて取得できるようにする必要があります。

カプセル化の基本的な考え方

  1. データの隠蔽(外から見えなくする)
    • データを直接外部からアクセスできないようにする。→データが誤って変更されるのを防ぐ。
    • プログラムの中では、データをクラス内に隠して、外部から直接見えないようにする。
  2. メソッドを使ったデータアクセス
    • データにアクセスするためには特別なメソッドを用意。→これらのメソッドを使うことで、データの読み取りや変更を行います。
    • こうすることで、データの操作方法をコントロールでき、データの一貫性や安全性を保つことが可能に。

Java:アクセス修飾子の使い方

アクセス修飾子は、カプセル化したい変数やメソッドに対してアクセスの範囲を制御するためのキーワードです。これを使うことで、どこからそのメンバーにアクセスできるかを決めることができます。

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

アクセス修飾子内容アクセス範囲
publicすべてのクラスからアクセス可能プロジェクト全体
protected同じパッケージおよびサブクラスからアクセス可能パッケージとサブクラス
default (なし)同じパッケージ内でアクセス可能パッケージ内
private同じクラス内でのみアクセス可能クラス内

これらの修飾子を使うことで、クラスやメンバー(フィールドやメソッド)に対するアクセスの範囲を設定できます。以下、それぞれの修飾子の使い方と動作を具体例を交えて説明します。

ちなみに、筆者の頭の中では↓みたいなイメージでいます。アクセス修飾子はコーディングするときの必須知識なので暗記必須です。

  • private
    「自分だけの部屋」
    この部屋には本人以外は入れません。他の人(クラスやオブジェクト)は完全にアクセスできないように鍵がかかっている。
  • パッケージプライベート(未指定)
    「同じ家に住んでいる仲間」
    同じパッケージは、同じ家に住んでいる人たちのようなもので、自由に出入りできますが、家の外の人は入れません。
  • protected
    「親族専用の部屋」
    同じ家(パッケージ)の人だけでなく、親族(サブクラス)も入ることができます。つまり、限られた範囲の「家族」だけがアクセスできる場所です。
  • public
    「誰でも使える公共スペース」
    家の内外問わず、どんな人でも自由にアクセスできるオープンなスペースです。

public(パブリック)

publicを使うと、そのクラスやメンバーはプロジェクト内のどこからでもアクセス可能です。

// クラス定義
public class Example {
    public int data; // パブリックフィールド

    public void display() { // パブリックメソッド
        System.out.println("Public Method");
    }
}

// クラスへのアクセス
public class Main {
    public static void main(String[] args) {
        Example obj = new Example();
        obj.data = 10; // 直接アクセス可能
        obj.display(); // 直接アクセス可能
    }
}

この例では、dataフィールドもdisplayメソッドもpublicなので、他のクラスから自由にアクセスすることができます。要はザックリいえば、どこからでも自由にアクセスしてOK!という場合に使うキーワードです。

protected(プロテクテッド)

protectedを使うと、同じパッケージ内とそのクラスを継承したサブクラスからアクセス可能です。

参考 クラスの「継承」とは?

// クラス定義
public class SuperClass {
    protected void display() {
        System.out.println("Protected Method");
    }
}

// クラスへのアクセス
public class SubClass extends SuperClass {
    public static void main(String[] args) {
        SubClass obj = new SubClass();
        obj.display(); // サブクラスからprotectedメソッドにアクセス可能
    }
}

が、別のパッケージにあるクラスからは、protectedメソッドにアクセスできません。

// クラス定義(別パッケージ)
public class SuperClass {
    protected void display() {
        System.out.println("Protected Method");
    }
}

// クラスへのアクセス
public class Main {
    public static void main(String[] args) {
        SuperClass obj = new SuperClass();
        obj.display(); // エラー:別のパッケージからアクセス不可
    }
}

default(デフォルト)

アクセス修飾子を指定しない場合、そのメンバーはデフォルトで同じパッケージ内からのみアクセス可能です。

// クラス定義
class Example {
    int data; // デフォルトフィールド

    void display() { // デフォルトメソッド
        System.out.println("Default Method");
    }
}

// クラスへのアクセス
public class Main {
    public static void main(String[] args) {
        Example obj = new Example();
        obj.data = 10; // 同じパッケージ内でアクセス可能
        obj.display(); // 同じパッケージ内でアクセス可能
    }
}

が、別のパッケージにあるクラスからは、デフォルトのメンバーにアクセスできません。

// クラス定義
package package1;

class Example {
    int data; // デフォルトフィールド

    void display() { // デフォルトメソッド
        System.out.println("Default Method");
    }
}

// 別パッケージのクラスへのアクセス
package package2;

import package1.Example;

public class Main {
    public static void main(String[] args) {
        Example obj = new Example();
        obj.data = 10; // エラー:別パッケージからアクセス不可
        obj.display(); // エラー:別パッケージからアクセス不可
    }
}

private(プライベート)

privateを使うと、そのクラス内でのみアクセス可能です。つまり、他のクラスからは直接アクセスできません。

① アクセスできるパターン

// クラス定義
public class Example {
    private int data; // プライベートフィールド

    public int getData() { // パブリックゲッターメソッド
        return data;
    }

    public void setData(int data) { // パブリックセッターメソッド
        this.data = data;
    }
}

// クラスへのアクセス
public class Main {
    public static void main(String[] args) {
        Example obj = new Example();
        obj.setData(10); // セッターメソッドでアクセス
        System.out.println(obj.getData()); // ゲッターメソッドでアクセス
    }
}

② アクセスできないパターン

// クラス定義
public class Example {
    private int data; // プライベートフィールド

    public int getData() { // パブリックゲッターメソッド
        return data;
    }

    public void setData(int data) { // パブリックセッターメソッド
        this.data = data;
    }
}

// クラスへのアクセス
public class Main {
    public static void main(String[] args) {
        Example obj = new Example();
        obj.data = 10; // エラー:直接アクセス不可
    }
}

最後に、すべてのアクセス修飾子を用いたクラスの例を見てみましょう。

package example;

public class AccessExample {
    // ① privateフィールド:このクラス内だけでアクセス可能
    private int privateField = 10;
    
    // ② パッケージプライベート(修飾子未指定)のフィールド:同じパッケージ内ならアクセス可能
    int packagePrivateField = 20;
    
    // ③ protectedフィールド:同じパッケージ内およびサブクラスからアクセス可能
    protected int protectedField = 30;
    
    // ④ publicフィールド:どこからでもアクセス可能
    public int publicField = 40;
    
    // コンストラクタ(同じクラス内なので全てのフィールドにアクセスできる)
    public AccessExample() {
        System.out.println("Constructor: 各フィールドの値");
        System.out.println("privateField = " + privateField);
        System.out.println("packagePrivateField = " + packagePrivateField);
        System.out.println("protectedField = " + protectedField);
        System.out.println("publicField = " + publicField);
    }
    
    // ⑤ privateメソッド:このクラス内だけで利用できる
    private void privateMethod() {
        System.out.println("privateMethodが呼ばれました");
    }
    
    // ⑥ パッケージプライベートメソッド:同じパッケージ内なら利用可能
    void packagePrivateMethod() {
        System.out.println("packagePrivateMethodが呼ばれました");
    }
    
    // ⑦ protectedメソッド:同じパッケージとサブクラスから利用可能
    protected void protectedMethod() {
        System.out.println("protectedMethodが呼ばれました");
    }
    
    // ⑧ publicメソッド:どこからでも利用可能
    public void publicMethod() {
        System.out.println("publicMethodが呼ばれました");
    }
    
    // mainメソッド:同じクラス内なので、すべてのメソッドにアクセスできる
    public static void main(String[] args) {
        AccessExample example = new AccessExample();
        
        // ここでは全メソッドを呼び出せます
        example.privateMethod();
        example.packagePrivateMethod();
        example.protectedMethod();
        example.publicMethod();
    }
}
  1. フィールドの定義
    • privateFieldprivate として宣言されており、AccessExample クラス内でのみ利用可能です。
    • packagePrivateField は修飾子を指定していないため、同じパッケージ内(この例では example パッケージ)でのみアクセス可能です。
    • protectedField は、同じパッケージ内はもちろん、他のパッケージにあるサブクラスからもアクセスできます。
    • publicField は、どのパッケージからもアクセス可能な公開フィールドです。
  2. メソッドの定義
    • privateMethod() は、クラス内部専用のヘルパーメソッドなどに利用し、外部から隠蔽したい処理を定義します。
    • packagePrivateMethod() は、同じパッケージ内であれば複数のクラス間で共有する処理などに使います。
    • protectedMethod() は、サブクラスでの拡張が必要な場合などに用い、パッケージ内でも利用されることがあります。
    • publicMethod() は、外部に公開するAPIなどに使い、どこからでも呼び出せます。
  3. mainメソッド内での呼び出し
    同じクラス内なら、すべてのメソッド(privateも含む)を自由に呼び出すことができます。

次に、異なるパッケージにあるサブクラスからどのフィールド・メソッドにアクセスできるかを示す例です。

package other;

import example.AccessExample;

// AccessExampleのサブクラスを定義
public class SubAccessExample extends AccessExample {
    public static void main(String[] args) {
        SubAccessExample subExample = new SubAccessExample();
        
        // subExample.privateField; 
        //   → エラー: privateなフィールドはサブクラスでもアクセス不可
        
        // subExample.packagePrivateField; 
        //   → エラー: 異なるパッケージではパッケージプライベートのフィールドにアクセスできません
        
        // protectedフィールドはサブクラスからアクセス可能
        System.out.println("protectedField = " + subExample.protectedField);
        
        // publicフィールドはどこからでもアクセス可能
        System.out.println("publicField = " + subExample.publicField);
        
        // メソッドのアクセスについても同様です
        
        // subExample.privateMethod();
        //   → エラー: privateメソッドはアクセス不可
        
        // subExample.packagePrivateMethod();
        //   → エラー: 他パッケージのパッケージプライベートメソッドはアクセス不可
        
        // protectedメソッドはサブクラスから呼び出し可能
        subExample.protectedMethod();
        
        // publicメソッドはどこからでも呼び出し可能
        subExample.publicMethod();
    }
}
  1. インポートと継承
    • 別パッケージ otherexample.AccessExample をインポートし、サブクラス SubAccessExample を定義します。
  2. フィールドへのアクセス
    • privateFieldpackagePrivateField は、サブクラスであっても他パッケージの場合、アクセスできません。
    • protectedField はサブクラスでアクセス可能です。
    • publicField はどのパッケージからもアクセスできます。
  3. メソッドへのアクセス
    • 同様に、privateMethod()packagePrivateMethod() は呼び出せませんが、protectedMethod()publicMethod() は利用可能です。
  • private
    → 「このクラスだけ」の秘密。内部処理専用のフィールドやメソッドに使う。
  • パッケージプライベート
    → 同じパッケージ内でのみ共有する。外部パッケージからは隠す。
  • protected
    → 同じパッケージとサブクラスに開放。クラスの拡張のための設計に有用。
  • public
    → どこからでもアクセス可能。APIや公開すべきメンバーに使う。

このように、実際のコード例を通して各修飾子の使い方とアクセス可能な範囲を確認すると、どの場面でどの修飾子を使うかを直感的に理解できるようになります。

各要素に対して利用できるアクセス修飾子

クラス

①トップレベルクラス(外部に直接定義されるクラス)

  • 利用できるアクセス修飾子
    • public
    • パッケージプライベート(何も指定しない)
  • 理由:
    Javaの仕様上、トップレベルのクラスは外部からインスタンス化される対象となるため、privateprotected を付与することはできません。
    • public にするとどのパッケージからもアクセス可能になります。
    • 何も指定しない(パッケージプライベート)の場合、同じパッケージ内でのみアクセスでき、モジュールの内部実装として隠蔽できます。

②ネストしたクラス(内部クラス、静的ネストクラス)

  • 利用できるアクセス修飾子(全部OK)
    • private
    • パッケージプライベート(未指定)
    • protected
    • public
  • 理由:
    内部クラスは、外側のクラスの一部として設計されるため、外部への公開範囲をより柔軟に設定できます。
    • private にすると、外側のクラス内部のみで利用可能となり、実装の隠蔽に役立ちます。
    • protectedpublic にすることで、サブクラスや他のクラスからも利用できるように設計できます。

メソッド

  • 利用できるアクセス修飾子(全部OK)
    • private
    • パッケージプライベート(未指定)
    • protected
    • public
  • 理由:
    メソッドは、クラスの振る舞い(処理)を定義するため、外部からの呼び出しをどこまで許可するかを柔軟に制御できます。
    • private メソッドは、内部ロジックやヘルパーメソッドとして実装の詳細を隠すために使用します。
    • パッケージプライベート にすると、同じパッケージ内の連携クラスからの呼び出しが可能となり、パッケージ全体での内部APIとして利用できます。
    • protected は、サブクラスによるオーバーライドや拡張を可能にしつつ、パッケージ外からは制限する役割があります。
    • public にすると、外部APIとしてどこからでも利用できるようにします。

フィールド(メンバ変数)

  • 利用できるアクセス修飾子(全部OK)
    • private
    • パッケージプライベート(未指定)
    • protected
    • public
  • 理由:
    フィールドは、クラスが持つ状態(データ)を表します。
    • private にすることで、直接の値の変更や不正なアクセスを防ぎ、カプセル化(情報隠蔽)を実現します。
    • 他の修飾子を使用する場合は、どこから状態を読み書きできるかの設計意図に基づき、アクセスの範囲を調整します。

コンストラクタ

  • 利用できるアクセス修飾子(全部OK)
    • private
    • パッケージプライベート(未指定)
    • protected
    • public
  • 理由:
    コンストラクタは、インスタンス生成の方法を決定するために使用されます。
    • private にすると、クラス内部またはファクトリーメソッドを通じたインスタンス生成(例:シングルトンパターン)に利用されます。
    • 他のアクセスレベルを設定することで、どのクラスが自由にインスタンスを生成できるかを制御できます。

インターフェース

①トップレベルインターフェース

  • 利用できるアクセス修飾子:
    • public
    • パッケージプライベート(未指定)
  • 理由:
    トップレベルのインターフェースも、クラスと同様に、外部に公開するかパッケージ内に留めるかを選択します。
    ※ インターフェースのメソッドは、明示的に public と宣言されます(省略可能)。

②ネストしたインターフェース

  • 利用できるアクセス修飾子(全部OK)
    • public
    • private
    • protected
    • パッケージプライベート
  • 理由:
    内部クラス同様、必要に応じてアクセス範囲を柔軟に制御できます。

このように、各アクセス修飾子は「どこからどの要素にアクセスできるか」を定義し、その設計意図に基づいて利用されます。これにより、コードの安全性、保守性、そして再利用性を向上させることが可能となります。

特別なメソッドの使い方

カプセル化の考え方では、クラスの中のデータ(フィールド)を外部から直接操作できないように隠します。これにより、データが不正に変更されるのを防ぎます。例えば、クラス内にあるカプセル化されたデータは、クラスの外から直接触れません。その代わりに以下の特別なメソッドを使ってデータを操作します。

  • ゲッター(getter): データを取得するためのメソッド
  • セッター(setter): データを設定するためのメソッド

例として、Personというクラスにnameageという2つのデータを持たせます。このデータをカプセル化し、ゲッターとセッターを作成します。

public class Person {
    // データを隠す(カプセル化)
    private String name;
    private int age;

    // ゲッターメソッド
    public String getName() {
        return name; // nameのデータを返す
    }

    public int getAge() {
        return age; // ageのデータを返す
    }

    // セッターメソッド
    public void setName(String name) {
        // 名前がnullや空文字でない場合に設定
        if (name != null && !name.isEmpty()) {
            this.name = name;
        }
    }

    public void setAge(int age) {
        // 年齢が0以上の場合に設定
        if (age >= 0) {
            this.age = age;
        }
    }
}

この作成したPersonクラスを使って、データを設定したり取得したりしてみましょう。

public class Main {
    public static void main(String[] args) {
        // Personオブジェクトを作成
        Person person = new Person();

        // セッターメソッドでデータを設定
        person.setName("John");
        person.setAge(30);

        // ゲッターメソッドでデータを取得
        System.out.println("Name: " + person.getName()); // 出力: Name: John
        System.out.println("Age: " + person.getAge()); // 出力: Age: 30
    }
}

ゲッターとセッターを使うことで、データを安全に操作し、プログラムが正しく動作するように保つことができます。初心者の方でも、この基本的な仕組みを理解し、実践することで、より良いプログラムを作成できるようになります。

タイトルとURLをコピーしました