変数

宣言場所

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

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

実体

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

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 と書いてください.

DateExample.java

何も参照していない値null

Java言語における変数は基本的に全て参照であり,メモリ上のどこを指し示しているかを表しています. では,何も指し示していない場合,すなわち,何も代入されていない場合,どのように扱われるのでしょうか. この場合,nullという値が代入されていることになります.

nullは何も参照していない値を表しています. そして,nullが代入されている変数に対して,メソッド呼び出しやフィールドを参照しようとすると NullPointerExceptionという実行時エラーが発生します.

例題

例えば,次のNullPointerExceptionDemoで確認してみましょう.

public class NullPointerExceptionDemo {
    void run() {
        String emptyString  = "";
        String stringString = "string";
        String nullString   = null;

        System.out.println(emptyString.toString());  // => 空文字が出力される.
        System.out.println(stringString.toString()); // => stringという文字列が出力される.
        System.out.println(nullString.toString());   // => NullPointerException
    }

    public static void main(String[] args) {
        NullPointerExceptionDemo demo = new NullPointerExceptionDemo();
        demo.run();
    }
}

実行結果(実行時のエラーメッセージの読み方)

このプログラムを実行すると,次のような出力が得られます(行頭の番号は説明のために追加したものであり,出力されません).

  1: 
  2: string
  3: Exception in thread "main" java.lang.NullPointerException
  4: 	at NullPointerExceptionDemo.run(NullPointerExceptionDemo.java:9)
  5: 	at NullPointerExceptionDemo.main(NullPointerExceptionDemo.java:14)

出力結果の1行目がプログラムの7行目により出力された空文字列(長さ0の文字列)です. そして,出力結果の2行目が,"string"という文字列が出力されています. 出力結果の3行目以降が NullPointerException という実行時エラーの情報です. 3行目が,どのようなエラーが発生したかを表しています(エラーの種類). 続く4行目以降で,どのメソッドを実行している時に,そのエラーが起きたのかを示しています(エラーの発生場所). ここでは,NullPointerExceptionDemo.javaの9行目(runメソッド内)で発生しました(実行結果の4行目). そして,そのrunメソッドはNullPointerExceptionDemo.javaの14行目(mainメソッド内)で呼び出されました,のように読みます.

なお,Java でクラス名やメソッド名などはキャメルケースで書かれます. つまりNullPointerExceptionは null pointer exception(ヌル・ポインタ・エクスセプション)と読み,null参照の例外という意味です.