知らないことは、それを知るまでは絶対にできない、そして、知るまでには多大な労力が必要となる、と思う今日この頃です。
かの有名な芸術家のミケランジェロは、
「私が残念に思うのは、やっと何でも上手く表現出来そうになったなぁ、と思うときに死なねばならぬことだ。」
と言ったそうな。
何事においても技術を身に着けるということは、時間がかかることを避けて通れないことなのかもしれないですね(涙)。
Oracle12c のデータベース・キャラクタ・セットの変更を先延ばしし続けているわけですが、今回は、Javaのイニシャライザの話でございます。
というわけで、レッツトライ。
イニシャライザ(initializer:初期化子)とは?
そも、イニシャライザって何ですか?
イニシャライザ(initializer)とは、クラスが初期化されたときやインスタンスが生成されたときに、その直後に変数を初期化します。何か特別な計算をした値を初期化の直後に代入したいとき等に利用します。イニシャライザ(initializer)は、クラス変数ならクラスが初期化されるときに実行され、インスタンス変数ならインスタンスが生成されたときに実行します。
ということのようです。Javaのイニシャライザ(initializer:初期化子)には、
- スタティックイニシャライザ(Static Initializer)
- インスタンスイニシャライザ(Instance Initializer)
の2つがあるそうです。
他サイト様を見てみますと、
⇧ インスタンスイニシャライザは、あんまり存在感がない感じですかね?
かく言う私も、まったく知りませんでした。
スタティックイニシャライザ(Static Initializer)
まずは、スタティックイニシャライザ(Static Initializer)から。
スタティックイニシャライザ(Static Initializer)は他にも「クラス初期化子」・「静的初期化子」「静的ブロック」・「staticブロック」とも呼ばれています。
⇧ 上記サイト様によりますと、いろんな呼ばれ方をするようです...認識の違いが生まれる原因になりますね。
スタティックイニシャライザは、どのタイミングで実行されるのか?
⇧ 上記サイト様が詳しいです。
クラスローダー(Class Loader)が関係してくるようです。
⇧ 上記サイト様の図では、載ってないのですが、Javaのプログラムは、
クラス(拡張子が「.java」)を用意しておくと、JVMのJavaコンパイラーによって、クラスファイル(拡張子が「.class」、中間ファイルとも呼ばれ、OSに依存しないバイトコードの状態)に変換されるわけですが、Class Loader SubSystemは、そのクラスファイルを読み込むシステムであると。
⇧ 上記サイト様の図によりますと、クラスファイルが用意されている前提で、まずは、Class Loader Subsystem というものが実行されるようですが、
- Loading
- Linking
- initialization
の順番で実行されるようです。
◇ loading
Class Loaderには、種類がいろいろあるようです。Bootstrap Class Loader が、一番の親にあたり、どのClass Loaderも基本的にはまず、Bootstrap Class Loaderに処理を委譲し、その後、必要に応じて対象のクラスを検索し、解決するってことのようです。
Class Loaderでクラスを見つけられない場合、NoClassDefFoundError というエラーが起こるようです。
ClassNotFoundExceptionとNoClassDefFoundErrorの違いにつきましては、
⇧ 上記サイト様が詳しいです。
◇ Linking
loadingでクラスファイルが見つかった場合、Linkingで、クラスファイルの妥当性を検証(verify)し、検証でNGが出なければ、クラスのstaticフィールド(静的フィールド)にデフォルト値を設定(Prepare)し、シンボリック参照の解決(実行時コンスタント・プール内のシンボリック参照が、実際に必要とされる型の妥当なクラスを指し示していることをチェック)されると参照先のクラスのロードが実行(Reslove)。
◇ Initialization
ここでようやく、staticフィールドの初期化や、スタティックイニシャライザが実行されるようです。つまり、クラスが、
public class testStatic { private static String TEST01; private static String TEST02 = "料理の鉄人"; private static int SIGN01; private static int SIGN02 = 777;
// スタティックイニシャライザ static { System.out.println(TEST01 + "\n" + TEST02 + "\n" + SIGN01 + "\n" + SIGN02 + "\n"); TEST02 = "Mr.POPO"; SIGN02 = 666; System.out.println(TEST01 + "\n" + TEST02 + "\n" + SIGN01 + "\n" + SIGN02 + "\n"); } public static void main(String[] args) { // TODO 自動生成されたメソッド・スタブ } }
というような感じであった場合、
という感じで、まず、staticフィールドの初期化が実行された後に、スタティックイニシャライザで、さらに初期化している感じですね。
ちなみに、static finalなフィールドは事前に初期化(何らかの値を代入)してないと怒られます。
2018年6月23日(土) ⇩ 追記ここから
ですが、static初期化子の中で初期化してしまえば、変数宣言の時に初期化していなくてもOKのようです。
2018年6月23日(土)⇧ 追記ここまで
ちなみに、配列のような参照型の場合、finalなフィールドでも、配列の要素には再代入できてしまう...
public class TestStatic { private static String TEST01; private static String TEST02 = "料理の鉄人"; private static int SIGN01; private static int SIGN02 = 777; private static final String[] TEST03 = {"ケセラセラ"};
// スタティックイニシャライザ static { System.out.println(TEST01 + "\n" + TEST02 + "\n" + SIGN01 + "\n" + SIGN02 + "\n" + TEST03[0] + "\n"); TEST02 = "Mr.POPO"; SIGN02 = 666; TEST03[0] = "なんくるないさ~、ヤママヤ~"; System.out.println(TEST01 + "\n" + TEST02 + "\n" + SIGN01 + "\n" + SIGN02 + "\n" + TEST03[0]); } public static void main(String[] args) { // TODO 自動生成されたメソッド・スタブ } }
そして、皆様ご存知のコンストラクタはクラスをインスタンス化するときに実行されるため、
public class TestStatic { private static String TEST01; private static String TEST02 = "料理の鉄人"; private static int SIGN01; private static int SIGN02 = 777; private static final String[] TEST03 = {"ケセラセラ"}; // コンストラクタ TestStatic() { System.out.println("コンストラクタ~"); }
// スタティックイニシャライザ static { System.out.println(TEST01 + "\n" + TEST02 + "\n" + SIGN01 + "\n" + SIGN02 + "\n" + TEST03[0] + "\n"); TEST02 = "Mr.POPO"; SIGN02 = 666; TEST03[0] = "なんくるないさ~、ヤママヤ~"; System.out.println(TEST01 + "\n" + TEST02 + "\n" + SIGN01 + "\n" + SIGN02 + "\n" + TEST03[0]); } public static void main(String[] args) { // TODO 自動生成されたメソッド・スタブ } }
とかやっても、クラスをインスタンス化してないので、ガン無視されます。
クラスをインスタンス化してあげると、
public class TestStatic { private static String TEST01; private static String TEST02 = "料理の鉄人"; private static int SIGN01; private static int SIGN02 = 777; private static final String[] TEST03 = {"ケセラセラ"}; // コンストラクタ TestStatic() { System.out.println("コンストラクタ~"); }
// スタティックイニシャライザ static { System.out.println(TEST01 + "\n" + TEST02 + "\n" + SIGN01 + "\n" + SIGN02 + "\n" + TEST03[0] + "\n"); TEST02 = "Mr.POPO"; SIGN02 = 666; TEST03[0] = "なんくるないさ~、ヤママヤ~"; System.out.println(TEST01 + "\n" + TEST02 + "\n" + SIGN01 + "\n" + SIGN02 + "\n" + TEST03[0]); } public static void main(String[] args) { // クラスのインスタンス化 new TestStatic(); } }
で、話がむちゃくちゃ脱線したのですが、Class Loader Subsystemでの処理が終わった段階で、クラス関連の情報は、JVM上のメモリ領域のMetaspace領域(Java8以前はPermanent領域)に格納されるようです。
といった感じで、スタティックイニシャライザはこんな感じのタイミングで実行されるようです。使いどころは、他サイト様を参考にしていただいて、次はインスタンスイニシャライザについて。
インスタンスイニシャライザ(Instance Initializer)
インスタンスイニシャライザ(Instance Initializer) は、
インスタンスフィールドの場合、通常はコンストラクタで初期化できます。通常の初期化なら別に問題ないのですが、例えば、コンストラクタをオーバーロード(多重定義)した場合等、コンストラクタ内で同じ処理を行わなければならないときにインスタンスイニシャライザ(Instance Initializer)を利用すると便利です。
⇧ 上記サイトによりますと、
- コンストラクタが複数存在する場合
- 匿名クラス(無名クラス)のように、コンストラクタが無い場合
などのケースにおいて、効果を発揮するようです。
上記のケースでの実装ではないですが、インスタンスイニシャライザを試してみたいと思います。
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public class TestInsutance { // Enumクラスを用意 public enum STATUS { stay("0", "通常"), deal("1", "処理中"), done("2", "確定"), stop("3", "異常"); private String code; private String statusName; // Enumクラスのコンストラクタ private STATUS(String code, String statusName) { this.code = code; this.statusName = statusName; } public String getCode() { return code; } public String statusName() { return statusName; } } private final List<Object[]> TEST01 = new ArrayList<>(); // インスタンスイニシャライザ { // [0]=処理パターンID, [1]=現在の状態, [2]=次の状態, [3]=エラー有無 TEST01.add(new Object[]{"0001", STATUS.stay.getCode(), STATUS.deal.getCode(), " "}); TEST01.add(new Object[]{"0002", STATUS.deal.getCode(), STATUS.done.getCode(), " "}); TEST01.add(new Object[]{"0003", STATUS.done.getCode(), STATUS.stay.getCode(), " "}); TEST01.add(new Object[]{"0004", STATUS.stop.getCode(), STATUS.stay.getCode(), "error"}); }; public static void main(String[] args) { TestInsutance testInsutance = new TestInsutance(); List<Object[]> test = testInsutance.TEST01; // 入力値 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); System.out.println("処理パターンIDを入力してください"); List<String> dealID = new ArrayList<>(); try { dealID.add(in.readLine()); } catch (IOException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); } String nextJob = null; String error = null; System.out.println(test); // 入力値と比較 if(test != null && !test.isEmpty()) { for(Object[] obj : test) { // 次の状態が確定した、またはエラーが起こった場合 if(nextJob != null || error != null) { break; // 処理を抜ける } for(int index = 0; index < obj.length; index++) { // 処理パターンIDのチェック if(dealID != null && obj[index].equals(dealID.get(0))) { // エラー有無のチェック if(obj[obj.length -1].equals("error")) { System.out.println("異常が発生しました。管理者に問い合わせてください。"); error = ""; break; } // 次の状態を保存 nextJob = String.valueOf(obj[index]); break; } } } // エラーはないけど、処理パターンIDに一致しない場合 if(nextJob == null && error == null) { System.out.println("次の状態に移れません。管理者に問い合わせてください。"); } } // 次の状態に遷移できる場合 if(nextJob != null) { System.out.println("次の状態に遷移しました。"); } } }
という感じで、 イニシャライザが使われているソースに遭遇する日があるかもしれないというお話でした。
特に、インスタンスイニシャライザは、業務系のシステムでは出現率が高くないかもですね。匿名クラス(無名クラス)とかの利用率が高そうなAndroidとかの開発だと結構出てくるんですかね?
それにしても、インスタンスイニシャライザ、{ } で囲んでるだけって分かりにくい....。
そして、久々に、コードに触れたら、まったくまとまりがない感じに(涙)。やっぱり優秀な技術者の方のコードから学ばないと駄目ですね...OJTに憧れますな~。
今回はこのへんで。