「static」でクラス共有の変数・メソッドを使いこなせ!:【改訂版】Eclipseではじめるプログラミング(11)(2/3 ページ)
これからプログラミングを学習したい方、Javaは難しそうでとっつきづらいという方のためのJavaプログラミング超入門連載です。最新のEclipse 3.4とJava 6を使い大幅に情報量を増やした、連載「Eclipseではじめるプログラミング」の改訂版となります
実体が1つだけの「クラス変数」「staticフィールド」とは
クラス変数を宣言するためには、変数の前に「static」を付けます。こうすることにより、staticがついたフィールドは、クラスのすべてのインスタンスに共有され、その実体が1つだけのものとなります。宣言の仕方から、クラス変数は「staticフィールド」とも呼ばれます。
「インスタンスフィールド」との違い
なお、インスタンスごとに保持される通常のフィールドは、「インスタンスフィールド」といいます。インスタンスフィールドは、インスタンスごとに値が保持されますが、クラス変数はクラス単位で値が保持されます。この違いをよく理解してください。
サンプルをクラス変数で改良
では、クラス変数を使った方式に先ほどの例を変更してみましょう。先ほどと同様にして、パッケージを丸ごとコピーして張り付け、sample11.app3パッケージを作成します。次に、AppInfoクラスの各フィールドをクラス変数としてみましょう。各フィールドにstaticを付けて、コンストラクタは削除します。具体的には次のようになります。
package sample11.app3; class AppInfo { static String appName; // アプリケーション名 static int version; // バージョン static int mode; // 実行モード 0:パフォーマンス優先、1:省電力 }
パッケージsample11.app3を作成して、そこへAppInfoクラスを作成をしています。なお、ここで紹介するプログラムは、クラス変数について説明するためのものなので、フィールドへのアクセス制限などは考慮していません。アクセス制御については本連載で別途紹介しますので、その点については、ご了承ください。
「クラス名.フィールド名」で指定
Appクラス、Messageクラス、Calcクラスは次のようになります。クラス変数は、クラスで共有されるものなので、「クラス名.フィールド名」という指定方法でアクセスができます。AppInfoのクラス変数の初期化をAppクラスで行っていますが、そのコードを見ると理解できるはずです。
クラス名.フィールド名 【例】AppInfo.appName
また、MessageクラスやCalcクラスでは、AppInfoのインスタンスを参照する必要がなくなるため、コンストラクタやメソッドのパラメータを変更しています。このAppプログラムの実行結果はsample11.app2パッケージのAppクラスのものと同じです。
package sample11.app3; class App { public static void main(String[] args) { // AppInfoのクラス変数を初期化 AppInfo.appName = "サンプルアプリ"; AppInfo.version = 1; AppInfo.mode = 0; // プログラムの実行 Message message = new Message(); message.print("Hello"); Calc calc = new Calc(); calc.exec(); } }
package sample11.app3; class Message { void print(String message) { System.out.println("[" + AppInfo.appName + "]"); System.out.println(message); } }
package sample11.app3; class Calc { void exec() { System.out.println("[" + AppInfo.appName + "]"); System.out.println(1+2); } }
どうでしょう、ずいぶんすっきりとしたプログラムになったと思いませんか。実行結果は同じなので省略します。
インスタンスを使ってクラス変数へアクセスするには?
ちなみに、AppInfoクラスのインスタンスを生成することもできますし、そのインスタンスを使ってクラス変数へアクセスすることもできます。ただし、コードを読んだときに、クラス変数を使っているということが分かるように、通常は「AppInfo.appName」のように、クラス名を使ったアクセスをします。試しに、sample11.app4.Appクラスを作ってみましょう。
まずは、sample11.app4パッケージを作成します。次に、sample11.app3.AppInfoクラスをコピーして、sample11.app4パッケージへ張り付けます。パッケージ名が違うだけで、処理内容は同じなので、sample11.app4.AppInfoクラスをそのまま作成しても構いません。その後に、次のsample11.app4.Appクラスを作成します。ちなみに、Eclipseでは、インスタンス経由でクラス変数へアクセスをすると、警告が出て、黄色の波線が付きます。
package sample11.app4; class App { public static void main(String[] args) { // インスタンス経由でもアクセスできるが、基本的に使わない AppInfo info = new AppInfo(); info.appName = "サンプルアプリ info"; System.out.println(info.appName); // クラス変数へのアクセスが分かる AppInfo.appName = "サンプルアプリ AppInfo"; System.out.println(AppInfo.appName); } }
サンプルアプリ info サンプルアプリ AppInfo
クラス変数の使いどころは?
このほかにも、クラス変数を用意する場面はあります。例えば、クラスのインスタンス数をカウントしておきたい場合です。DB接続をするプログラムでは、DB接続用のクラスを用意し、そのインスタンスをあらかじめ複数用意しておくことにより、DB接続時のパフォーマンスを良くすることがあります。その際に生成されたインスタンス数をカウントする必要があるのです。
また、メモリ使用量を抑制するために、生成可能なインスタンス数を制限したクラスを作成したい場合もあります。こういった場面で、クラス変数を利用します。
Java標準でクラス変数を持つ「ラッパークラス」
Javaでクラス変数を持つクラスとしては、基本データ型をラップするクラスや、Systemクラスなどがあります。ほかにもたくさんありますが、これらは基本的なクラスですから、ちょっと見てみましょう。
ラッパークラスでクラス変数を使ってみよう
基本データ型をラップするクラス(「ラッパークラス」)とは、intに対応するIntegerクラスや、longに対応するLongクラスのことです。これらのクラスでは、値の範囲をクラス変数で定めていて、MIN_VALUEからMAX_VALUEの間の値を取得できます。
また、その型の値を表現するために使用されているビット数はSIZEを参照することで分かります。これらの値をIntegerとLongについて表示してみましょう。なお、booleanをラップするBoolean型では、これらのクラス変数は定義されていません。
package sample11.app5; class Sample { public static void main(String[] args) { // ラッパークラスのクラス変数の例 System.out.println(Integer.MIN_VALUE); System.out.println(Integer.MAX_VALUE); System.out.println(Integer.SIZE); System.out.println(Long.MIN_VALUE); System.out.println(Long.MAX_VALUE); System.out.println(Long.SIZE); } }
-2147483648 2147483647 32 -9223372036854775808 9223372036854775807 64
System.outはクラス変数だった!
これまでもよく利用してきたSystem.outは、Systemクラスのクラス変数になります。このクラス変数はアプリケーションの標準出力を表現していて、Eclipseではコンソールウィンドウが対応しています。コマンドプロンプトでJavaプログラムを実行すると、実行しているコマンドプロンプトが対応します。
コラム 「finalフィールドと定数変数」
ここで、APIリファレンスを参照すると、Integer.MIN_VALUEやInteger.MAX_VALUEには「final」というキーワードも使われていることが分かります。これは、「初期化された後は変更されないフィールドだ」ということを意味します。
Integer.MIN_VALUEはInteger型の最小値ですから、言語仕様で決められています。そのため、プログラム中で変更されることはありません。こういった値については、「finalフィールド」として宣言します。
このように、プログラム中では、ある値に名前を付けて利用することはよくあります。ある値に対して名前を付けるために変数を利用しているため、Integer.MIN_VALUEのようなものは「定数変数」といわれます。定数変数は、次の項目のどちらかを満たしている必要があります。
- コンパイル時に値が決定される定数式で初期化された基本データ型のfinalフィールド
- コンパイル時に値が決定されるnullでない定数式で初期化されたString型のfinalフィールド
クラス共有のメソッド「クラスメソッド」「staticメソッド」とは
クラス変数と同じように、クラス共有のメソッドというものも使いたい場面があります。ここでも、キーワードstaticを使います。冒頭にstaticを付けて宣言されたメソッドは、「クラスメソッド」もしくは「staticメソッド」と呼ばれ、「クラス名.メソッド名」でほかのクラスから呼び出すことができます。インスタンスを使って呼び出す通常のメソッドは、「インスタンスメソッド」と呼びます。
クラス名.メソッド名 【例】Integer.parseInt("-123");
クラスメソッドの使いどころ
例えば、文字列を暗号化する機能は、いろいろなクラスで利用することがあり得ます。これを、それぞれのクラスでインスタンスメソッドとして実装することを考えるよりも、クラスを1つ用意して、そのクラスメソッドとして実装した方がいい場合があります。アプリケーションの動作を記録するログを出力する機能なども、いろいろなクラスから利用できるように、ログ専用クラスを用意して、そのクラスメソッドとして実装したいことがあるでしょう。
このように、複数のクラスから利用できる便利なメソッドを提供したい場合に、クラスメソッドは役に立ちます。
Java標準でも使われているクラスメソッド
Javaの標準クラスにも、クラスメソッドを持つクラスはたくさんありますが、ここでは例として、IntegerクラスとSystemクラスに定義されているクラスメソッドをいくつか使ってみましょう。また、簡単なクラスメソッドも独自に用意してみましょう。
具体例として、sample11.app6.Sampleクラスを作成してみましょう。クラスメソッドとしては、単にSystem.out.printlnメソッドを呼び出すprintメソッドを用意します。
実は、これまでも使ってきたmainメソッドも、staticが付いていましたから、クラスメソッドです。クラスメソッドはクラスメソッドからそのまま呼び出せます。ですから、mainメソッド内では、printメソッドをそのまま呼び出しています。
package sample11.app6; class Sample { static void print(String s) { System.out.println(s); } public static void main(String[] args) { // 文字列 "-123" を解析して int値へ int n = Integer.parseInt("-123"); print(n + ""); // 16進数 0x20(10進数 32)をInteger型の値へ Integer v = Integer.decode("0x20"); print(v + ""); // Javaバージョン print(System.getProperty("java.version")); // オペレーティングシステム名 print(System.getProperty("os.name")); // UTC 1970年1月1日午前0時から経過したミリ秒 print(System.currentTimeMillis() + ""); } }
mainメソッド処理内の具体的な処理についてはコメントを入れましたが、簡単に説明をします。
Javaのラッパクラスでは、文字列から値へ変換する便利なメソッドが定義されています。
ここでは、「-123」という文字列をint値へ、「0x20」という文字列をInteger型の値へ変換しています。decodeメソッドでは、「0x」で始まる文字列は16進数と見なして変換をします。このため、Integer型の値は32となります。同様にして、Long型にはparseLongメソッドがあり、「Long.parseLong("123");」のように使うことができます。
Systemクラスでは、getPropertyメソッドというものがあり、「java.version」「os.name」といったキーを渡すことにより、対応するシステムの値を取得できます。ほかにもいろいろなキーがあります。APIリファレンスのSystemクラスのgetPropertiesメソッドの説明に記載されていますから、確認をしてみてください。
また、現在時刻の計算によく使うcurrentTimeMillisメソッドというものもあります。「協定世界時のUTC 1970年1月1日午前0時から経過したミリ秒」を返します。ちなみに、ナノ秒を返すnanoTimeメソッドというクラスメソッドもあります。
なお、printメソッドを呼び出す際に、「print(n + "");」のようにパラメータへ「+ ""」を追加しています。これにより、例えばint型のnをString型へ変換できます。printメソッドの仮パラメータの型がStringなので、それに合わせた型にして値を渡す必要があるので、こういった変換をしています。
-123 32 1.6.0_11 Windows Vista 1253610780015
次ページでは、さらに「初期化ブロック」「static初期化」「staticインポート」について説明します。
コラム 「クラスメソッドと同じ機能をインスタンスメソッドで実装するには」
ところで、「sample11.app6.Sampleクラスと同じ結果を出力するプログラムを、printメソッドをインスタンスメソッドとして実装して実現する」という課題があった場合はどうすればいいでしょうか。そんなときは、次のようなSampleクラスを作成し、そのインスタンスを生成して、インスタンスメソッドを使うようにします。
package sample11.app7; class Sample { void print(String s) { System.out.println(s); } public static void main(String[] args) { Sample app = new Sample(); // 略 app.print(System.currentTimeMillis() + ""); } }
ちなみに、次のように、execメソッドを用意して実装するという方法もあります。こうすると、sample11.app7.Sampleでは、「app.print(n);」のようにapp変数を使って呼び出していたところを、「print(n);」のようにしてインスタンスメソッドを呼び出せるようになります。
package sample11.app8; class Sample { void exec() { int n = Integer.parseInt("-123"); // 略 print(System.currentTimeMillis() + ""); } void print(String s) { System.out.println(s); } public static void main(String[] args) { Sample app = new Sample(); app.exec(); } }
Copyright © ITmedia, Inc. All Rights Reserved.