1. はじめに
Java でも現実と同じく、すべてが常に誰にでも公開されてよいわけではありません。家に例えると、隣人に寝室まで自由に出入りしてほしくはありませんよね? プログラムでも同様に、外部から触れてほしくない変数やメソッドには、外からアクセスできないように「ドアを閉める」ことがあります。
そのため Java には アクセス修飾子 という、変数やメソッドをどこから使えるかを指定する特別なキーワードがあります。たとえば public や private です。
主なアクセス修飾子
| 修飾子 | 可視範囲 |
|---|---|
|
クラスが見える場所ならどこでも(他のパッケージや別ファイルからも) |
|
同じクラスの内部のみ |
|
同一パッケージ内のみ(つまり同じディレクトリのクラス内) |
他にも修飾子はありますが、それらは継承に進んだときに扱います。
実例
public class User
{
public String name; // 誰からでも見える
private int age; // User クラスの内部でのみ見える
public void sayHello()
{
System.out.println("こんにちは、私の名前は " + name);
}
private void secretMethod()
{
System.out.println("これは秘密のメソッドです!");
}
}
説明:
- name — User クラスが使える場所ならどこでも見える。
- age — User クラスの内部でのみ見える。
- sayHello() — public メソッドで、他のどのクラスからでも呼び出せる。
- secretMethod() — private メソッドで、外部からは呼び出せない。
実際の課題ではどう使う?
たとえば銀行口座を表すクラスがあるとします。残高を誰でも直接変更できるようにはしたくありませんよね。そのため、残高の変数は private にし、それを操作するための専用メソッドを用意します。
なぜすべてを public にしてはいけないのか?
「じゃあ全部を public にして、悩まずに済ませよう!」という誘惑があるかもしれません。しかしそれはカオスへの道です。誰もがあなたの残高やユーザー名を変更できたり、本来は内部専用のメソッドを呼べてしまったりします。大規模なプログラムでは、これがエラーや不可解なバグの原因になります。
Java 開発者の黄金律:
まずはすべてを private にし、本当に必要なものだけを外に公開する。
2. 変数のスコープ
スコープとは、変数が「存在し」、使用できるコードの範囲のことです。この範囲を外れると、その変数は「消え」て、存在しなかったかのようになります。
Java にはスコープによっていくつかの種類の変数があります:
- ローカル変数 — メソッドや { ... } のブロック内で宣言される。
- メソッドのパラメータ — メソッドの丸括弧内で宣言される。
- クラスのフィールド — クラス内で宣言されるが、メソッドの外側にある。
変数が { ブロック } の内側で宣言されている場合、
その ブロック の内側でのみ可視で、
外側からはアクセスできません。
ローカル変数
ローカル変数は、宣言されたメソッドまたはブロックの内部でのみ生きています。
void printSum(int a, int b)
{
int sum = a + b; // ローカル変数
System.out.println(sum);
}
// ここでは sum はもう存在しない!
メソッドの外で sum にアクセスしようとすると、コンパイルエラー「変数 sum が見つかりません」になります。
メソッドのパラメータ
パラメータも変数ですが、メソッド内部でのみ生存します。
void greet(String name)
{
System.out.println("こんにちは、" + name);
}
// ここでは name はもう存在しない!
クラスのフィールド(メンバー変数)
クラスのフィールドは、クラス内で(メソッドの外で)宣言されます。これらはこのクラスのすべてのメソッドから見えます。
public class Counter
{
private int count = 0; // クラスのフィールド
public void increment()
{
count++; // フィールドを使える
}
public int getCount()
{
return count; // こちらでもフィールドを使える
}
}
3. 変数のシャドーイング(Shadowing)
シャドーイング(shadowing)とは、あるスコープで、外側のスコープと同じ名前の変数(またはパラメータ)を宣言してしまう状況のことです。そのブロック内では「新しい」名前が古いものを隠してしまうため、外側の値には直接アクセスできなくなります。
シャドーイングの例:
class ShadowDemo
{
int value = 10; // クラスのフィールド
void printValue()
{
System.out.println(value); // 10 — クラスのフィールドを出力
int value = 5; // ローカル変数がクラスのフィールドをシャドーイング
System.out.println(value); // 10 ではなく 5 を出力
}
}
この例では、int value = 5; と書いたとき、同名のクラスフィールドよりも優先される新しいローカル変数を宣言しています。メソッド printValue() の中で value にアクセスすると、クラスフィールドではなくローカル変数が使われます。
それでもなお クラスの静的フィールド にアクセスしたい場合は、プレフィックスとしてクラス名を使います:
class ShadowDemo
{
static int value = 10; // クラスの静的フィールド
void printValue()
{
System.out.println(value); // 10 — クラスのフィールド
int value = 5;
System.out.println(value); // 5 — ローカル変数
System.out.println(ShadowDemo.value); // 10 — クラスの静的フィールド('ShadowDemo' 経由でアクセス)
}
}
非静的なクラスフィールド にアクセスしたい場合は、キーワード this を使います。これは現在のオブジェクトのインスタンスを指します。
class ShadowDemo
{
int value = 10;
void printValue()
{
System.out.println(value); // 10 — クラスのフィールド
int value = 5;
System.out.println(value); // 5 — ローカル変数
System.out.println(this.value); // 10 — クラスのフィールド('this' 経由でアクセス)
}
}
4. 実践: アクセス修飾子とスコープ
学習用の簡単な学生管理アプリを発展させていきましょう。フィールドやメソッドに異なるアクセス修飾子を付けてみます。
例: Student クラス
public class Student
{
public String name; // 学生名(誰からでも見える)
private int age; // 年齢(クラス内部でのみ見える)
public Student(String name, int age)
{
this.name = name;
this.age = age;
}
public void sayHello()
{
System.out.println("こんにちは、私の名前は " + name);
}
private void printSecret()
{
System.out.println("私の年齢: " + age);
}
public void revealSecret()
{
printSecret(); // クラス内からは private メソッドを呼び出せる
}
}
Student クラスの使用例
public class Main
{
public static void main(String[] args)
{
Student s = new Student("Vasya", 20);
s.sayHello(); // OK: public メソッド
s.revealSecret(); // OK: public メソッド(内部で private を呼び出す)
s.age = 30; // エラー! フィールド age は private
s.printSecret(); // エラー! メソッド printSecret は private
}
}
5. アクセス修飾子とスコープで陥りがちなミス
間違い1: 何もかもを public に — それはカオスへの近道!
すべてのフィールドやメソッドを公開にすると、誰がどのようにデータを変更しているのかを制御できなくなります。フィールドは private にし、本当に必要なものだけを公開しましょう。
間違い2: スコープ外で変数を使おうとしてしまう。
たとえば、メソッド内で変数を宣言したのに、その外側からアクセスしようとするとコンパイルエラーになります。ローカル変数は自分のブロック { ... } の中だけで生きています。
間違い3: 名前の衝突(シャドーイング)。
メソッド内でクラスフィールドと同じ名前の変数を宣言すると、どの変数を使っているのか混乱しがちです。this を使ってクラスフィールドを明示しましょう: this.value。
間違い4: 別のクラスから private メソッドやフィールドにアクセスしようとする。
メソッドやフィールドが private の場合、それらは同じクラスの内部からのみアクセス可能です。外部からアクセスしようとするとコンパイルエラーになります。
間違い5: ループやブロックのスコープを忘れる。
ループや任意のブロック { } の中で宣言された変数は、そのブロックの中でのみ生きています。ブロックの外では存在しません。
GO TO FULL VERSION