変数

宣言場所

変数は宣言する場所によって2種類の呼び方があり,特徴も少し異なります. 一つは,メソッド内で宣言する変数,ローカル変数(local variable), もう一つは,メソッドの外で宣言する変数,フィールド(field)です. メソッドもフィールドもどちらも,クラス宣言の直下になければいけません(クラス宣言の括弧({ })のすぐ内側).

  • フィールド(field)
    • 初期値は null(何も参照していないことを表す予約語).
    • 変数名.フィールド名でアクセスできる.
    • 有効範囲は,そのフィールドが宣言されているクラスの実体が有効な範囲.
      • クラスの実体の有効範囲と同じ.
      • クラスの実体の有効範囲は,そのクラスがフィールド,ローカル変数のどちらで宣言されたかによって異なる.
  • ローカル変数(local variable)
    • 初期化されなければコンパイルエラーとなる.
    • 有効な時間は,そのメソッド内のスコープ内のみ.
      • その変数が宣言された箇所以降かつ,宣言された場所の一番内側の括弧内({ })のみ.

プログラムを書くとき,変数の有効範囲はできるだけ狭く,というのが定石です. そのため,基本的には,ローカル変数を使い,ローカル変数で対応できない場合に,フィールドを利用しましょう.

実体

実体を確認するために,次のプログラムを考えてみましょう.

Date は日付を表す型です.new Date(), もしくは,new Date(year, month, day, hour, minute) という命令で実体を作成します.与える引数の型は全て Integer です. ただし,年月日,時間を指定する場合,年は1900年からの経過年数, 月は0から始まる(0が1月を表す)点に注意してください. 古い時代のもので,互換性のために残されているもので,変な仕様ではありますが,これで確認してみましょう.

また,コンパイル時に「非推奨のAPIを使用または...」という注意が出ますが,無視してください. これはコンパイルエラーではなく,注意(Warning)ですので,コンパイルはできています. お勧めできない古い時代のライブラリを使っている時に出ます.基本的には使わない方が良いのですが, 今回は説明のために用います.

なお,Date型を利用するときは,import java.util.Dateクラス宣言の前に必要になります.import文が必要な理由は, FAQ import文とは何かを参照してください.

実際に,入力して,どのような実行結果になるかを考えてから,動かしてみましょう. そして,予想との違いを考えてください.

import java.util.Date;

public class DateExample{
  void run(){
    // year は 1900年からの経過年数.
    // month は 0 から始まる.
    Date date1 = new Date(115, 8, 29, 9, 0);
    Date date2 = date1;
    System.out.printf("date1: %s%n", date1);
    System.out.printf("date2: %s%n", date2);

    // Year を +1 している.
    date1.setYear(date1.getYear() + 1);
    System.out.printf("date1: %s%n", date1);
    System.out.printf("date2: %s%n", date2);
  }
  public static void main(String[] args){
    DateExample example = new DateExample();
    example.run();
  }
}

Javaの変数はほぼ全てがポインタとなっています. ポインタとは,メモリ領域の特定の場所を指し示すだけの変数です. 上のプログラムの date2 = date1 という命令が実行されると, date2date1 の参照先が代入されたことを表します. その結果,date1date2 は同じ参照先の実体を指し示すようになり, 一方を変更すると(date1.setYear(...);), もう一方も変更されることになります. 下の図をクリックすると図が更新されます.何度かクリックして見て動作を確認しましょう.

値の一致性

基礎プログラミング演習で学習したものとは変数の扱いが異なる場合がありますので,注意してください. Java言語では,全ての変数は,メモリ上のどこかにある実体を参照しているものと思ってください.

参照の一致性

先のプログラムrun メソッドに次のコードを追加して, 実行結果を確認してみましょう.

void run(){
    Date date1 = new Date(115, 8, 29, 9, 0);
    Date date2 = date1;
    date1.setYear(date1.getYear() + 1);
    // 以下のプログラムを追加する.
    Date date3 = new Date(116, 8, 29, 9, 0);

    System.out.println(date1 == date2);
    System.out.println(date1 == date3);
    System.out.println(date2 == date3);
}

各変数が指し示す Date 型変数の値は全て,2016-09-29 9:00 になっているはずですが, なぜ truefalse という違いが現れるのでしょうか. 実は,== は参照先が同じであるかを判定する演算子であり,値が一致するか否かは判定されません. 以下の画像をクリックし,動作を確認してみましょう.

参照の一致性

値の一致性

では,2つの変数の値が一致するかどうかを判定するにはどうすれば良いのでしょうか. それには,Objects型のequalsメソッドを利用します. 先ほどと同じく,runメソッドに次の処理を追加し,実行結果を確認しましょう.

import java.util.Date;
import java.util.Objects;

public class DateExample{
    void run(){
        // ...
        // 次の処理を追加する.
        System.out.println(Objects.equals(date1, date2));
        System.out.println(Objects.equals(date1, date3));
        System.out.println(Objects.equals(date2, date3));
    }
    // ...
}

このようにすることで,値の一致性を確認できます. Javaで比較する時は,参照の一致か,値の一致か,どちらを判定したいのかを区別して比較しましょう.

なお,Objects型を利用するときは,クラス宣言の前に import 文が必要です. import java.util.Objects と書いてください.