Developer

【はじめてのJava】staticイニシャライザ(説明)【オブジェクトとクラス編】
2021.09.30
Lv1

【はじめてのJava】staticイニシャライザ(説明)【オブジェクトとクラス編】

はじめてのJava

このシリーズでは、初めてJavaやプログラミングを勉強する方向けに、Javaによるプログラミングの基礎を説明していきます。
目標レベルは、Javaの資格試験の一つである「Oracle Certified Java Programmer, Silver」(通称Java Silver)に合格できる程度の知識の習得です。
はじめてJavaやプログラムに触れる方にもできるだけわかりやすい解説を心がけていきます。


オブジェクトとクラス編

オブジェクトとクラス編では、Javaを扱う上で非常によく出てくる「オブジェクト」や「クラス」について扱っていきます。

以前の記事では「イニシャライザ」について扱いました。

今回は「staticイニシャライザ」について扱います。


目次


staticイニシャライザ

 

 staticイニシャライザ とはクラスが読み込まれた際に実行する処理を定義する箇所です。

詳しく見ていきましょう。


staticイニシャライザとは

 staticイニシャライザ とはクラスが読み込まれた際に実行する処理を定義する箇所です。

staticイニシャライザには主にstatic変数の初期値を1度だけ設定したい場合に利用します。

具体例を見てみましょう。

例えばスマートフォンを表すSmartPhoneクラスを考えてみます。SmartPhoneクラスには、電話番号を表す変数phoneNumberと通信の残り容量を表すStatic変数capacityがあります。

通信の残り容量であるcapacityは、誰かが通信するたびに減っていき、0になったら誰も通信できなくなる仕様です。全インスタンスで共用している変数ですのでstatic変数としましょう。図にすると次の通りです。

ソースコードにすると以下の通りです。

class SmartPhone{
  String phoneNumber; //電話番号
  static double capacity; //データ通信の容量
}

このSmartPhoneクラスをインスタンス化するためのコンストラクタを考えます。

電話番号のみを指定すればよい場合は次の通りです。

class SmartPhone{
  String phoneNumber; //電話番号
  static double capacity; //データ通信の容量

  SmartPhone(String phoneNumber){
    this.phoneNumber = phoneNumber;
  }
}

しかしこれでは、最初の1台をインスタンス化した際に通信容量が0になってしまいます。

では、最初の1台のインスタンス化の際に、以下の図のように初期容量を30.0で設定したい場合はどうすればよいでしょうか。

設定の方法としては以下の4つが考えられます。

  • メソッド内で直接設定する
  • コンストラクタで設定する
  • イニシャライザで設定する
  • staticイニシャライザで設定する

順にみていきましょう。


メソッド内で直接設定する場合

まずはメソッド内で直接static変数の値を変更する場合を考えます。

この方法を使う場合、ソースコードが長くなるとどこで初期値を設定しているのか分かりにくくなるという欠点があります。
 分かりにくいコード=バグが入り込みやすいコード ですので、この点には注意が必要になります。

また、副次的な問題点として、static変数をほかのクラスから直接変更可能なソースコードになってしまうという問題点もあります。
(ここでは詳しくは割愛しますが、アクセス修飾子やカプセル化といった言葉が密接に関係してきます。)

どちらもプログラムの管理やメンテナンスにかかわる点ですので、この方法を取る場合は設計資料などでかなり注意深く管理する必要があります。ソースコード作成時も、想定外の箇所で誤って変更しないように常に神経を張り巡らせる必要があります。もし注意を怠ると、後々プログラムを改修する際にかなり苦労することになります。


コンストラクタで設定する場合

次に、コンストラクタで設定した場合にどうなるか考えてみます。

コンストラクタで設定する場合は次のようになります。

class SmartPhone{
  String phoneNumber; //電話番号
  static double capacity; //データ通信の容量

  SmartPhone(String phoneNumber){
    this.phoneNumber = phoneNumber;
    capacity = 30.0; //ここを追加
  }
}

このようにすると、次の図のようにインスタンス化の際にcapacityの初期容量が30.0で設定されます。

確認用のソースコードは次の通りです。

public class CheckPhone{
  public static void main(String[] args){
    //1台目
    SmartPhone sp1 = new SmartPhone("090-XXXX-XXXX"); //インスタンス化
    //↓sp1の容量を確認。本来はsp1.capacityではなくSmartPhone.capacityの方が良い。
    System.out.println(sp1.phoneNumber + " : " + sp1.capacity); 
  }
}

インスタンス化したsp1の通信容量を5だけ使ったとしましょう。

プログラムとしては次の通りです。
(プログラム中に記載がありますが、本来capacityはSmartPhone.capacityという呼び出しになるべきです。ここでは説明の都合でわざとsp1.capacityとしています。)

public class CheckPhone{
  public static void main(String[] args){
    //1台目
    SmartPhone sp1 = new SmartPhone("090-XXXX-XXXX"); //インスタンス化
    //↓sp1の容量を確認。本来はsp1.capacityではなくSmartPhone.capacityの方が良い。
    System.out.println(sp1.phoneNumber + " : " + sp1.capacity); 

    sp1.capacity -= 5.0; //通信容量を5だけ使ったと想定
    System.out.println(sp1.phoneNumber + " : " + sp1.capacity); //通信容量は25.0
  }
}

一見するとこれで問題ないように見えます。しかし、今回のSmartPhoneの仕様を考えると、ここには大きな問題点があります。


今回の場合の問題点

先ほどの例では一見問題が無いように見えました。

そこで、次の図のように2台目をインスタンス化した際のことを考えてみましょう。

一見すると問題ないように感じるかもしれません。しかし、この方法ではインスタンス化のたびにcapacityが30.0で再設定されてしまいます

新しいSmartPhoneをインスタンス化し続ければ無制限に通信ができることになります。

インスタンスが生成されるたびに値が上書きされてOKであれば問題ありません。実際にプログラムを作成する場合、そのような動作をさせたい場合もあります。

しかし、今回のSmartPhoneは、「誰かが通信するたびに減っていき、0になったら誰も通信できなくなる」という仕様です。この仕様と併せて考えると、インスタンス化のたびにcapacityが30.0で再設定されてしまっては問題があります。


ソースコード

動作確認用のソースコードは以下の通りです。

SmartPhone.java

class SmartPhone{
  String phoneNumber; //電話番号
  static double capacity; //データ通信の容量

  SmartPhone(String phoneNumber){
    this.phoneNumber = phoneNumber;
    capacity = 30.0; //ここを追加
  }
}

CheckPhone.java

public class CheckPhone{
  public static void main(String[] args){
    //1台目
    SmartPhone sp1 = new SmartPhone("090-XXXX-XXXX"); //インスタンス化
    //↓sp1の容量を確認。
    System.out.println(sp1.phoneNumber + " : " + sp1.capacity); 

    sp1.capacity -= 5.0; //通信容量を5だけ使ったと想定
    System.out.println(sp1.phoneNumber + " : " + sp1.capacity); //通信容量は25.0

    //2台目
    SmartPhone sp2 = new SmartPhone("080-YYYY-YYYY"); //インスタンス化
    //↓sp2の容量を確認。
    System.out.println(sp2.phoneNumber + " : " + sp2.capacity); //通信容量が30.0に戻っている 

    //↓sp1の容量を再確認
    System.out.println(sp1.phoneNumber + " : " + sp1.capacity); //通信容量が30.0に戻っている
  }
}

実行した様子は次の通りです。


イニシャライザで設定する場合

今回のSmartPhoneクラスでは、イニシャライザを使った場合も、コンストラクタを使った場合と同様の理由からしようと異なる動作となり不適です。


staticイニシャライザで設定する場合

前述の通り、staticイニシャライザはクラスが読み込まれたときに動作します。

JVMでは、クラスファイルは必ず利用前に読み込みます。その読み込みは通常は クラスを最初に利用する前に 行われ、以降は行われません。

つまり、staticイニシャライザは最初にクラスを利用する前のどこかで1度だけ実行されるということです。

今回のように最初に1度だけstatic変数を初期化したいという場合はこのstaticイニシャライザを使うと便利です。

詳しい書き方は次回の記事で紹介します。


まとめ

staticイニシャライザはクラスが読み込まれた際に動作する
static変数の初期化にはstaticイニシャライザを用いる


次回

次回はstaticイニシャライザの書き方を内容を扱います。


はじめてのJavaシリーズの目次はこちら
オブジェクトとクラス編はこちら


java 11 の練習問題一覧はこちら
はじめてのJavaシリーズの練習問題一覧はこちら