CodeGym /コース /JAVA 25 SELF /アクセス修飾子のエラー

アクセス修飾子のエラー

JAVA 25 SELF
レベル 23 , レッスン 2
使用可能

1. はじめに

Java のアクセス修飾子は、家の鍵の仕組みのようなものです。誰がどこからあなたの部屋(クラスのフィールド/メソッド)に「入れるか」を決めます。すべてのドアが開いていれば、誰でも来て何かを変えられます。すべて閉め切っていれば、誰も壊せませんが、ある時点で自分自身も出られなくなるかもしれません。

復習すると、Java には4つの基本的なアクセスレベルがあります:

修飾子 クラス内からアクセス可 同一パッケージ内からアクセス可 サブクラスからアクセス可 他パッケージからアクセス可
private
(package)
protected
(継承経由)
public

(package) — 修飾子を明示しない場合を指します。こうしたクラスメンバは同一パッケージ内でのみ可視です。

2. アクセス修飾子でありがちなミス

ミス 1: デフォルト修飾子(package-private)のフィールドやメソッド

最もよくある初学者のミスは、アクセス修飾子の指定を忘れることです。その結果、フィールドやメソッドがパッケージ全体から見えるようになり、意図せず他クラス(同一パッケージだがあなたのクラスとは関係ない)がオブジェクトの内部状態を変更できてしまいます。

// ミス: フィールド name が保護されていない!
class User {
    String name; // package-private!
}

その結果、このパッケージ内の任意のクラスが次のように書けてしまいます:

User user = new User();
user.name = "ヴァーシャ"; // 制限なし!

ミス 2: カプセル化の破壊 — フィールドを開放(public

次に多いのは、クラスのフィールドを public にしてしまうことです。学習中や短いサンプルでは便利ですが、実プロジェクトではほぼ常に悪手です。誰がどのようにデータを変更するかを制御できなくなります。

public class Account {
    public double balance; // 危険!
}

これで任意のコードが次のようにできます:

Account acc = new Account();
acc.balance = -1000000; // さて、誰の責任でしょうか?

ミス 3: getter と setter の未整備

開発者がフィールドを private にしたものの、それを操作するメソッドの追加を忘れてしまうことがあります。その結果、本来は適切だった取得や変更ができなくなります。

public class Product {
    private String name;
    // getName() も setName() もない
}

ミス 4: 他クラスから private メンバへアクセスしようとする

フィールドやメソッドを private として宣言した場合、同じパッケージ内であっても他クラスからはアクセスできません。初心者はしばしば、なぜフィールドが「見えない」のかと驚きます。

public class User {
    private String password;
}

public class UserService {
    public void resetPassword(User user) {
        // user.password = "123"; // コンパイルエラー!
    }
}

ミス 5: protected に関する誤解

多くの人は、protected は「継承があるところならどこでも見える」と考えがちです。しかし Java では、パッケージ外から protected メンバへアクセスできるのは継承経由のみ、しかもサブクラスに限られます。見落としやすい微妙な点です。

package animals;

public class Animal {
    protected void sleep() {}
}

package zoo;
import animals.Animal;

public class Dog extends Animal {
    public void test() {
        sleep(); // OK — サブクラス
    }
}

public class NotADog {
    public void test() {
        Animal a = new Animal();
        // a.sleep(); // エラー: サブクラスではない!
    }
}

3. 正しいやり方: ベストプラクティス

ルール 1: デフォルトはフィールドを private

これはカプセル化の要です。フィールドは当該クラス以外から隠すべきです。アクセスを与える必要があれば、getter/setter を使いましょう。

public class Book {
    private String title;
    private int pages;

    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}

ルール 2: 必要なメソッドだけを公開する

メソッドを外部から使えるようにする必要があるなら public に。パッケージ内だけで良いなら package-private のままに。継承先だけに必要なら protected を使います。

ルール 3: 可視性(スコープ)は最小限に

可視範囲が小さいほど、偶発的なエラーや「予期せぬ侵入者」の可能性は下がります。必要がなければフィールドやメソッドを public にしないでください。

ルール 4: アクセス制御に getter と setter を使う

読み書き時に追加のロジック(例えばバリデーション)を組み込めます。

public class Account {
    private double balance;

    public void setBalance(double balance) {
        if (balance < 0) {
            throw new IllegalArgumentException("残高は負の値にできません!");
        }
        this.balance = balance;
    }

    public double getBalance() {
        return balance;
    }
}

ルール 5: 内部実装を直接さらさない

配列やリストをフィールドに持つ場合、getter でそれをそのまま返さず、コピーを返すか必要な操作だけを提供しましょう。

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

    // 正しい:
    public List<String> getMembers() {
        return new ArrayList<>(members); // コピーを返す
    }
}

4. 実践例

たとえば、図書館の利用者を表すクラス LibraryUser があるとします。

よくない実装例

public class LibraryUser {
    public String name;
    public int borrowedBooks;
}

この形だと、どんなコードでもオブジェクトに対して好き放題できます:

LibraryUser user = new LibraryUser();
user.name = null;
user.borrowedBooks = -10; // ロジック? どんなロジック?

カプセル化を用いた正しい実装例

public class LibraryUser {
    private String name;
    private int borrowedBooks;

    public LibraryUser(String name) {
        this.name = name;
        this.borrowedBooks = 0;
    }

    public String getName() {
        return name;
    }

    public int getBorrowedBooks() {
        return borrowedBooks;
    }

    public void borrowBook() {
        borrowedBooks++;
    }

    public void returnBook() {
        if (borrowedBooks > 0) {
            borrowedBooks--;
        }
    }
}

これで外部コードは、借りた冊数や利用者名を直接変更できません。すべてクラスのメソッド経由でのみ制御されます。

5. 実装上の特徴と注意点

大量の getter/setter を書くより、フィールドを public にしてしまったほうが簡単に思えることがあります。しかしそれは落とし穴です。公開フィールドは、鍵のかかっていない玄関と同じ — たしかに楽ですが、安全ではありません。

もう一つの注意点として、すべてのフィールドに getter/setter が必要とは限りません。オブジェクト生成後に値を変えるべきでないフィールドは、getter のみを提供し、フィールドは final にしましょう:

public class Passport {
    private final String number;

    public Passport(String number) {
        this.number = number;
    }

    public String getNumber() {
        return number;
    }
}

また覚えておきましょう: クラスを public として宣言した場合、ファイル名はクラス名と一致しなければなりません!これはアクセス修飾子そのものの話ではありませんが、初学者が非常によくやるミスです。

6. アクセス修飾子でよくあるミス集

ミス №1: フィールドやメソッドにアクセス修飾子を付け忘れる。 その結果、意図せずパッケージ全体から見えるようになります。IDE が警告しない場合でも、常に明示的に修飾子を指定しましょう。

ミス №2: すべてのフィールドを public にする。 カプセル化を壊し、コードを脆弱かつ予測不能にします。「簡単さ」を重視したサンプルの癖を本番コードに持ち込まないでください。

ミス №3: 他クラスから private フィールドへアクセスしようとする。 Java はそれを許可しません — コンパイラが守ってくれます。もしリフレクションで「回避」したくなったなら、そもそもなぜその必要があるのかを考えましょう。

ミス №4: protected メンバは、継承があるところならどこでも使えると期待する。 実際には、パッケージ外ではサブクラスから、かつ this またはサブクラスのオブジェクト経由でのみアクセスできます。

ミス №5: 内部コレクションをそのまま getter で返す。 内部の配列やリストへの参照を返すと、外部コードがそれを変更でき、クラスの不変条件が破られます。

ミス №6: setter で値を設定する際のチェック不足。 入力値を検証しないと、(負の残高など)不正なオブジェクト状態を招きます。

ミス №7: メソッドの可視性が広すぎる。 本来はパッケージ内やクラス内だけでよいメソッドを public にしてしまうと、不要な API を公開してしまい、保守が複雑になります。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION