Revised: Dec./27th/2001: Since: Dec./26th/2001
先に、メソッド内部のローカル変数と、クラスのメンバーであるメンバー変数について説明しました。ここでは、それぞれの有効範囲(スコープ)についてまとめておきます。
変数は宣言された場所以外からはアクセスできません。あるメソッド内で宣言されればその変数名は当該メソッド内でしか有効ではないということです。別のメソッドで同じ変数名を参照しても、全く別の変数が参照されます。
同じメソッド内でも、あるブロック内で宣言されれば、当該ブロック内でしか有効ではありません。一つのメソッド内に複数のブロックがある場合、あるブロック内で変数名 i が宣言されていても、別のブロックで i を参照しても、全く別の変数が参照されます。「for ループ」のところでも一度説明しました。
このように、メソッド内で宣言された変数は有効範囲が制限されています。変数を参照できる範囲(変数の有効範囲)をスコープと呼びます。 JavaVM は、プログラムの実行時に、制御が変数のスコープ内ならば変数をメモリ上にロードして値をセットし、制御がそのスコープから外れれば自動的にメモリ上からドロップ(アンロード)します。
従って、メソッド内部で宣言される変数は、そのスコープに制御が移る時点で具体的な値をセットしておくことが必要です。参照型変数の場合は、明示的な値の代入を省略したときには null が自動的にセットされます。基本データ型変数の場合は、明示的な値の代入をせずにその変数を利用しようとすると、コンパイル・エラーになります。
メソッド内で宣言された変数をローカル変数(局所変数)と呼びます。スコープがコードの特定の行数内に制限された変数のことです。
大きなプログラムでは、変数は膨大な個数に上ります。その変数名を全て重複しないようにしておくことは不可能ですし、前後数行のコードでしか利用しない変数が、プログラムの開始から終了までメモリを占有し続けるのもナンセンスです。
ローカル変数のスコープは、不便なのではなく、とても便利で現実的な仕様です。
TestLocal.java:
class TestLocal {
public static void main(String[] args) {
int y = 30;
//メソッド呼び出し
method(y);
System.out.println("----main()------");
System.out.println("main() y: " + y);
}
static void method(int y) {
y *= 10;
System.out.println("----method()----");
System.out.println("method() y: " + y);
}
}
main() メソッドで宣言された y は 30 が明示的に代入されています。mathod() の引数で宣言された y はメソッド呼び出し代入されていますが、これもローカル変数です。
二つの変数はそれぞれのメソッド内部でしか有効ではありませんので、 method() ローカル変数 y の値がどのように変化しても、 main() のローカル変数 y には無関係です。
C:\Java>javac TestLocal.java C:\Java>java TestLocal ----method()---- method() y: 300 ----main()------ main() y: 30
クラスのメソッド外で宣言された変数はメンバー変数(フィールド)と呼ばれます。
この変数は当該クラス内のメソッドからは制限なく参照できます。当該クラス内で定義されたメソッドで変数を共有できるわけですから、それだけでもかなり便利です。
また、別のクラスからも参照できます。但し、別のクラスからのアクセスの場合は、修飾子によってアクセス制限を掛けることができます。
修飾子によるアクセス制限によって、クラスの変数が想定した範囲以上に変更されないように保護することができます。例えば、 private 宣言しておけば、他のクラスからは直接参照できなくなりますので、クラスの開発者が想定した以外の値がセットされることを防げます。このように他のクラスからフィールドを隠蔽することはカプセル化と呼ばれる重要な概念です。他のクラスからフィールドを保護しているわけです。
メンバ変数は、実行時の制御が当該クラスから外れても、その変数が含まれているオブジェクトと共にメモリ上に保持されつづけます。スコープから外れると自動的にメモリ上からドロップされるローカル変数とは大きく異なる点です。オブジェクトが占有したメモリの解放は、 JavaVM の garbage collector によって自動的に実行されます。ユーザが明示的にメモリを操作することはありえません。
FieldTest.java:
class Field {
int y;
void method() {
y *= 5;
System.out.println("----method()----");
System.out.println("method() y: " + y);
}
}
class FieldTest {
public static void main(String[] args) {
//インスタンス化
Field obj = new Field();
//フィールドにアクセス
obj.y = 10;
//メソッド呼び出し
obj.method();
System.out.println("----main()------");
System.out.println(" main() y: " + obj.y);
}
}
ここでは、 Field クラスのフィールドとして y を宣言しています。この変数は、 Field クラスから生成したオブジェクト obj が存在する限り、一貫してメモリ上に保持されつづけます。
main() 実行Field のインスタンス化:y の暗黙の初期値は 0y に値を代入: 10method() 実行: y = y*5 method() で出力main() で出力C:\Java>javac FieldTest.java C:\Java>java FieldTest ----method()---- method() y: 50 ----main()------ main() y: 50
ここではフィールド変数を直接アクセス obj.y しています。カプセル化という観点では、フィールドは private 宣言しておき、メソッドを介してアクセスしたほうが、変数値を保護できて安全です。
CapsuleTest.java:
class Capsule {
private int y;
void setY(int x) {
y = x;
}
int getY() {
return y;
}
void method() {
y *= 5;
System.out.println("----method()----");
System.out.println("method() y: " + y);
}
}
class CapsuleTest {
public static void main(String[] args) {
//インスタンス化
Capsule obj = new Capsule();
//フィールドにアクセス
obj.setY(10);
//メソッド呼び出し
obj.method();
System.out.println("----main()------");
System.out.println(" main() y: " + obj.getY());
}
}
フィールド y は private 修飾されているので、他のクラスからはアクセスできません。他のクラスからのアクセスのために、ゲット/セットのメソッドを作りました。
フィールドはオブジェクトの属性としてメモリ上に保持されつづける変数です。他のオブジェクトからアクセスされるために、おかしな値がセットされる危険が伴います。クラス開発者の責任として、予めおかしな値がセットされないように、ロジックを組んでおくことが推奨されますが、そのためにフィールドを private 宣言しておき、アクセスするにはメソッドを介するようにします。他のパッケージからのアクセスも許可する場合は、これらのメソッドを public 宣言しておきましょう。
値を代入するメソッドをセッター (setter) 、取得するメソッドをゲッター (getter) と呼びます。フィールド xyz のセッターは setXyz()、ゲッターは getXyz() と命名することが多いようです。
では、ローカル変数とフィールドの名前が同じ場合は、ローカル変数のスコープ内でその変数名を記述すると、どちらの変数が参照されるでしょうか?このような場合は、ローカル変数が優先されます。フィールドを指し示したいときは、 this キーワードを使います。次に、その利用法を紹介します。