Javaのクラス変数・インスタンス変数・ローカル変数の違いを徹底解説!【Java勉強ログ #19】

Java

はじめに

さて今回は、Javaにおけるクラス変数、インスタンス変数、ローカル変数の違いについてまとめたいと思います。

つい最近仕事をする中で、「この変数はクラス変数じゃなくてインスタンス変数にしておいた方が良い」といった話を聞きました。

案の定Javaに対する知識が全くない私にとっては何が何だかさっぱり・・・だったので調べて理解した内容を忘れないよう備忘録的に残しておきます。

それでは見ていきましょう~

クラス変数、インスタンス変数、ローカル変数の違い

ざっくりですが、「どこで宣言されるか」、「誰が共有するか」が大きく異なるポイントです。

もう少し細かい違いを表で見てみましょう。

種類宣言場所共有範囲自動初期化主な用途
クラス変数クラス内全インスタンスで共有型ごとのデフォルト値が自動設定共通データ
インスタンス変数クラス内インスタンスごと型ごとのデフォルト値が自動設定オブジェクトの状態
ローカル変数メソッド内そのメソッドのみ必ず自分で初期化一時的な処理

クラス変数、インスタンス変数で自動的に設定される値は以下です。

初期値
int0
double0.0
booleanfalse
Stringnull

クラス変数(static変数)

クラス変数、またの名をstatic変数と呼ぶらしいですが、以下のような特徴があります。

  • staticを付けて宣言する
  • 全てのインスタンスで共有される
  • クラスが読み込まれたときに1つだけ作られる
  • クラス名からアクセスできる

ちなみに「static」とは、”クラスに属しインスタンスを作らなくても利用できる”という意味です。

ゲームを例にとって具体的にイメージしてみましょう。

例えばクラス変数は、ゲームの中で「現在ログインしているプレイヤー数」です。これらはどのプレイヤーから見ても変わらない変数ですよね?

要するにゲーム全体で1つだけ持っておくような値をクラス変数とします。

<コード例>

class Player {

    static int playerCount = 0; // クラス変数

    String name;

    Player(String name) {
        this.name = name;
        playerCount++; // プレイヤー作成時に人数を増やす
    }
}

「playerCount」がプレイヤーの人数です。最初にstatic intで宣言し、初期値として”0″をセットしています。(自動設定されますが、もちろん意図的にセットしてもOKです。)

「name」という変数はプレイヤーの名前で、各プレイヤー毎に付けられるようになってますね。

Player(String name)が処理に当たりますが、引数で指定した名前を変数にセットし、合わせてプレイヤー作成時に人数を増やしています。

<使用例>

public class Main {

    public static void main(String[] args) {

        Player p1 = new Player("勇者");
        Player p2 = new Player("魔法使い");

        System.out.println(Player.playerCount);
    }

}

使用例はこんな感じです。

“勇者”と”魔法使い”を作っています。

<実行結果>

2

プレイヤーの人数を確認すると、”2″となっていますね。当然のことながら、”勇者”と”魔法使い”がいるからです。

<イメージ>

ゲーム全体
   │
   └ playerCount = 2
       ↑
     全員共有

Player p1
 └ name = 勇者

Player p2
 └ name = 魔法使い

インスタンス変数

インスタンス変数は以下のような特徴があります。

  • クラスの中で宣言
  • staticを付けない
  • インスタンスごとに別の値を持つ
  • オブジェクトの状態を表す

具体例でイメージしましょう。

<コード例>

class Player {

    String name;
    int hp;

}

例えば、キャラクター毎の”名前”や”HP”がインスタンス変数にあたります。オブジェクト毎(キャラクター毎)に別の値を持つわけですね。

<使用例>

Player hero = new Player();
hero.name = "勇者";
hero.hp = 100;

Player mage = new Player();
mage.name = "魔法使い";
mage.hp = 80;

System.out.println(hero.hp); // heroのHPを出力する
System.out.println(mage.hp); // mageのHPを出力する

上記では、「hero」というプレイヤーを定義し、名前を”勇者”に、HPを”100″にしています。

また、「mage」というプレイヤーは、名前を”魔法使い”に、HPを”80″にしています。

<実行結果>

100
80

セットした通りの値が出力されていますね。ゲームの中で全部共通というわけではなく、”勇者”は”勇者のHP”、”魔法使い”は”魔法使いのHP”を持っていることが分かります。

<イメージ>

Player hero
 ├ name = 勇者
 └ hp = 100

Player mage
 ├ name = 魔法使い
 └ hp = 80

ローカル変数

ローカル変数は以下のような特徴があります。

  • メソッドの中で宣言
  • そのメソッド内でしか使えない
  • メソッド終了で消える
  • 初期化しないと使えない

具体例でイメージしましょう。

<コード例>

class Player {

    void attack() {

        int damage = 10;

        System.out.println("ダメージ:" + damage);

    }

}

attackメソッドは攻撃処理です。

「int damage = 10」が攻撃で与えられるダメージを表しているわけで、これはattackメソッドの中だけで使われます。

<使用例>

Player hero = new Player();

hero.attack();
hero.attack();

heroというオブジェクトを作成し、attackメソッドを2回呼び出しています。

<実行結果>

ダメージ:10 // 1回目の処理結果
ダメージ:10 // 2回目の処理結果

単純に、「ダメージ:10」が2回出力されているだけに見えますが、処理の流れをイメージすることが重要です。

① 1回目の「hero.attack();」でattackメソッドを呼び出す

② 「damage = 10」となり、damageに10がセットされる

③ 「ダメージ:10」と出力される

④ メソッドが終了し、damageにセットした値が消える ← ★ここが重要

⑤ 2回目の「hero.attack();」でattackメソッドを呼び出す

~~~以下繰り返し~~~

上記のように、damageにセットした10という値は、メソッドが終わるとともに値が削除されます。これがローカル変数の特徴です。

<イメージ>

attack()

   damage = 10
       ↓
   ダメージ表示
       ↓
   メソッド終了
       ↓
   damage消える

なぜ使い分けが必要なのか?

結論から言うと、使い分けが必要な理由は大きく以下の3点です。

  1. データの役割が違うから
  2. 不要なデータ共有を防ぐため
  3. 不要なメモリ使用を防ぐため
  4. 一時データを安全にするため

それぞれ細かく見ていきましょう。

1. データの役割が違うから

ゲームには次のように役割の違うデータがあります。

データ役割種類
プレイヤー人数ゲーム全体の情報クラス変数
HPキャラクターごとの情報インスタンス変数
攻撃ダメージ一時的な計算ローカル変数

これをすべて同じ種類の変数で扱うとプログラムが分かりにくくなってしまうので、それぞれ適切な変数で役割を持たせているわけですね。

2. 不要なデータ共有を防ぐため

もしすべての変数が共有されると、困ったことが起きます。

例えば、HPをクラス変数にしてみましょう。

class Player {

    static int hp;

}

そしてプレイヤーを作ります。

Player hero = new Player();
Player mage = new Player();

hero.hp = 100;
mage.hp = 50;

こうすると結果は、

hero.hp = 50
mage.hp = 50

となってしまいます。

勇者のHPは100にしたはずが、その次にある魔法使いのHPを50にする処理を実行してしまうことで、HP(全体で共通しているクラス変数)が書き換えられてしまいます。

これは困ったことですよね。キャラクター毎に固有のHPを持てなくなってしまいます。

3. 不要なメモリ使用を防ぐため

もし全てをインスタンス変数にすると、今度は逆の困ったことが起きます。

例えばゲーム設定です。

class Game {

    int maxLevel = 100;

}

ゲームの最大レベルが100という設定ですね。この上で、もしGameオブジェクトを1000個作った場合、

maxLevel = 100
maxLevel = 100
maxLevel = 100
...

と同じデータが1000個作られます。これはどう考えても無駄です。そのため、ゲーム全体で共通しているデータを1つとして持っておこうというわけです。

4. 一時データを安全にするため

攻撃ダメージのような値は、「計算 → 使用 → 終了」だけでよいです。もしこれをインスタンス変数にすると、

class Player {

    int damage;

}
damage = 10

といったように、攻撃もしていないのに古い値が残る可能性があります。そのため、一時的な値はローカル変数とします。

最後に

さて今回は、Javaにおけるクラス変数、インスタンス変数、ローカル変数の違いについてまとめました。

具体例も交えたのでイメージしやすかったのではないでしょうか?

また、なぜ使い分けが必要なのかの理由も理解してもらえてたら嬉しいです。

持ち主変数
クラスクラス変数
オブジェクトインスタンス変数
メソッドローカル変数

以上!

前回まとめた記事も読んでもらえると嬉しいです!

Java以外の勉強記事も是非!

コメント

タイトルとURLをコピーしました