1. はじめに
Java のアクセス修飾子は、家の鍵の仕組みのようなものです。誰がどこからあなたの部屋(クラスのフィールド/メソッド)に「入れるか」を決めます。すべてのドアが開いていれば、誰でも来て何かを変えられます。すべて閉め切っていれば、誰も壊せませんが、ある時点で自分自身も出られなくなるかもしれません。
復習すると、Java には4つの基本的なアクセスレベルがあります:
| 修飾子 | クラス内からアクセス可 | 同一パッケージ内からアクセス可 | サブクラスからアクセス可 | 他パッケージからアクセス可 |
|---|---|---|---|---|
|
✔ | |||
| (package) | ✔ | ✔ | ||
|
✔ | ✔ | ✔ | (継承経由) |
|
✔ | ✔ | ✔ | ✔ |
(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 を公開してしまい、保守が複雑になります。
GO TO FULL VERSION