「参照がない」状態の参照型変数
質問:参照型変数は常に参照をもっていなければいけませんか?
解答:いいえ。参照型変数に「null」という入れれば、「どのインスタンスも指してない」状態になります。
参照型変数は参照を持つことで「インスタンスを指す」状態になります。
これを、参照の代わりに「null」という値を入れることで、「どのインスタンスも指していない」状態にすることができます。
/**
* フィールド1つだけ持つシンプルなクラス。
* (SimpleClassと同じものです)
*/
class SimpleClass5
{
// int型変数のフィールドdataを定義します。
int data;
}
/**
* 実行用クラス。このクラスを実行してください。
*/
class NoRefRunner
{
public static void main( String[] args )
{
// SimpleClass5クラスの参照型変数を作ります。
SimpleClass5 ref;
// この状態で使うとコンパイルエラーになります。
// ref変数が初期化されていないためです。
// ref.data = 100;
// コンパイルエラー:
// NoRefRunner.java:24: 変数 ref は初期化されていない可能性があります。
// インスタンスを作ってref変数に参照を入れます。
ref = new SimpleClass5();
ref.data = 100;
// 「null」を入れると参照を持たない状態になります。
ref = null;
// この状態で使うとNullPointerException例外が投げられます。
try
{
ref.data = 100;
}
catch( NullPointerException e )
{
e.printStackTrace();
// 出力結果:
// java.lang.NullPointerException
// at NoRefRunner.main(NoRefRunner.java:37)
}
}
}
// NoRefRunner.java /** * フィールド1つだけ持つシンプルなクラス。 * (SimpleClassと同じものです) */ class SimpleClass5 { // int型変数のフィールドdataを定義します。 int data; } /** * 実行用クラス。このクラスを実行してください。 */ class NoRefRunner { public static void main( String[] args ) { // SimpleClass5クラスの参照型変数を作ります。 SimpleClass5 ref; // この状態で使うとコンパイルエラーになります。 // ref変数が初期化されていないためです。 // ref.data = 100; // コンパイルエラー: // NoRefRunner.java:24: 変数 ref は初期化されていない可能性があります。 // インスタンスを作ってref変数に参照を入れます。 ref = new SimpleClass5(); ref.data = 100; // 「null」を入れると参照を持たない状態になります。 ref = null; // この状態で使うとNullPointerException例外が投げられます。 try { ref.data = 100; } catch( NullPointerException e ) { e.printStackTrace(); // 出力結果: // java.lang.NullPointerException // at NoRefRunner.main(NoRefRunner.java:37) } } }
まず注意して欲しいのは、作っておいて何の値も入れていない参照型変数は使うことができないという点です。
SimpleClass5 ref;
// この状態で使うとコンパイルエラーになります。
// ref変数が初期化されていないためです。
ref.data = 100;
// コンパイルエラー:
// NoRefRunner.java:24: 変数 ref は初期化されていない可能性があります。
// SimpleClass5クラスの参照型変数を作ります。 SimpleClass5 ref; // この状態で使うとコンパイルエラーになります。 // ref変数が初期化されていないためです。 ref.data = 100; // コンパイルエラー: // NoRefRunner.java:24: 変数 ref は初期化されていない可能性があります。
変数を作り、その中に値を入れて使える状態にすることを「初期化」と言います。
変数は、作ったばかりの時には「中に何が入っているか分からない状態」なので、このままでは使えません。値を入れることで、初めて使える状態になります。その使える状態にすることを「初期化」と言います。
参照型変数の場合、参照や、後述する「null」を入れることで初期化できます。このいずれも入れてない状態では、参照型変数は使えません。
ちなみに、別にこれは参照型変数に限らず、通常の変数ならどれも当てはまります。
System.out.println( i );
// コンパイルエラー:
// NoRefRunner.java:12: 変数 i は初期化されていない可能性があります。
int i; System.out.println( i ); // コンパイルエラー: // NoRefRunner.java:12: 変数 i は初期化されていない可能性があります。
なので、これは参照型変数に限らないということを覚えておいてください。
ちなみにフィールドの場合には、自動的に初期化されます。int型のフィールドなら0、参照型のフィールドならnullが自動的に入れられます。そのためこの問題は起きません。
「null」という値
話を戻して。
参照型変数には参照の他に、「null(ヌル)」という値を入れることができます。
次のプログラムは、参照型変数にnullという値を入れている箇所です。
ref = null;
// 「null」を入れると参照を持たない状態になります。 ref = null;
「null」は参照型変数にのみ入れることができる定数値です。
この値を参照型変数に入れると、その参照型変数は「どのインスタンスも指していない」という状態になります。
この状態を利用することで、参照型変数に「データを持っていない」という状態を作ることができます。
たとえば、画面から数値を入力してもらい、その情報をクラスのフィールドに持つような場合、nullを使うことで「未入力」を表すことができます。これがint型の変数だと、未入力でも何かの値(0とか-1とか)を入れなければいけないため、ちょっと危なくなってしまいます。参照型変数とnullを使うことで「データそのものがない」という表現が可能になるわけです。
参照型変数にnullを入れると、当然その参照型変数はどのインスタンスとも結びついていません。
この状態で「参照型変数.フィールド」のようにインスタンスを操作しようとすると、インスタンスが存在しないので「NullPointerException」という例外が投げられます。
try
{
ref.data = 100;
}
catch( NullPointerException e )
{
e.printStackTrace();
// 出力結果:
// java.lang.NullPointerException
// at NoRefRunner.main(NoRefRunner.java:37)
}
// この状態で使うとNullPointerException例外が投げられます。 try { ref.data = 100; } catch( NullPointerException e ) { e.printStackTrace(); // 出力結果: // java.lang.NullPointerException // at NoRefRunner.main(NoRefRunner.java:37) }
ref変数にはnullが入っているため、「ref.data」は「どのインスタンスのdataフィールド」を使おうとしているのか分かりません。そのためJavaシステムが困ってしまい、NullPointerExceptionという例外が投げられます(例外については市販の本等をご参考ください)。
nullが入っているかどうかは「if( ref == null )」のようにifで調べることができますが、nullを多用するとプログラムが複雑になりやすいので注意しましょう。