ポリモーフィズム

ポリモーフィズム(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("0001");
                              ^
エラー1個

>

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

>java DownCastSample
Exception in thread "main" 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を再生します。

>

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

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

抽象クラスとインターフェース

抽象クラスもインターフェースも、書式を理解しても、実際のプログラミングではどのように使い分けたらよいのかはっきりしません。
どのような場合に抽象クラス、そしてどういう時にはインターフェースを使ってプログラミングすれば良いのか、使い分けを考えてみましょう。

抽象クラス

抽象クラスの書式

修飾子 abstrac class クラス 名 {
  フィールド
  コンストラクタ
  メソッド
  抽象メソッド
}
※抽象クラスを継承した抽象クラスは、元の抽象クラスを拡張し、新しい抽象メソッドを追加したり、既存の抽象メソッドをオーバーライドして実装することができます。
※abstractは継承を前提としているため、final修飾子を同時に指定すると、コンパイル・エラーなります。

抽象メソッドの書式

修飾子 abstract 戻り値の型 メソッド名(引数リスト);

抽象クラスを継承するクラス定義の書式

修飾子 class クラス名 extends 抽象 クラス 名 {
  フィールド
  コンストラクタ
  メソッド
  抽象メソッド のオーバライ
}

クラスと抽象クラスの使い分け
クラスはもともと(狭義の)オブジェクトを抽象化して作ります。では抽象クラスは一般的なクラスと何が違うのでしょう。
本題に入る前に「抽象」という言葉を正しく理解しましょう。
「抽象」というと抽象画の印象や、「具象」の反対語だから「曖昧な」といった意味だろうと思っている人もいます。ですが英語のabstractには「曖昧な」という意味はありません。英語の辞書を見ると、動詞として「~を取り除く」「~を概念化する」「~要約する」とあります。

概念化とは「いくつかの事物を一般化してとらえる考え方」ですから、抽象化とは「個別的な物を取り除いて、事物に共通なものを抜き出し、要約する」ことです。
クラスは個々のオブジェクトから個別的な物を取り除き、共通する点を抜き出して、具体的なフィールドとメソッドに要約してスーパークラスとし、各オブジェクト固有の事物をサブクラスで定義します。
一方、抽象クラスはこの概念化(抽象化)を一歩進めて、個別的な物が持つ振舞いの詳細は違っていても共通して持っている振舞いを、詳細を決めずに概念として定義する抽象メソッドを使って定義します。そして、振舞いの詳細はサブクラスに定義させます。

言い換えると、スーパークラスはサブクラスに対し振舞いの定義を強制しません。スーパークラスの振舞いをオーバーライドするかしないかはサブクラスの自由意志です。一方抽象クラスは抽象クラスを実装するサブクラスに対し、詳細は決めずに(詳細を自分では決められない場合を含め)決めなければいけない振舞いを規定して、その詳細を定義することを強制します。つまり、サブクラスが抽象メソッドをオーバーライドしないとコンパイルエラーになります。
また、Javaの場合クラスは一つのスーパークラスしか持てない(単一継承)ので、抽象クラスも単一継承です。

インターフェース
インターフェースはクラスとは別物の、継承構造を持ったクラス群に設定される、備えるべき性質、特性です。
インターフェース自体はインターフェースを継承(多重継承が可能)出来ますが、クラスはインターフェースを継承出来ず、実装(implements)して実現します。
実装は継承と異なり、クラスは複数のインターフェースを実装(多重実装)する事が出来ます。
実体(具体的な値)のないインターフェースは抽象クラスと同じようにインスタンス化することは出来ません

なお、実装によって設定されるインターフェースですが、インタフェース内の抽象メソッドの実現は、実装したクラスが行わなければいけないわけではありません。インターフェースは特定のクラスではなく、クラスの継承構造全体に設定されたものなので、クラスの継承構造のどこかのクラスに具象メソッドがあれば良いのです。

インターフェースの書式

修飾子 interface インターフェス名 {
定数フィールド
抽象メソッド
}

定数フィールドの書式(暗黙にpublic static final)

データ型 フィールド名 = 初期値 ;

抽象メソッドの書式(暗黙にpublic abstract)

戻り値の型 メソッド名 (引数リスト ) ;

インターフェス実装の書式

修飾子 class クラス名 implements インターフェス名 ,インターフェス名 ,… {
  メソッドのオーバーライド定義({ }だけを記述して処理は記述しない)
}

インターフェースは抽象クラスと違って相手に与える定数(相手に変えてもらっては困る)とstaticな初期化された変数(インスタンスを作らなくても使える)及び相手に解放する振舞いとしての抽象メソッドしか記述できません

インタフェース内で宣言されるフィールドは暗黙的にpublic static finalで修飾された定数となります。従ってprotectedやprivateで修飾するとコンパイルエラーになります。また、初期化しない場合もコンパイルエラーになります。

インタフェースは具象クラスに実装してもらって初めて存在意義が出ます。そこで、インタフェースを実現したクラスの具象メソッドのアクセス修飾子はインターフェースと同じかより緩い物でなければいけません。インターフェースは暗黙的にpublicなのでpublicしか使えません

インターフェース

interface InterfaceSample {
	final String teisu = "定数";
	static int intNum = 0;
	
	void interfaceFeldPrint();
}

インターフェースを実装した抽象クラス(但し、実際は何もしていない)

abstract class InterfaceSubClass implements InterfaceSample{ }

抽象クラスを継承したサブクラス(インターフェースの実装を実際に行っている)

class AbstractSubClass extends InterfaceSubClass{
	
	public void interfaceFeldPrint() {
		System.out.println("interfaceFeld : " + teisu + ", " + intNum);
	}
}

抽象メソッドのサブクラスを通して、インターフェースで定義されたメソッドを実行する処理

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

		AbstractSubClass asc =new AbstractSubClass( ); 
		asc.interfaceFeldPrint();
	}
}

実行結果

>java InterfaceAbstractTest
interfaceFeld : 定数, 0

>

抽象クラスとインターフェースの使い分け
一般のクラスとは違うといっても抽象クラスもクラスですから、objectからのクラスの階層構造のどこかに位置します。一方、インターフェースは外見的に抽象クラスと似ていてもクラスではないので、この階層構造の中には位置しません。インターフェースは特定のクラスに付随するだけです。このためクラスの継承(extends)に対し、インターフェースでは実装(implements)と言います。

抽象クラスといえどもクラスです。抽象メソッドだけでなく具象メソッドも持つことが出来ますから、抽象メソッドもスーパークラスの性格を引き継いだサブクラスを作る仕組み、という点ではクラスと変わりません。
インターフェースは英語の意味から考えるとインターフェースは「つなぎ合わせる」「調和させる」です。つまり、インターフェースはクラス間をつなぎ合わせる仕組みです。この点こそが抽象クラスとインターフェースの違いであり、使い分けのポイントです。では何をつなぎ合わせ、調和させるのでしょう。

実際の開発では大勢のプログラマがプログラミングを分担し、数多くの機能(振舞い)を開発するので、全ての開発が同期して開発できるわけではありません。つまりある部分を開発し、テストしようとしても関連する別の部分はまだ設計中で結合してテストすることが出来なかったり、開発している場所が離れていてリアルタイムで開発を同期させることが出来なかったり、そもそもシステムが複雑で部分部分を個別にテストしてからでないと結合してテストが出来なかったりします。

このような場合、インターフェースを使えば複雑なシステムをモジュールに分解して、モジュール単位で開発を進め、単体テストが終わったら実装モジュールを実際の物に入れ替えて総合テストを行う事ができます。つまりインターフェースは実装の仕方は問わずに相手にしてもらいたい事だけを定義し、自分と相手モジュールをつなぎ合わせます。

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

ラムダ式

数学の世界では関数を表現する式にギリシャ文字のラムダ(lambda):λを使う慣習があったそうですが、この名前に由来するラムダ計算という計算体型が1930年代に考案され、1958年に誕生した関数型言語のLISPで関数の定義法として採用され、多くの言語で関数を他の関数の引数として与える操作が許されています。

こういった背景のラムダを冠した表記法:ラムダ式がJava8で導入されました。
ラムダ式を使えば、実装が必要なメソッドを1つだけ持つインターフェース(関数型インタフェース)の実装が、従来の無名クラスを使うよりも簡潔に記述できます

書式

( 実装するメソッドの引数 ) -> { 処理 }

利用例
インターフェース

interface IntFunc {
	public int func(int x);
}

無名クラスによる実装

public class NamelessClass {
      public static void main( String args[] ) {
		IntFunc iNLF = new IntFunc(){
			public int func(int x){
				return x+x;
			}
		};
		System.out.println("NamelessFunc return = " + iNLF.func(2));
	}
}

実行結果

>java NamelessClass
NamelessFunc return = 4

>

ラムダ式による実装

public class LambdaClass {
      public static void main( String args[] ) {
		IntFunc iLF = (int x) -> {return x+x;};
		System.out.println("LambdaFunc return = " + iLF.func(2));
	}
}

無名クラスでは5行の記述が、ラムダ式では僅か1行で記述できます。

実行結果

>java LambdaClass
LambdaFunc return = 4

>

ラムダ式の基本書式は、引数には型を明示し、処理は{ }で囲みます。

(int x, int y) -> {
	return x + y;
};

しかし、条件によりラムダ式はさらに簡略記述できます。

引数の型定義は省略可能(コンパイラが推測)

(x, y) -> {
	return x + y;
};

処理が1行なら、{ }もreturnも不要

[javat gutter=”false”]
(x, y) -> x + y;
[/java]

引数が1つなら、引数の( )も不要

x -> x * 2;

ただし、引数が無い場合は、( )は必要

() -> 1;

また、Java8では、使用頻度の高い関数型インターフェースがjava.util.functionの中で提供されています。

Supplier系
引数なしで値を返す
Supplier s = () -> “input”;
System.out.println(s.get());
Consumer系
値を受け取るが、返却しない(処理を行う)
SupplierConsumer c = (str) -> System.out.println(str);
c.accept(“output”);
Predicate系
値を受け取って論理値を返す
Predicate p = (str) -> str == “yes”;
System.out.println(p.test(“no”));
Function系
値を受け取って、何らかの値を返す
ToIntFunction f = s -> s.length();
System.out.println(f.applyAsInt(“abc”));
上記全て、引数を2つ受け取る場合はインターフェース名の先頭に「Bi」を付ける
BiFunction lambda5 = (str, num) -> str + num;

さらに、受取るデータ型によりInt、Long、Doubleを名前の先頭に付けたインターフェースがありますし、返すデータ型によりToInt、ToLong、ToDoubleを名前の先頭に付けたインターフェースがあります。

Javaではメソッドを関数と考えられない事もありませんが、メソッドはあくまでクラスの振舞い、機能なのでクラスを意識せずに使うことが出来ません。これに対しラムダ式はインターフェースで規定されたメソッドを、クラスを意識した実装を記述しないで必要な処理だけ記述し、直接使う方法とも考えられるでしょう。

クラスに戻る

内部クラスと無名クラス

クラス内に宣言されたクラスを内部クラス(インナークラス)といいます。また、内部クラスにはクラス名を宣言しない無名クラス(匿名クラス)もあります。

あるクラスのメンバ変数やメソッドに強く依存するクラスは、そのクラスの中に内部クラスとして記述する方が使いやすくなります。

クラス内にインナークラスを定義したクラス

public class ClassInClass {
	private String classInClassMsg = "インナークラスの外のprivateメンバ変数にアクセスしました。";

	private class InnerClass {
		private String innerClassMsg = "インナークラスの中のprivateメンバ変数にアクセスしました。";
		
   		void innerSpeak() {
   			System.out.println("インナークラスのメソッドがメッセージを出します。");
			System.out.println(classInClassMsg);
			System.out.println(innerClassMsg);
   		}
	}
	
	void classInClassSpeak() {
		System.out.println("クラス内でインナークラスのインスタンスを作り、そのメソッドを呼びます。");
		InnerClass inC = new InnerClass();
		inC.innerSpeak();
	}
}

インナークラスを定義したクラスのインスタンスを使ってインナークラスを使う処理

public class ClassInClassSample {
	public static void main( String args[] ) {
		ClassInClass cic = new ClassInClass();
		cic.classInClassSpeak();
	}
}

実行結果

>java ClassInClassSample
クラス内でインナークラスのインスタンスを作り、そのメソッドを呼びます。
インナークラスのメソッドがメッセージを出します。
インナークラスの外のprivateメンバ変数にアクセスしました。
インナークラスの中のprivateメンバ変数にアクセスしました。

>

内部クラスからは同じクラス内のprivate修飾子の付いたメンバ変数、メソッドにもアクセスできます。

内部クラスはメソッド内にも作ることができます。その場合、その内部クラスはメソッド内でのみ機能します。

メソッド内にインナークラスを定義したクラス

public class ClassInMethod {
	private String mainClassMsg = "インナークラスの外のprivateメンバ変数にアクセスしました。";

	
	void classInMethodSpeak() {
		
		class InnerClass {
			private String innerClassMsg = "インナークラスの中のprivateメンバ変数にアクセスしました。";
		
   			void innerSpeak() {
   				System.out.println("インナークラスのメソッドがメッセージを出します。");
				System.out.println(mainClassMsg);
				System.out.println(innerClassMsg);
   			}
		}
		System.out.println("メソッド内でインナークラスのインスタンスを作り、そのメソッドを呼びます。");
		InnerClass inC = new InnerClass();
		inC.innerSpeak();
	}
}

インナークラスを定義したクラスのインスタンスを使ってインナークラスを使う処理

public class ClassInMethodSample {
	public static void main( String args[] ) {
		ClassInMethod cim = new ClassInMethod();
		cim.classInMethodSpeak();
	}
}

実行結果

>java ClassInMethodSample
メソッド内でインナークラスのインスタンスを作り、そのメソッドを呼びます。
インナークラスのメソッドがメッセージを出します。
インナークラスの外のprivateメンバ変数にアクセスしました。
インナークラスの中のprivateメンバ変数にアクセスしました。

>

メソッド内で内部クラスを作る場合、内部クラスに名前を与えないクラスを無名クラスと呼びます。
無名クラスは、宣言から、実装、インスタンス化までをまとめて一文で記述できますが、その記述が通常のクラスの記述と異なるため注意が要ります。
名前が無いため継承が出来ませんから一代限りのシンプルな抽象クラスの実現やインターフェースの実装に使います

書式

new 抽象クラス名( ) { 無名クラス };

new インタフェース名( ) { 無名クラス };

インターフェースを無名クラスで実装するところを見てみましょう。

インターフェース

interface Conversation{		
	public void greeting();
}

インターフェースを実装し、インスタンス化して、インスタンスを利用する処理
7行目の}の後に;が必要なことに注意してください。

public class NamelessClass {
	  public static void main( String args[] ) {
		  Conversation introduction=new Conversation(){					//インタフェースを無名クラスで実装し、即インスタンス化。
			  public void greeting(){
				  System.out.println("私は無名クラスで実装されました。");
			  }
		  };
		  introduction.greeting();
	  }
}

実行結果

>java NamelessClass
私は無名クラスで実装されました。

>

参考に、インターフェースを通常のクラスで実装し、そのクラスをインスタンス化して利用する場合を見てみましょう。

インターフェースは同じなので、インターフェースを実装するクラス

class Talking implements Conversation {
	public void greeting(){
		System.out.println("私は名前のある通常クラスで実装されました。");
	}
}

そして、クラスをインスタンス化して利用する処理

public class NamedClass {
	  public static void main( String args[] ) {
		  Talking introduction=new Talking();
		  introduction.greeting();
	  }
}

実行結果

>java NamedClass
私は名前のある通常クラスで実装されました。

>

使う用途によっては無名クラスは簡潔に処理を記述できますが、用途は限定されるでしょう。自ら作ることは少ないかもしれませんが、一つだけで良いインターフェースの実現、たとえばGUI作成のためActionListenerインターフェースなどの実現などではよく使われます

クラスに戻る

構造化例外処理

「構造化例外処理」の構文はtry-catch-finallyの三つのブロックで構成され、tryブロックに行いたい処理、catchブロックに処理によって発生する可能性のある例外の回復処理、そしてfinallyブロックに例外発生の有無にかかわらず実行したい処理を記述します。

書式

	try {
		行いたい処理;
	} catch (例外クラスの型 例外クラスの変数) {
		例外の回復処理;
	} fainally {
		例外発生の有無にかかわらず実行したい処理;
	}

さらに例外が発生した場合、その発生したメソッド内でキャッチして例外を処理することも、メソッド名に続けてthrows宣言を行い、例外処理をメソッドの呼び出し元に移譲することも出来ます。この場合、そのメソッド内でのその例外に対する例外処理は不要です。
なお、例外処理の移譲によってメソッド呼び出しのおお元で例外を一元管理して処理することが出来ます。

書式

戻り値の型	メソッド名(引数並び) <strong>throws 例外クラスの型</strong> { }

除算によりArithmeticException(算術例外)が発生しても自分では例外処理をせず、呼び出しもとに例外処理を移譲する

int Divide(int num, int denum) throws ArithmeticException {
	return (num / denum);
}

try ブロック内には、複数の文を記述できます。もし例外が発生したらtryブロック内の残りの処理は全て読み飛ばされ、対応するcatchブロックに制御が移ります
catchブロックの目的はプログラムを正常な状態に復帰させることで、キャッチできる例外はすべての例外のルートクラスであるThrowableクラス、例外処理が必須のExceptionクラスだけでなく、例外処理が必須ではないErrorクラスやRuntimeExceptionクラスもキャッチできます。catchブロックの処理が終了すると「不具合は対処された」として、finallyブロックの処理に移ります
finallyブロックは必ず実行されるブロックであり、例外をthrows宣言している場合、例外が移譲される前にfinallyブロックが実行されます。またcatchブロックにreturn文がある場合でも、return処理の前にfinallyブロックが実行され、その後catchブロックに戻りreturnが実行されます。

try-catch-finallyの構文は、各ブロックの順序を変更することはできません。誤った順序で記述するとコンパイルエラーになります。tryブロックとfinallyブロックは1つずつしか記述できず、複数記述するとコンパイルエラーになります。一方、catchブロックは複数記述できます。

なお、catchブロックを複数記述する場合はcatchする例外の順序はサブクラスを先頭にする必要があります。これは例外クラスのインスタンスも、ほかのクラスと同様にポリモーフィズムが成り立ち、サブクラスの例外クラスのcatchはスーパークラスの例外処理がcatch可能であり、結果サブクラスの例外処理がスーパークラスの例外処理の後に記述されていると、サブクラスの例外処理は常に実行されない事になるからです。このような場合、コンパイラーは到達不可能なコードをあるとして、コンパイルエラーを発生します。

Exceptionクラスのサブクラスとして独自例外を作る

public class SubException extends Exception { }

Exceptionのcatch文を先に記述

public class CatchOrder {
	public static void main(String[] args) {
		try {
			throw new SubException();
		} catch (Exception e) {
			
		} catch (SubException e) {
			
		}
	}
}

SubExceptionの例外もExceptionのcatch文が取ってしまうのでSubExceptionのcatch文は到達不可能なコードとなりコンパイルエラーになります。

>javac CatchOrder.java
CatchOrder.java:7: エラー: 例外SubExceptionはすでに捕捉されています
                } catch (SubException e) {
                  ^
エラー1個

>

SubExceptionのcatchブロックはExceptionのcatchブロックより前に置きます。

複数のtry-catchがネストしている場合、スローされた例外を受け取るのは、その例外に対応したもっとも近いcatch ブロックです。ネスト階層ごとにfinallyブロックがある場合は、ネストの内側から順にすべてのfinallyブロックが実行されます

例外処理に戻る

ErrorとException

Javaでは例外処理の対象はError、Exceptionの2種類です。
さらに詳しく言えばExceptionはコンパイラーが例外処理が記述されていることをチャックする検査例外であるのに対し、ExceptionのサブクラスであるRuntimeExceptionは、例外自体が発生しないようにプログラマがロジックを工夫することが期待されているためコンパイラーは例外処理の記述をチェックしない事。
そして、例外処理の記述法などを説明しました。

ここでは、それらError、ExceptionとしてJavaが用意している例外クラスを説明し、さらにそれらを踏まえて独自に例外クラスを作って例外処理する方法を説明します。

Error
トラブルが発生した場合、JVMがErrorクラスまたは、そのサブクラスのインスタンスを生成しプログラムに通知します。プログラム処理ではこの種のトラブルは復旧出来ないので例外処理の記述は不要ですが、例外処理を記述してプログラムが強制終了させずに、トラブルが発生したことをユーザに知らせ、プログラムを正常終了させることはできます。
基本的には、このような事態が発生しないようにプログラムを十分テストしたり、実行環境を確認し、整えておく必要があります。
NoClassDefFoundError
JVMが実行対象のクラスファイルを発見できなかった。
コンパイル時点で存在していたクラスが、見つからない。
VirtualMachineError
JVMが壊れているか、または動作を継続するのに必要なリソースが足りなくなった。
StackOverflowError
JVMが「再帰呼び出し」などで、スタック領域の不足を検出した。
OutOfMemoryError
JVMがインスタンスを保存したり、クラスの定義情報を保存したりするヒープ領域が一杯になり、新しいオブジェクトを割り当てることができない。
Internal Error
JVM内で何らかの内部エラーが発生した。
ExceptionInInitializerError
JVMがstaticイニシャライザを処理している間に例外が発生(NullPointerExceptionなど)したとき、例外の通知相手がまだ存在しないのでErrorを発生させた。
Exception
プログラム処理による復旧が可能なで例外であり例外処理の記述が必須です。コンパイラーが例外処理の記述をチャックするので例外処理の記述がないとコンパイルが完了しません。
ClassNotFoundException
クラスの文字列名を使用してforName、findSystemClass、loadClassメソッドでロードしようとしたが、指定された名前のクラスの定義が見つからなかった。
IOException
入出力処理の失敗、または割り込みの発生による例外
FileNotFoundException
指定されたパス名で示されるファイルが開けなかった
SQLException
データベース操作時に発生した例外
RuntimeException
プログラム時の考慮によって回避できる例外です。コンパイラーは必要な考慮がされていることを前提に例外処理の有無をチャックしません。従って、プログラム上の考慮が不足していてもコンパイルは通ってしまうので、実行時に例外が発生する可能性があります。
ArithmeticException
ゼロ割等、不能演算が指示された
IllegalArgumentException
利用される側のオブジェクトが不正な引数を受取った
NumberFormatException
parselntメソッドが変換形式に合わない引数を受取った
例えば、Integerクラスのparselntメソッドに文字として英数字以外を渡す。
IndexOutOfBoundsException
配列や文字列、コレクションの範囲外アクセス
ArrayIndexOutOfBoundsException
配列の範囲外アクセス
public class Main01 {
	public static void main(String[] args) {
		String[] str = new String[1];
		for(String s :str) {
			System.out.println(s + " , " + str[1]);
		}
	}
}
>java Main01
Exception in thread &quot;main&quot; java.lang.ArrayIndexOutOfBoundsException: 1
        at Main01.main(Main01.java:5)

>
StringlndexOutOfBoundsException
文字列の範囲外アクセス
public class Main03 {
	public static void main(String[] args) {
		String str = "string";
		System.out.println(str.charAt(6));
	}
}
>java Main03
Exception in thread &quot;main&quot; java.lang.StringIndexOutOfBoundsException: String index out of range: 6
        at java.lang.String.charAt(Unknown Source)
        at Main03.main(Main03.java:4)

>
NullPointerException
オブジェクト参照がnullの状態で、インスタンスにアクセスする。
配列変数がnullなのに、配列のサイズを調べる。
public class Main02 {
	public static void main(String[] args) {
		String[][] str = null;
		System.out.println(str.length);
	}
}
>java Main02
Exception in thread &quot;main&quot; java.lang.NullPointerException
        at Main02.main(Main02.java:4)

>
DateTimeException
Java SE 8で追加されたjava.time.LocalDateクラスでは従来のjava.util.Dateやjava.util.Calendarと異なり月は1から始まります。このため月に0を指定すると、構文上の誤りではないのでコンパイルエラーは発生せず実行時にこの例外が発生します。
ClassCastException
継承関係や実現関係にない型に互換性のないクラスをキャストしようとした。
IlegalStateException
利用される側のオブジェクトが、まだ利用するための準備が終わっていない。

以上はJavaが用意している例外クラスの一部です。その他の例外クラスについてはJava APIドキュメントで確認することが出来ます。

この様に数々の例外クラスが用意されていますが、プログラム開発上独自に値の確認をしたい変数などあるでしょう。これらの確認に自分で独自の例外を定義して、例外処理の仕組みを使えばスマートに不具合への対処を組み込むことが出来ます。

参考例として、映画館のチケットの代金を計算してみましょう。
通常料金は1800円として、夜10:00(22:00)からをレイトショーとして1300円に割引します。
上映は朝9:00から始まるとして、希望の上映開始時間を整数で入力してもらいます。
入力された数字が9より少なかったり、24以上だったりした場合は再入力。また、数字以外の文字が入力された場合も再入力してもらいます。
時間の入力範囲チェックにはRuntimeExceptionのサブクラスとして独自例外:OutOfTimeRangeExceptionを作成し、9~23以外の入力があった場合にそれを発行し、数字以外の誤文字入力は実行時例外として通知されるNumberFormatExceptionをメインクラスでそれぞれ例外処理しています。

独自例外OutOfTimeRangeException

public class OutOfTimeRangeException extends RuntimeException { }

レイトショー割引も考慮してチケット代金を求めるHowMuchIsTheFee

import java.util.Scanner;

public class HowMuchIsTheFee {

	public static void main(String[] args) {
		
		HowMuchIsTheFee hmticket = new HowMuchIsTheFee();
		
		int ticketFee = 1800;
		boolean lateShow = hmticket.isLateShow();

		System.out.print("チケット代は");
		if( lateShow ) {
			System.out.print("レイトショー割引で");
			ticketFee = 1300;
		}
		System.out.println(ticketFee + "円です。");
	}
	public boolean isLateShow() {
    //22時以降はレイトショーで割引あり。
		while( true ) {
			Scanner sc = new Scanner(System.in);
			System.out.print("購入するチケットは何時の回ですか?\n9以上24より少ない数字を入力してください。:");
			String keyString = sc.next();
			try {
				int ticketTime = Integer.parseInt(keyString);
				if(ticketTime<9 || ticketTime>=24) throw new OutOfTimeRangeException();
				if(ticketTime>=22 && ticketTime<24) return true;
				return false;
			}
			catch (NumberFormatException e) {
				System.out.print("整数値以外の入力がありました。");
			}
			catch (OutOfTimeRangeException e) {
				System.out.print("入力された数字に誤りがあります。");
			}
			finally {
				System.out.println("再入力してください。\n");
			}
		}
	}
}

ハイライトされたisLateShowメソッドが、希望の上映開始時間を受付し、入力値チェックを行い入力誤りがあれば例外処理の中でエラーメッセージを出し、入力に誤りが無ければレイトショーに該当するか否かを論理値としています。
実行結果は以下のようになります。

>java HowMuchIsTheFee
購入するチケットは何時の回ですか?
9以上24より少ない数字を入力してください。:aa
整数値以外の入力がありました。再入力してください。

購入するチケットは何時の回ですか?
9以上24より少ない数字を入力してください。:8
入力された数字に誤りがあります。再入力してください。

購入するチケットは何時の回ですか?
9以上24より少ない数字を入力してください。:24
入力された数字に誤りがあります。再入力してください。

購入するチケットは何時の回ですか?
9以上24より少ない数字を入力してください。:10
再入力してください。

チケット代は1800円です。

>java HowMuchIsTheFee
購入するチケットは何時の回ですか?
9以上24より少ない数字を入力してください。:23
再入力してください。

チケット代はレイトショー割引で1300円です。

>

例外処理に戻る

継承

継承(inheritance)とは、あるクラスの特徴(フィールドとメソッド)を引き継ぎながら、継承元の特徴を一部変更したり、新たな特徴を付加して新しいクラスを作りだす仕組みです。継承によりソフトウェアでも実物の部品と同じようにパーツからユニット、コンポーネントを作る事ができ、階層構造を持ったプログラム部品を作ることによりプログラムの生産性が大きく向上します。

例えば類似のクラスがいくつか必要な場合、共通部分をコピーしていくつかのクラスを作ることも出来ますが、もしコピーした共通部分を変更したくなった場合、コピーした全ての共通部分を修正しなければいけません。これに対し継承を使って類似クラスを作れば、共通部分をクラスとして作りそのクラスを継承すれば、類似クラスが作れます。そして、共通部分の変更が必要になった場合、継承元のクラスを修正しさえすれば、継承して作った全ての類似クラスは何も手をかけることなく共通部分が変更されたものとして使うことが出来ます

継承の元になるクラスをスーパークラス、スーパークラスを継承したクラスはサブクラスと呼びます。

継承の書式

クラス宣言でスーパークラスを明示します。
class サブクラス名 extends スーパークラス名 { }

スーパークラス
通常のクラスとして記述します。

public class NormalTV {
	
	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();
	}
}

サブクラス
スーパークラスの明示と共にスパークラスとのフィールドおよびメソッドの差分を記述します。

public class SmartTV extends NormalTV {
	
	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 InheritanceSample {
	public static void main(String[] args) {

		NormalTV ntv = new NormalTV();
		SmartTV stv = new SmartTV();
		
		System.out.println("NomalTVの機能:");
		ntv.functions();
		System.out.println("SmartTVの機能:");
		stv.functions();
	}
}

実行結果

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

>

これらのクラスの継承関係をクラス図で表すと

継承関係はサブクラスからスーパークラスに白い三角形の実線矢印を引きます。(親を指し示す)

なお、継承によってサブクラスはスーパークラスの特徴を引き継ぎますが、次の2つは引き継げません。

コンストラクタ
privateなフィールドやメソッド

サブクラスはスーパークラスのコンストラクタを引き継がないという事は、サブクラスは独自のコンストラクタを持たなければなりません。そして、サブクラスはスーパークラスとサブクラスの付加部分で構成されているので、サブクラスのインスタンスを作るためにはスーパークラスとサブクラス両方のコンストラクタを持たせる必要があります

そこで、サブクラスでコンストラクタを独自に定義する場合はスーパークラスが持つコンストラクタが先に実行され、サブクラスが持つコンストラクタが後から実行されるようにサブクラスのコンストラクタの先頭に、スーパークラスのコンストラクタを呼び出すコードsuper();を追加しなければいけません。

一方、デフォルトコンストラクタを使う場合はコンパイラが自動的にサブクラスのデフォルトコンストラクタの先頭でスーパークラスのデフォルトコンストラクタを呼ぶコードを追加します。

なお、Javaではクラスの多重継承は禁止されています。(インタフェースはクラスではないので、多重実現は認められています。)

また、継承関係にあるサブクラスのインスタンスの中身は、スーパークラスとサブクラス(スーパークラスとの差分)のインスタンスで構成されています。そして、各インスタンスは独立しておりJVMからは別々に扱われています。従って、スーパークラスとサブクラスで同名のインスタンス変数があったとしても、それらは異なるものです。
そこで、メソッドがインスタンス変数を扱う場合、同一クラス内のインスタンス変数であればthis.を、サブクラスからスーパークラスのインスタンス変数であればsuper.をインスタンス変数名の前に付けて記述します。

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

メソッド

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

引数はメソッドを実行するために必須のデータであり、呼び出し側(実引数)と呼び出される側(仮引数)の引数の数、及び型が一致しないとコンパイルエラーが発生します。
基本型の引数をメソッドに渡すとき、引数の値はコピーされて渡されるため、引数を渡されたメソッド内で値が変更されても、呼び出し元の値は変わりません
一方、オブジェクト型の引数では、呼び出し元から呼び出されたメソッドにオブジェクトの参照値が渡されるので、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なメンバにアクセスすることは可能です。

クラスに戻る

配列

配列は同じデータ型の要素データを指定個数集めて一つの配列変数として扱えるようにしたものです。
複数の要素データを一つのまとまりとして扱う方法は配列以外にもいくつかありますが、配列の特徴

1次元だけでなく、2次元以上の多次元でも要素を構造化し、int型の整数で要素を特定することが出来る。
配列を宣言し、その領域を割り当てると配列の再構成はできない。(要素の追加・削除はできない)。

配列の作成は3段階に分かれていますが、記述の仕方で一度にすべて行うことも出来ますが、基本は次の順で行わなければいけません。

1.
配列の宣言
2.
配列の領域割当て(配列インスタンスの作成)
3.
配列要素の設定(値の代入)

配列の宣言

書式
要素データの型 次元を表す[ ]のセット 配列要素名;

ですが、Javaの変数宣言に類似した語順である形式1とC言語などの変数宣言に類似した形式2の2種類の表記法があります
形式1表記 int[] array;
形式2表記 int array[];

多次元配列は[ ]の数で次元を指定します。
多次元配列の宣言でも形式1表記、形式2表記が選択できます。

int[ ][ ] arrayA;    2次元配列型変数の宣言
int arrayB[ ][ ][ ];  3次元配列型変数の宣言

なお形式1表記、形式2表記を併用しても構いません。つまり、[ ]を一度にまとめて記述する必要はありません

int[] arrayA[];
int[][] arrayB[];

配列の宣言時には値がnullの配列型変数を確保するだけで、配列用の領域確保は行いません。従って、配列宣言の段階で[ ]内に数字を書き込み配列要素の数を指定するとコンパイルエラーになります。

配列の領域割当て
new演算子を使い、int型の値またはint 型の値を返す式により要素の数を指定して配列用の領域を確保します。

array = new int[要素の数];

なお、配列の宣言と領域の割当てを同時に行うことは可能です。

int[] array = new int[3];

また、配列の宣言と領域の割当てを同時に行う場合に限り、new演算子による領域確保の代わりに、配列初期化子を使った初期化によって直接配列用の領域を確保することもできます

int[] array = { 1, 2, 3 };

配列初期化子には、リテラル値のほか式も指定できるので、配列要素としてインスタンス生成式を記述しオブジェクトの参照を配列に格納することもできます。

なお、処理の途中で配列変数に配列初期化子で値を代入するとコンパイルエラーになります。しかし、new演算子を使った領域割り当てと同時であれば文中でも配列初期化子による値の代入は可能です。ただし、配列初期化子で要素の数を指定しているので、大カッコの中に要素数を指定するとコンパイルエラーになります。

多次元配列では全ての次元の配列インスタンスを一度に作成する必要はありません。例えば2次元配列では、まず1次元目の配列インスタンスを生成し、2次元目の配列インスタンスはあとから生成し、1次元目の配列の要素に2次元目の配列インスタンスへの参照を代入します。
また、多次元配列の要素を管理するための変数(一次元目の配列インスタンスの要素)は参照先の配列インスタンスの要素数とは無関係なので、2次元目以降の配列の要素数が全て同じである必要もありません

new演算子、初期化演算子による配列の作成
および、段階的な二次元配列の作成

public class InitializationSpecifier {
	public static void main(String args[]) {
		int[] array1 = {1, 2, 3};
		int array2[];
		array2 = new int[]{10, 20, 30};
		int array3[][]= {array1, array2};
		
		System.out.print("array1:");
		for(int ent1: array1) 	System.out.print(ent1 + " ");
		System.out.println();
		System.out.print("array2:");
		for(int ent2: array2) 	System.out.print(ent2 + " ");
		System.out.println();
		System.out.print("array3:");
		for(int[] arr: array3) {
			for(int ent: arr) System.out.print(ent + " ");
		}
	}
}

実行結果
拡張for文による2次元配列からの要素取得法

>java InitializationSpecifier
array1:1 2 3
array2:10 20 30
array3:1 2 3 10 20 30
>

配列要素の設定
配列インスタンスを作成した時に配列要素に設定されるデフォルト値は作る配列の型によって異なります

整数型
0
浮動小数点数型
0.0
真偽型
false
文字型
\u0000
オブジェクト型
null

特にchar型とString型(オブジェクト型)はデフォルト初期値が異なるので注意が必要です。
char型のデフォルト初期値は文字コード\u0000(印字は半角スペース)従って、char型の配列を宣言した場合に取られる要素のデフォルト初期値も\u0000、これに対しString型の配列を宣言した場合に取られる要素のデフォルト初期値はnullです。

配列インスタンスと要素は別物であるため、オブジェクト型配列は注意が必要です。
つまりオブジェクト型配列を作成しただけではオブジェクト型要素の初期値はnullですから、要素に特定のインスタンスを代入せずにオブジェクト型要素のフィールド値を参照すると、実行時に参照先がないという意味のNullPointerExceptionが発生します。

char型の配列とString型配列のデフォルト初期値の確認

public class InitValue {
	public static void main(String args[]) {
	
		char[] charArray = new char[3];
		System.out.print("charArry:[");
		for(char charEnt : charArray) {
			System.out.print(charEnt);
		}
		System.out.println("]");
		
		String[] strArray = new String[3];
		System.out.print("strArry:[");
		for(String strEnt : strArray) {
			System.out.print(strEnt);
		}
		System.out.println("]");

	}
}

実行結果

>java InitValue
charArry:[   ]
strArry:[nullnullnull]

>

char型の配列ではデフォルト初期値\u0000が半角スペースで表示され、String型配列では初期値のnullを文字として表示します。
(printlnメソッドがnull値を文字として扱う仕様です。)

プログラミングに戻る

制御文

制御文とは処理の流れを制御するための文です。
処理の流れを制御するといっても、自由気ままに処理が定義出来れば良いというわけではありません。「構造化プログラミング」では処理は「順次」、「分岐」、「繰返し」の3種類の組み合わせで定義すべきと推奨されています。
それ以外の「移動(goto文)」などを使うとプログラムはいわゆるスパゲティ状態になり、作者以外はもちろん作者でさえも時間が経つと処理の流れを把握しずらくなってしまうといわれます。

順次」は何々をしたら、次に何をする、という処理を次々に行う形態で、コーディングは命令文を順次書き連ねていきます。
分岐」は何々の条件では何を行い、その条件でなければ何を行うという形態で、コーディングはif-else文if-else if文switch文を使って命令文を区分けして書きます。
繰返し」はある条件が有効な間は何を行い続け、条件が有効で無くなったら次の命令に移るという形態で、コーディングはwhile文do-while文for文拡張for文を使って記述します。

言語仕様として難しい点はありませんが、「分岐」と「繰返し」には注意すべき点がいくつかあります。

for文

書式

	for(初期化文; 繰返し条件文; 繰返しが終わる毎に行う更新文) { 繰り返し時に行う処理 }

for文の構文で注意すべき点は、
for文の初期化文、条件文、更新文のうち、初期化文と更新文は複数記述できますし、省略することも出来ます。条件文は一つしか記述できず省略することはできません。複数の条件が必要な場合は論理演算子を使って複合条件にします。
初期化文で複数の変数を宣言する場合、変数は同じ型でなければいけません。異なる型の変数を複数宣言すると、コンパイルエラーが発生します。
初期化文で宣言した変数のスコープはfor文のブロック内であり、for文のブロック外で参照するとコンパイルエラーになります。
繰返し条件の評価は繰返し処理の前に行われるので、条件式の指定を誤ると繰返し処理を一度も行わない場合もあります。

条件式の指定を誤った場合

public class ForSample {
	public static void main(String args[]) {
	
		for(int i=0; i<0; i++) {
			System.out.println("forループの実行ブロック");
		}
		System.out.println("ここはforループの外です");
	}
}

実行結果
繰返し処理を行っていません。

>java ForSample
ここはforループの外です

>

到達不能文
if文やWhile文、for文では条件として論理値を記述する場合がありますが、論理値としてfalseを記述すると命令文を全く実行しなくなってしまいます。Javaではこのような到達不能文が発生すると多くの場合コンパイルエラーを出します。
確かにWhile文、for文ではコンパイルエラーが出ます、しかしif文ではコンパイルエラーは出ません
この処理の違いはif文の場合、下記のような記述により定数DEBUGをtrue、falseに切り替えて利用する為と想像できます。

	if(DEBUG) System.out.println("This is Debug write.");

拡張for文
拡張for文は、ある型のデータ集合から順にデータを呼び出す便利な構文です。しかし、その性質からデータを降順で呼び出したり、一つおきに呼びだすという事は出来ません。従って基本型を収納した一次元配列では標準for文に対する利点は単に記述が簡素化されるだけです。
逆にデータを呼び出すインデックスを指定する必要がないので、標準for文では扱えないHashMap等のint型のインデックスを持たないデータ集合を扱うことが出来ます

if-else if文
Javaの文は句の句の間で空白だけでなく、改行を挿入してもコンパイルエラーにはなりません。しかし、これは改行を置いた場合の解釈がどう扱われるかという事とは別です。
if-else if文の場合elseとifの間に改行を置くと、if-else if文はif-else文として解釈され、改行後のifはelse文の中のif文として解釈されます。

if~else if文のelseの後に改行がある例

public class IfElseSample {
	public static void main(String args[]) {
	
		int num = 1;
		if(num != 1)
			System.out.println("ここは印字されません");
		else if(num < 1)
			System.out.println("ここは印字されません");
		else
			if(num == 1)
				System.out.println("ここは印字されます");
		else
			if(num == 1)
				System.out.println("ここは印字されません");

	}
}

実行結果

>java IfElseSample
ここは印字されます

>

break文、continue文
for文やwhile文、do-while文ではbreak文やcontinue文を使って細かな分岐や繰返し制御が行えます。
break文はその場で実行中のループ抜けます。
ネストされたループの場合はbreak文の置かれたレベルの繰返しを終わるのであって、外側にも別の繰返しがあるケースですべての繰返しを終わりたい場合は外側の繰返しの先頭にラベルを置いて、break文でそのラベルを記述します。

loopExit:	for(int i=0; i<array.length; i++) {
				for(int J=0; j<array[i].length; j++) {
					if(array[i][j] == 99) break loopExit;
				}
			}

continue文はcontinue文以降の命令文をスキップして次の繰返し処理を継続実行します。continue分もラベルを使って処理の継続先を指定することが出来ます。

do-while文
doのあとにはセミコロンがなく実行ブロックが続きます。実行ブロックを{ }で括らない場合は、実行文は1文で2文以上を書くとコンパイルエラーになります。

プログラミングに戻る