3.

Java の変数|宣言と初期化・var と final・スコープとシャドーイング

編集
この記事の要点
  • Java は静的型付け。宣言時に型を書き、あとから別の型を入れられない
  • ローカル変数は初期化しないと使えない。フィールドは自動で既定値が入る
  • var は「型を書かない」のではなくコンパイラに推論させるだけ。ローカル変数専用
  • final再代入を禁じるだけ。参照先のオブジェクトの中身は変えられる
  • スコープはブロック単位{ } を抜けた変数は使えない

宣言と代入

int count = 10;              // 型を明示する
String name = "田中";
double price = 1980.5;
boolean isActive = true;

int a, b;                    // まとめて宣言できる
a = 1;
b = 2;

int x = 1, y = 2;            // 宣言と同時に代入

// count = "十";              // コンパイルエラー: 型が違う

ローカル変数は初期化が必須

public void run() {
    int n;
    // System.out.println(n);
    // エラー: variable n might not have been initialized

    n = 1;
    System.out.println(n);    // OK

    int m;
    if (cond) {
        m = 1;
    }
    // System.out.println(m);  // エラー: 通らない経路がある

    int k;
    if (cond) { k = 1; } else { k = 2; }
    System.out.println(k);    // OK: すべての経路で代入されている
}

「代入されていない可能性がある」だけでコンパイルが通りません。実行時に未初期化の値を読む事故が起きないようになっています。

フィールドには既定値が入る

public class Sample {
    int count;          // 0
    double rate;        // 0.0
    boolean flag;       // false
    char c;             // ''
    String name;        // null      ← 参照型は null
    int[] items;        // null

    public void show() {
        System.out.println(count);          // 0
        System.out.println(name.length());  // NullPointerException
    }
}
既定値既定値
byte short int long0float double0.0
char''booleanfalse
参照型(String 配列 クラス)null

フィールドが null のまま使われるのが NullPointerException の最大の原因です。コンストラクタで必ず値を入れるか、final を付けて代入を強制してください。

プリミティブ型と参照型

// プリミティブ: 値そのものを持つ
int a = 1;
int b = a;
b = 2;
System.out.println(a);        // 1   影響しない

// 参照型: オブジェクトの場所を持つ
int[] x = {1, 2, 3};
int[] y = x;
y[0] = 99;
System.out.println(x[0]);     // 99  同じ配列を指している

// コピーしたいなら複製する
int[] z = x.clone();
z[0] = 0;
System.out.println(x[0]);     // 99  影響しない
// メソッドの引数も同じ
void f(int n)      { n = 99; }        // 呼び出し元は変わらない
void g(int[] arr)  { arr[0] = 99; }   // 中身が変わる
void h(int[] arr)  { arr = new int[]{0}; }  // 呼び出し元は変わらない

Java の引数はすべて値渡しです。参照型では「参照の値」がコピーされるため、中身は変えられますが、引数自体に別のオブジェクトを代入しても呼び出し元には反映されません。

var による型推論

var count = 10;                       // int と推論される
var name = "田中";                     // String
var list = new ArrayList<String>();   // ArrayList<String>
var map = new HashMap<String, List<Integer>>();   // 長い型名を書かずに済む

for (var entry : map.entrySet()) { }

// count = "十";                        // エラー: int のまま

// var n;                               // エラー: 初期値が必要
// var x = null;                        // エラー: 型を決められない
// var f = () -> {};                    // エラー: ラムダは推論できない

Java 10 以降で使えます。型が消えるわけではなく、右辺から決まった型に固定されます。ローカル変数だけで、フィールドや引数には使えません。

右辺を見ても型が分からない場合(var result = service.process();)は、読み手が困るので明示的に書いたほうが親切です。

final

final int MAX = 100;
// MAX = 200;                 // エラー: 再代入できない

final List<String> items = new ArrayList<>();
items.add("a");               // OK: 中身は変えられる
// items = new ArrayList<>(); // エラー: 参照先は変えられない

// 中身も変えたくないなら不変のコレクションにする
final List<String> fixed = List.of("a", "b");
// fixed.add("c");            // UnsupportedOperationException

// ラムダや匿名クラスから使う変数は final 相当でなければならない
int n = 1;
Runnable r = () -> System.out.println(n);
// n = 2;                     // エラー: n が実質 final でなくなる

final は「この名前が別のものを指さない」ことだけを保証します。不変にしたいなら List.of()record を使ってください。

スコープ

public class Sample {
    private int field = 1;            // インスタンス全体で使える
    private static int shared = 0;    // クラス全体で 1 つ

    public void run() {
        int local = 2;                // このメソッドの中だけ

        if (true) {
            int inner = 3;            // このブロックの中だけ
            System.out.println(local);
        }
        // System.out.println(inner); // エラー

        for (int i = 0; i < 3; i++) { }
        // System.out.println(i);     // エラー: for の外では使えない
    }
}

同じ名前で隠れる(シャドーイング)

public class User {
    private String name;
    private int count = 1;

    public User(String name) {
        // name = name;          // 引数に引数を代入しているだけ。フィールドは null のまま
        this.name = name;        // this. でフィールドを指す
    }

    public void run() {
        int count = 99;          // フィールドと同じ名前のローカル変数
        System.out.println(count);        // 99   ローカルが優先
        System.out.println(this.count);   // 1    フィールドは this. で見る
    }
}

コンストラクタでの this. の付け忘れはコンパイルエラーにならず、フィールドが null のまま残ります。IDE の警告を有効にしておいてください。

命名規則

対象書き方
変数・メソッドcamelCaseuserName getTotal()
クラス・インターフェースPascalCaseUserProfile
定数(static finalUPPER_SNAKEMAX_RETRY
パッケージすべて小文字com.example.app
型パラメータ大文字 1 文字T K V E
// 使える文字: 英数字 _ $(先頭は数字以外)
int _count = 1;      // 使えるが避ける
// int 2nd = 1;      // エラー
// int class = 1;    // エラー: 予約語

// 数値リテラルはアンダースコアで区切れる
int million = 1_000_000;
long hex = 0xFF_FF;

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 基本的なルール
  2. データ型
  3. 変数
  4. 定数
  5. 配列
  6. コレクション(List,Set,Queue)
  7. Map(連想配列)
  8. 演算子
  9. 条件分岐
  10. 繰り返し制御文
  11. クラス
  12. メソッド
  13. インスタンス化
  14. コンストラクタ
  15. staticキーワード
  16. オーバーロード
  17. 継承
  18. オーバーライド
  19. this
  20. super
  21. パッケージ
  22. アクセス修飾子
  23. 抽象クラス・メソッド
  24. インターフェース
  25. カプセル化
  26. データベース接続
  27. セッション
  28. ファイル入出力
  29. ラムダ式