1. アクセス修飾子の概要
Java には、クラス、フィールド、メソッド、コンストラクタに対して 4 つのアクセスレベルがあります。
| 修飾子 | どこからアクセス可能か |
|---|---|
|
あらゆる場所(クラス内・他クラス・他パッケージ) |
|
クラス内、サブクラス(継承先)、同一パッケージ内の他クラス |
|
パッケージ内のみ(修飾子を明示しない場合) |
|
現在のクラス内のみ |
それぞれを、例やちょっとした小ネタも交えつつ詳しく見ていきましょう。
public — 公開アクセス
public は世界に向けた掲示「どなたでもどうぞ!」のようなものです。クラス、フィールド、メソッド、コンストラクタが public なら、他のパッケージからであっても任意のクラスからアクセスできます。
例:
public class Cat {
public String name;
public void sayMeow() {
System.out.println("ニャー!");
}
}
このクラスとそのフィールド/メソッドにはどこからでもアクセスできます。ライブラリを作るなど、クラスを誰でも使えるようにしたい場合に便利です。
ただし! 公開フィールドが常に良いとは限りません(前回の講義を参照)。通常、外部から利用できる必要があるメソッドだけを public にし、フィールドはほぼ常に private にします。
private — クラス内部限定アクセス
private は暗証番号付き金庫のようなものです。クラス自身以外はそれらのメンバーにアクセスできません。サブクラス(派生クラス)であっても、private のフィールドやメソッドは見えません。
例:
public class Cat {
private String secretName;
public void setSecretName(String name) {
secretName = name;
}
public String getSecretName() {
return secretName;
}
}
ここでは、secretName を他のクラスから直接読み書きすることはできません。Cat 自身(あるいはそのメソッド)だけが操作できます。これはカプセル化の基本で、内部の詳細を隠し、メソッド経由でのみアクセスを提供します。
protected — 保護されたアクセス
protected は VIP パスのようなものです。クラス自身、サブクラス(別パッケージであっても)、そして同一パッケージ内のすべてのクラスからアクセスできます。
例:
public class Animal {
protected int age;
protected void growOlder() {
age++;
}
}
これで、Animal を継承する任意のクラスは、フィールド age とメソッド growOlder() にアクセスできます。
public class Cat extends Animal {
public void haveBirthday() {
growOlder();
System.out.println("猫は " + age + " 歳になりました!");
}
}
また、同一パッケージ内のすべてのクラスからも protected メンバーにアクセスできます。
(package-private) — パッケージ内アクセス
アクセス修飾子を明示しない場合、そのクラスメンバーは package-private(デフォルトのアクセス)となります。鍵のないドアのようなものですが、身内だけ用です。同一パッケージのクラスからのみアクセスできます。
例:
class Dog {
String name; // package-private
void bark() { // package-private
System.out.println("ワン!");
}
}
Dog クラス、そのフィールド name、メソッド bark() は同一パッケージ内でのみ利用可能です。別パッケージからアクセスしようとするとコンパイルエラーになります。
2. フィールドとメソッドへの修飾子の適用
なぜフィールドはほとんど常に private にするのか
クラスのフィールドは、その内部状態です。公開のままにすると、外部のコードがいつでも勝手に変更できてしまいます。たとえるなら、見知らぬ子どもにあなたの家具をレゴのようにして遊ばせるようなものです。気づいたら、冷蔵庫が浴室で逆さまになっているかもしれません。
悪いカプセル化の例:
public class Person {
public String name;
public int age;
}
良いカプセル化の例:
public class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
メソッド(ゲッターやセッター)を使えば、外部クラスがフィールドをどのように変更できるかを制御できます。たとえば、年齢を負の値にすることを禁止できます。
メソッドを public、protected、package-private のどれにするか
- public — だれからでも呼ばれるべきメソッド。通常はクラスの主要な機能。
- protected — サブクラスや同一パッケージ内だけで必要なメソッド(子クラスで役立つ補助メソッドなど)。
- package-private — パッケージ内だけで必要、外部に公開したくないメソッド(実装の内部詳細など)。
- private — クラス内部だけで使うメソッド(内部ロジックの補助など)。
例:
public class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
protected void applyInterest() {
balance *= 1.05;
}
void internalAudit() {
// package-private: パッケージ内のクラス専用
}
private void logAction(String action) {
// クラス内部でのみ使用
}
}
3. コード例: さまざまなアクセスレベルを持つクラス
公開、非公開、保護、パッケージ可視のメンバーをすべて含むクラスを作成し、他のクラスからアクセスしてどうなるかを確認してみましょう。
package zoo;
public class Animal {
public String publicName = "誰でもアクセス可";
protected String protectedName = "サブクラスとパッケージ内のみ";
String packageName = "パッケージ内のみ";
private String privateName = "Animal 内だけ";
public void publicMethod() {
System.out.println("公開メソッド");
}
protected void protectedMethod() {
System.out.println("保護されたメソッド");
}
void packageMethod() {
System.out.println("パッケージメソッド");
}
private void privateMethod() {
System.out.println("非公開メソッド");
}
}
次に、同一パッケージ内の別クラスからアクセスしてみます:
package zoo;
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.publicName); // OK
System.out.println(animal.protectedName); // OK
System.out.println(animal.packageName); // OK
System.out.println(animal.privateName); // エラー: private
animal.publicMethod(); // OK
animal.protectedMethod(); // OK
animal.packageMethod(); // OK
animal.privateMethod(); // エラー: private
}
}
次に、別パッケージからアクセスしてみます:
package other;
import zoo.Animal;
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.publicName); // OK
System.out.println(animal.protectedName); // エラー: protected
System.out.println(animal.packageName); // エラー: package-private
System.out.println(animal.privateName); // エラー: private
animal.publicMethod(); // OK
animal.protectedMethod(); // エラー: protected
animal.packageMethod(); // エラー: package-private
animal.privateMethod(); // エラー: private
}
}
まとめ:
- public — どこからでもアクセス可能。
- protected — パッケージ内およびサブクラスからアクセス可能(他パッケージのサブクラスでも継承経由なら可)。
- package-private — パッケージ内のみ。
- private — クラス内部のみ。
4. ベストプラクティス: アクセス修飾子の選び方
可視性を最小化する
あなたのフィールドやメソッドを見られる(触られる)コードは少ないほどよいのです。本当に外部に必要なものだけを公開しましょう。これは最小権限の原則(principle of least privilege)と呼ばれます。
- フィールドはほぼ常に private。例外は真の定数(public static final)のみ。詳細は次回以降の講義で扱います。
- メソッドを public にするのは、それがクラスの外部インターフェースの一部である場合だけ。
- 補助メソッド(内部ロジック)は private。
- サブクラス向けのメソッド は protected。
- パッケージ内部のサービス用メソッド は package-private。
なぜ重要か
- 実装詳細を公開すると、変更時に他者のコードを壊してしまう可能性がある。
- クラスのテストや保守が難しくなる。
- うっかりしたミス(たとえば誤ったフィールドの変更)がバグにつながる。
初心者は時々「そんな複雑なことは要らない、全部 public にしちゃおう!」と考えます。しかし、プロジェクトが大きくなるころには、誰かがフィールドを直接いじっていたせいで、プログラムの半分を書き直す羽目になったりします。
5. アクセス修飾子でありがちなミス
ミス 1: フィールドを public のまま、または既定の package-private のまま放置する。
修飾子を指定しないと、そのフィールドやメソッドはパッケージ内のすべてのクラスからアクセス可能になります。誰かが直接フィールドを変更し始めると、思わぬ事態を招きかねません。
ミス 2: 他のクラスから private メンバーへアクセスしようとする。
コンパイラは許してくれずエラーになります。もし無理やり reflection(リフレクション)で回避しようものなら、バグと予期せぬクラッシュの世界へようこそ。
ミス 3: public が多すぎる。
何でもかんでも public にすると、クラスはむき出しの配線が入った箱のようになり、誰かが間違った配線を引っ張ってすべて壊してしまうかもしれません。
ミス 4: サブクラスだけに必要なメソッドを protected にしない。
子クラスでの拡張にだけ必要なメソッドは、protected として公開し、public にすべきではありません。
ミス 5: 暗黙の package-private 可視性。
修飾子を付け忘れると、メソッドがパッケージ全体からアクセス可能になります。private のつもりでいると、思わぬ落とし穴になります。
GO TO FULL VERSION