クラス及びインスタンスの初期化

似ているけれども、同一ではない個々のインスタンスを作りだすのが初期化処理です。
初期化はコンストラクタで行うのが一般的ですが、初期化はそれだけではありません

段階的に行われる初期化

1.
クラスロード時にスタティックイニシャライザが実行され、クラス変数(static変数)の初期化が行われる。
2.
クラスのフィールドで宣言した変数を既定値で初期化(初期値を代入)する。
3.
オブジェクトの生成時にインスタンスイニシャライザが実行される。
4.
次にコンストラクタが実行され、引数などによる初期化処理が行われる。

スタティックイニシャライザ
クラスのロード時、クラス変数を初期化するために使用します。
クラス変数はインスタンス化しなくても存在し、使える変数ですから初期化が必要です。通常は初期値の代入で行われますが大量の要素を初期化しなければいけないような場合には有効です。
スタティックイニシャライザはクラスのどこにでも、いくつでも記述できますが、ロード時に実行されるのでクラスの先頭から順方向で参照が行われることに注意して、適切な位置に記述する必要があります。

書式

static{
  初期化処理
}

参考例

public class StaticInitializeSample {
	public static void main(String[] args) {

		System.out.print("static arrayの値は[");
		for(int num: StaticInitialize.array) {
			System.out.print( num );
		}
		System.out.println( "]です。");
	}
}

class StaticInitialize {
	static int[] array = new int[10];
	
	//スタティックイニシャライザ
	static{
		for (int i=0; i<array.length; i++) {
			array[i] = i;
		}
	}
}

実行結果

>java StaticInitializeSample
static arrayの値は[0123456789]です。

>

なお、JVMがスタティックイニシャライザを処理中に何らかのトラブルが発生(例えばNullPointerException)した場合、そのことを通知するクラスが存在しないため、JVMはExceptionlnlnitializerErrorを発生させ、プログラムを強制終了させます。

インスタンスイニシャライザ(初期化ブロック)
インスタンス生成時にインスタンス変数(インスタンスごとに作成される変数)を初期化します。インスタンス変数は、通常コンストラクタで初期化しますが、オバーロードした複数のコンストラクタで共通する処理がある時にはインスタンスイニシャライザで一括して初期化すればプログラムの可読性が向上します。
なお、インスタンスイニシャライザはコンストラクタより先に実行されます。

書式

{ }に囲まれたブロックで、クラスブロック直下にフィールドやメソッド、コンストラクタと
同列の扱いで記述します。

参考例

public class InstanceInitializeSample {
	public static void main(String[] args) {

		StaticInitialize si1 = new StaticInitialize();
		StaticInitialize si2 = new StaticInitialize("引数一つの");

		System.out.println("インスタンスsi1は" + si1.constracterType + si1.postFix);
		System.out.println("インスタンスsi2は" + si2.constracterType + si2.postFix);
	}
}

class StaticInitialize {
	String postFix;
	String constracterType;
	
	//インスタンスイニシャライザ
	{
		postFix = "コンストラクタを使いました。";
	}
		
	StaticInitialize() {
		this.constracterType = "デフォルト";
	}

	StaticInitialize(String type) {
		this.constracterType = type;
	}
}

実行結果

>java InstanceInitializeSample
インスタンスsi1はデフォルトコンストラクタを使いました。
インスタンスsi2は引数一つのコンストラクタを使いました。

>

クラスに戻る

ポリモーフィズム

ポリモーフィズム(polymorphism)は、オブジェクト指向プログラにおける概念のひとつです。日本語では「多態性」「多様性」など呼ばれていますが、このままではイメージが掴めません
英文の意味に立ち戻ると「poly」には「複数の」であり、「morph」とは「姿を変える」という意味ですから、ポリモーフィズムは一つの事を複数の違った形で扱えたり、類似したいろいろな物を同じものとして扱える概念です。
ポリモーフィズムによって、プログラムをブラックボックのまま、より柔軟に扱うことが出来ます。

ポリモーフィズムは概念ですから、具体的な機能はいくつか考えられます。
例えば、継承とオーバーライドによって同じメソッド呼び出しであっても、常に同じふるまいではなく、そのときの目的に適したふるまいを自動的に切り替えることが出来るのもポリモーフィズムです。

また、継承関係にある参照変数間の代入や、インスタンスのキャストもポリモーフィズムです。

継承関係にある参照変数間の代入
これはインスタンスを作成する時に、参照変数の型をインスタンスの型ではなく継承関係にあるスーパークラスの型にすれば、そのインスタンスはあたかもスーパークラスの型として扱えるという物です。

継承で使ったNormalTVとSmartTVに在庫情報を追加して、異なる製品を同じ配列で管理する場合を考えてみましょう。

スーパークラス

public class NormalTV2 {
	String serialNo;
	String tvType;
	
	NormalTV2(String tvType, String serialNo) {
		this.tvType = tvType;
		this.serialNo = serialNo;
	}
	
	NormalTV2(String serialNo) {
		this.tvType = "NormalTV";
		this.serialNo = serialNo;
	}
	
	void on() {
		System.out.println("\t電源を入れます。");
	}
	void off() {
		System.out.println("\t電源を切ります。");
	}
	void display() {
		System.out.println("\t番組を視聴します。");
	}
	void channelChange() {
		System.out.println("\tチャネルを切り替えます。");
	}
	void functions() {
		on();
		off();
		display();
		channelChange();
	}
	void printProductInfo() {
		System.out.println(tvType + " : serial = " + serialNo);
	}
}

サブクラス

public class SmartTV2 extends NormalTV2 {
	
	SmartTV2(String serialNo) {
		super("SmartTV ", serialNo);
	}
	
	void internet() {
		System.out.println("\tインターネットにアクセスします。");
	}
	void recode() {
		System.out.println("\t番組を録画します。");
	}
	void dvd() {
		System.out.println("\tDVDを再生します。");
	}
	void functions() {
		super.functions();
		internet();
		recode();
		dvd();
	}
}

スーパークラス、サブクラスを一緒に管理する処理

public class PolymorphismSample {
	public static void main(String[] args) {

		NormalTV2[] tvs = { new NormalTV2("0001"), new SmartTV2("0101"), new NormalTV2("0002"), 
			new NormalTV2("0003"), new SmartTV2("0101") };
		
		for(NormalTV2 tv : tvs) {
			tv.printProductInfo();
		}
	}
}

実行結果

>java PolymorphismSample
NormalTV : serial = 0001
SmartTV  : serial = 0101
NormalTV : serial = 0002
NormalTV : serial = 0003
SmartTV  : serial = 0101

>

これにより、継承関係にあるいろいろな実装を同じ型として扱えますが、インスタンスがどのようなメソッドやフィールドを持っていたとしても、扱っている型で定義されているもの以外は使えません。使おうとするとコンパイルエラーになります。
このポリモーフィズムが成り立つのは、継承関係だけでなく実現関係でも構いません。継承関係も実現関係もない場合にはポリモーフィズムは成立せずコンパイルエラーになります。
※インターフェースはインスタンス化が行えないので、インターフェース自体をポリモーフィズムで扱う事はできません。

インスタンスのキャスト
サブクラスをスーパークラス型に変換するのをアップキャストと呼びます。アップキャストは、型の互換性チェックが簡単に行えるので自動的に行われます。
一方、スーパークラス型で扱っていたインスタンスを、元の型に戻すのはダウンキャストと呼びます。ダウンキャストは型の互換情報が無いので、明示的にキャスト指定する必要があります。
インスタンスが扱えるのは参照変数の型に定義されているものだけです。そこで、アップキャストして作ったサブクラスのインスタンスをダウンキャストすると、サブクラスの差分として定義したメソッドやサブクラスで定義したフィールド(スーパークラスのフィールドと同名の変数がサブクラスにある場合には変数の実体が切り替わる)が使えるようになります。しかし、スーパークラスをダウンキャストしてサブクラスの差分のフィールドやメソッドにアクセスすると実行時に例外が発生します。

NormalTVとSmartTVを使ったアップキャスト、ダウンキャストの例
アップキャスト

public class UpCastSample {
	public static void main(String[] args) {

		NormalTV2 tv = new SmartTV2("0101");
		
		tv.printProductInfo();
		tv.functions();
//		tv.internet();
//		tv.recode();
//		tv.dvd();
	}
}

実行結果

>java UpCastSample
SmartTV  : serial = 0101
        電源を入れます。
        電源を切ります。
        番組を視聴します。
        チャネルを切り替えます。
        インターネットにアクセスします。
        番組を録画します。
        DVDを再生します。

>

アップキャストした参照変数型に含まれるメソッドにアクセスする限りは実行できます。
(オーバーライドしたメソッドはオーバーライドした側のメソッドが使われます。)
しかし、ソースのコメントアウトを外して参照型変数型に含まれないメソッドにアクセスするとコンパイルエラーが出ます。

>javac UpCastSample.java
UpCastSample.java:8: エラー: シンボルを見つけられません
                tv.internet();
                  ^
  シンボル:   メソッド internet()
  場所: タイプNormalTV2の変数 tv
UpCastSample.java:9: エラー: シンボルを見つけられません
                tv.recode();
                  ^
  シンボル:   メソッド recode()
  場所: タイプNormalTV2の変数 tv
UpCastSample.java:10: エラー: シンボルを見つけられません
                tv.dvd();
                  ^
  シンボル:   メソッド dvd()
  場所: タイプNormalTV2の変数 tv
エラー3個

>

ダウンキャスト

public class DownCastSample {
	public static void main(String[] args) {

		SmartTV2 tv = (SmartTV2) new NormalTV2("0001");
		
		tv.printProductInfo();
		tv.functions();
	}
}

実行結果
ダウンキャストを明示しない>場合は以下のコンパイルエラーになります。

>javac DownCastSample.java
DownCastSample.java:4: エラー: 不適合な型: NormalTV2をSmartTV2に変換できません:
                SmartTV2 tv = new NormalTV2(&quot;0001&quot;);
                              ^
エラー1個

>

ダウンキャストを明示すると、コンパイルは通りますが実行時に例外が発生します。

>java DownCastSample
Exception in thread &quot;main&quot; java.lang.ClassCastException: NormalTV2 cannot be cast to SmartTV2
        at DownCastSample.main(DownCastSample.java:4)

>

アップキャストしたものをダウンキャストする

public class UpDownCastSample {
	public static void main(String[] args) {

		NormalTV2 ntv =  new SmartTV2("0101");
		SmartTV2 stv = (SmartTV2) ntv;
		
		stv.printProductInfo();
		stv.internet();
		stv.recode();
		stv.dvd();
	}
}

実行結果
アップキャスト時にはアクセスできなかったメソッドがアクセスできるようになります

>java UpDownCastSample
SmartTV  : serial = 0101
        インターネットにアクセスします。
        番組を録画します。
        DVDを再生します。

>

つまり、インスタンスのキャストはインスタンスの扱い方が変わるので、参照先のインスタンスが変わるわけではありません。

プログラムの部品化に戻る

メソッド

メソッドはクラスの振舞い方、所作、何かを行う機能の定義です。
メソッドは引数を受取ったり、処理の結果の値を返すことが出来ます。

引数はメソッドを実行するために必須のデータであり、呼び出し側(実引数)と呼び出される側(仮引数)の引数の数、及び型が一致しないとコンパイルエラーが発生します。
基本型の引数をメソッドに渡すとき、引数の値はコピーされて渡されるため、引数を渡されたメソッド内で値が変更されても、呼び出し元の値は変わりません
一方、オブジェクト型の引数では、呼び出し元から呼び出されたメソッドにオブジェクトの参照値が渡されるので、2つのメソッドが参照するインスタンスは同じ物であり、内容を変更すれば両方のメソッドがその変更を参照することになります。

同じ型の引数は任意個まとめて可変長引数にすることが出来ます。
可変長引数は引数の型の直後にピリオド3つ「…」を付けて宣言し、その後に変数名(配列名)を書きます
可変長引数を持つメソッドは、引数を2つでも、3つでも、いくつでも渡して呼び出すことができます。渡された複数の値は、JVMによって配列に置き換えられますから、**可変長引数の値を使うときには、配列と同じように[ ]を変数名に追加して使います。

void sample(int... num) {
     for (int i =0; i<num. length; i++) {
          System.out.println(num[i]);
     }
}

なお、可変長引数を使う時には、

可変長引数は一組のみ記述できます。
同じ型の引数を任意個まとめられるだけで、異なる型はまとめられません。
可変長引数以外に通常の引数も受け取る必要がある場合、可変長引数は引数の最後に記述します。可変長引数の後に通常の引数を書くとコンパイルエラーになります。

処理の結果の値は戻り値と呼びますが、戻り値を呼び出し元のメソッドに戻すためには、return文を使います。
戻り値型にはint 型を、return文ではdouble型を戻すように記述すると、コンパイルエラーが発生します。
また、戻り値型を宣言しているにもかかわらす、return文を記述しない場合もコンパイルエラーになります。
逆に戻り値を戻さないvoidを宣言しているのにreturn文で値を戻そうとしてもコンパイルエラーになります。

public class ReturnSample {
	public static void main(String[] args) {

		System.out.println("retrun value = " + returnValue() );
		int voidGet = voidReturn();
		
	}
	static int returnValue() {
		return 1.0;
	}
	static void voidReturn() {}
}

コンパイル結果

>javac ReturnSample.java
ReturnSample.java:5: エラー: 不適合な型: voidをintに変換できません:
                int voidGet = voidReturn();
                                        ^
ReturnSample.java:9: エラー: 不適合な型: 精度が失われる可能性があるdoubleからintへの変換
                return 1.0;
                       ^
エラー2個

>

メソッドからクラスのメンバへのアクセス
staticなメンバはインスタンスの有無にかかわらす使えますが、staticではないメンバは、インスタンスがないと使えません。
このため、staticなメソッドはstaticなフィールドやメソッドにはアクセスできますが、staticでないメンバにはアクセスできません。しかし、逆にstaticではないメソッドから、staticなメンバにアクセスすることは可能です。

クラスに戻る