目的
Java言語を含めたプログラミングの学習を始めたばかりの方、既学習者の方は復習用に、
変数のスコープを学ぶ為に書いています。
前回は型変換について学びました。
今回は変数のスコープについてです。
【Java入門目次】
・変数と型
・型変換
・変数のスコープ ←今ここ
・文字列の操作
・配列の操作
・演算子
・条件分岐
・繰り返し処理
・クラスについて(準備中)
・抽象クラス(準備中)
・インターフェース(準備中)
・カプセル化(準備中)
・モジュールについて(準備中)
・例外処理について
・ラムダ式について
・Stream APIについて
変数のスコープ(有効範囲)とは
文字通り定義した変数の有効な範囲のこと。
(変数の宣言した場所によって、その変数を使用できる範囲が制限される)
・ローカル変数
・インスタンス変数
・static変数
宣言する場所によって、呼び名も複数種類に分かれています。
簡単な例を用いながらスコープについて見ていきましょう。
ローカル変数
{ } ブロック単位
で有効範囲が違います。
ブロックの中で、変数を宣言した位置より後ろで使用することが出来ます。
class Main {
public static void main(String args[]) {
String block1 = "ブロック1";
System.out.println(block1); // ブロック1
{
String block2 = "ブロック2";
// block2を宣言したブロック内なのでアクセスできる
System.out.println(block2); // ブロック2
// block1はアクセスできる
System.out.println(block1); // ブロック1
}
// block2を宣言したブロックの外側なので、コンパイルエラーになる
System.out.println(block2); // block2 cannot be resolved to a variable
}
}
上記は単体でブロックが登場したので、もっと具体的な例を...
別メソッドから違うメソッド内で宣言された変数にはアクセスできない。
class Main {
public static void main(String args[]) {
// mainメソッド内にてblock1変数を宣言・初期化している
String block1 = "ブロック1";
System.out.println(block1); // ブロック1
}
public static void method() {
// 範囲外であるこのメソッドは、block1変数にはアクセスできないので、コンパイルエラーになる
block1 = "ブロック3"; // block1 cannot be resolved to a variable
}
}
if文内で宣言された変数にはアクセスできない。
class Main {
public static void main(String args[]) {
String block1 = "ブロック1";
System.out.println(block1); // ブロック1
boolean flg = true;
if(flg) {
String block2 = "ブロック2";
}
// 変数block2は、if文内で定義したためブロックの外側なので、コンパイルエラーになる
block2 = "ブロック2を書き換えたい"; // block2 cannot be resolved to a variable
}
いずれも { } ブロック単位
の有効範囲が違うためアクセスできずにコンパイルエラーとなります。
ちなみに、
JDK 10よりLocal-Variable Type Inference(ローカル変数型推論)が使用できる様になっています。
詳しくは後日別記事にまとめようと思いますので、今回は割愛させていただきます。
インスタンス変数
クラス定義の直下で宣言する変数。
class Person {
// クラスの下で変数を定義(インスタンス変数)
String name;
int age;
int height;
// クラス内のメソッドからもアクセスできる(staticメソッドからはアクセスできません。下記参照。)
public void checkInfo() {
System.out.print("名前は"+ name + "、");
System.out.print("年齢は"+ age + "、");
if(age < 20) {
System.out.print("未成年。");
}
System.out.println("身長は"+ height + " です。");
}
}
Personクラスを元にnewキーワードでオブジェクトを複数生成(インスタンス化)した時、
インスタンス変数の値はインスタンス毎に異なり、オブジェクト毎に異なる値を扱うことができる。
オブジェクトを代入した変数名.インスタンス変数名
でアクセスする。
class Main {
public static void main(String args[]) {
Person tanaka = new Person(); // newキーワードでオブジェクトを生成
tanaka.name = "田中"; // オブジェクトを代入した変数名tanakaで、インスタンス変数nameに値を代入している
tanaka.age = 19; // // オブジェクトを代入した変数名tanakaで、インスタンス変数ageに値を代入している
System.out.println(tanaka.name); // 田中
System.out.println(tanaka.age); // 19
// ↓ローカル変数は宣言・初期化を行わなければならなかったが、
// インスタンス変数は自動で初期化される。
System.out.println(tanaka.height); // 0
tanaka.checkInfo(); // 名前は田中、年齢は19、未成年。身長は0 です。
Person suzuki = new Person(); // newキーワードでオブジェクトを生成
suzuki.name = "鈴木"; // オブジェクトを代入した変数名suzukiで、インスタンス変数nameに値を代入している
suzuki.age = 51; // オブジェクトを代入した変数名suzukiで、インスタンス変数ageに値を代入している
suzuki.height = 146; // オブジェクトを代入した変数名suzukiで、インスタンス変数heightに値を代入している
System.out.println(suzuki.name); // 鈴木
System.out.println(suzuki.age); // 51
System.out.println(suzuki.height); // 146
suzuki.checkInfo(); // 名前は鈴木、年齢は51、身長は146 です。
}
}
ただし、staticメソッドからのアクセスは出来ません。
インスタンス変数は、インスタンス化されて オブジェクトを代入した変数名.インスタンス変数名
でアクセスする。
どのインスタンスか特定しなければアクセスできない。
なので、インスタンス化されていない状態で、アクセスしようとするとコンパイルエラーとなる。
class Person2 {
// クラスの下で変数を定義
String name;
int age;
// クラス内のメソッドからもアクセスできる
public void checkInfo() {
System.out.print("名前は"+ name + "、");
System.out.print("年齢は"+ age + "、");
}
// インスタンス変数は、staticメソッドからアクセスはできないためコンパイルエラーとなる
public static void checkAge() {
if(age < 20) {
System.out.print("未成年。"); // Cannot make a static reference to the non-static field age
}
}
}
thisについて
ローカル変数とインスタンス変数が同名である時、this
を使って明示的にインスタンス変数を示してあげます。
・クラス内の関数内に、インスタンス変数と同じ名前の引数を定義する
・インスタンス変数と引数が同じ名前である時
class Person2 {
// クラスの下で変数を定義
String name;
int age;
public void getName() {
// 引数を受け取らない場合、インスタンス変数と同じ変数名の変数を定義出来る
String name = "getName()から呼んだ名前は山田";
// インスタンス変数を出力するのではなく、このメソッド内で定義したnameを優先的に出力してしまう
System.out.println(name);
// インスタンス変数のnameを出力したい時は、thisを使う
System.out.println("インスタンス変数の名前は" + this.name);
}
public void setAge(int age) {
// インスタンス変数と同じ変数名を引数で受け取った場合、
// 同じ変数名で定義しようとすると、重複していますとエラーがでる
// int age = 101; // Duplicate local variable age
// インスタンス変数に受け取った引数を代入する時も上述通りthisを使う
this.age = age;
System.out.println("setAgeでセットしたインスタンス変数の年齢は" + age);
}
}
class Main2 {
// インスタンス変数
public static void main(String[] args) {
Person2 sato = new Person2();
sato.name = "佐藤";
sato.getName();
// getName()から呼んだ名前は山田
// インスタンス変数の名前は佐藤 と出力される
sato.setAge(77);
// setAgeでセットしたインスタンス変数の年齢は77 と出力される
}
}
#static変数(クラス変数)
クラス定義の直下で宣言する変数。
変数を宣言する時、static修飾子を指定することでstatic変数として扱われる。
インスタンス変数は、各オブジェクト毎に値を保持していたが、
static変数は、1箇所にまとめられて値を保持する(全てのオブジェクトに対して同じ値が使用される)。
インスタンス変数がインスタンス毎の変数であり、static変数(クラス変数)はクラス毎の変数となります。
class Sample {
int number = 99; // インスタンス変数
static String color = "黄"; // static変数(クラス変数)
// static変数(クラス変数)に値をセットするメソッド
public void setColor(String value) {
color = value;
}
}
故にクラスをインスタンス化することなく、アクセスができる。
クラス名.static変数(クラス変数)名
class Main {
public static void main(String args[]) {
Sample sample1 = new Sample();
sample1.number = 10;
System.out.println(sample1.number); // 10
sample1.setColor("赤");
System.out.println(Sample.color); // 赤
Sample sample2 = new Sample();
// インスタンス変数であるnumberは、オブジェクト毎に保持しているものなので、初期値の99が入っている。
System.out.println(sample2.number); // 99
// static変数(クラス変数)のcolorは共通の値を共有するため、
// デフォルトの「黄」はsample1.setColorのタイミングで赤がセットされた。
// だからここでstatic変数(クラス変数)にアクセスすると赤と出力される。
System.out.println(Sample.color); // 赤
}
}
static変数(クラス変数)はクラスの共通変数のため、変更を加えると使用している箇所全てに影響が出てしまいます。
static変数(クラス変数)を使うタイミング
例) 人が何人作成されたかのデータを保持したい時。
class Human {
// インスタンス変数
String name;
// static変数(クラス変数)
static int count = 0;
// インスタンス化された時に、static変数(クラス変数)を +1 する
Human() {
count++;
}
public static void humanCount() {
// 何人作成されたか確認する
System.out.println(count + "人作成されています");
}
}
class Main {
public static void main(String[] args) {
Human yamada = new Human();
yamada.name = "山田";
System.out.println(yamada.name); // 山田
// static変数(クラス変数)を出力
System.out.println(Human.count); // 1
Human takeda = new Human();
takeda.name = "武田";
System.out.println(takeda.name); // 武田
// static変数(クラス変数)を出力
System.out.println(Human.count); // 2
Human oota = new Human();
oota.name = "太田";
System.out.println(oota.name); // 太田
// static変数(クラス変数)を出力
System.out.println(Human.count); // 3
Human.humanCount(); // 3人作成されています と出力される
}
}
終わりに
変数のスコープ(影響範囲)について学びました。
スコープを意識しないでプログラミングすると...
・エラーが発生する可能性がある
・意図していない所で変数に値の代入が出来てしまう(代入するつもりがないのに代入されてしまっている)
想定外の動きになってしまうという問題が発生しかねません。
スコープの影響範囲を考えて実装すると、上記の様なトラブルを防ぐ事ができるかと思います。
次回は文字列の操作を掘り下げていこうと思います。