目次 | | | 索引 Java言語規定
第2版

15. 式

プログラムの仕事のほとんどは,変数への代入などの副作用か,より大きい式において引数又はオペランドとして使用されるか,文の中の実行系列に影響を与える値のいずれか又は両方のために,式 (expressions) を評価することで行われる。

15.は,式の意味とそれらの評価規則を規定する。

15.1 評価,表示及び結果

プログラムの式を評価 (実行)するとき,その結果は,次の三つのうちどれか一つを指示する。

式の評価は副作用を発生させることがある。 なぜなら,式が埋込み代入,増分演算子,減分演算子及びメソッド呼出しを含むかもしれないからである。

値を返さないメソッド,すなわち void (8.4)を宣言するメソッドを呼び出すメソッド呼出し(15.12)に限り,式は何も指示しない。 そのような式は式文(14.8)としてだけ用いられる。 なぜなら,ある式が現れ得る他のあらゆる文脈は,何かを指示するためにその式を必要とするからである。 また,メソッド呼出しの式文は,結果を生成するメソッドを呼び出してよい。 この場合,メソッドが返す値は捨てられる。

値集合変換 (5.1.8) は,値を生成するすべての式の結果に適用される。

おのおのの式は,何らかの(クラス又はインタフェース)型の宣言に現れる。 すなわちフィールド初期化子,静的初期化子,コンストラクタ宣言,又はメソッドのためのコードに現れる。

15.2 値としての変数

もし式が変数を指示し,そして値がさらなる評価での使用に必要ならば,その変数の値は使用される。 この文脈では,その式が変数又は値を指示するならば,単に式の値 (value)と呼んでもよい。

もし型 float 又は型 double の変数の値がこのように使用されるならば,値集合変換(5.1.8)がその変数の値に適用される。

15.3 式の型

もし式が変数又は値を指示するならば,その式はコンパイル時に知らされる型をもつ。 式の型を決定するための規則は,それぞれの種類の式についてこの後に別々に説明する。

変数に記憶された値が変数の型に対して常に互換であるように,式の値は式の型に対して常に代入互換(5.2)とする。 すなわち,型が T である式の値は,常に型 T の変数への代入に適している。

型が final 宣言されたクラス型 F である式は,空参照又はそのクラスが F 自身であるオブジェクトのいずれかの値をもつことが保証されることに注意すること。 なぜなら,final 型は下位クラスを持たないからである。

15.4 FP厳密式

もし式の型が float 又は double であるならば,その式の値が,どの値集合(4.2.3)から導き出されるかについて疑問があるだろう。 これは値集合変換(5.1.8)の規則によって決定される。 すなわち,これらの規則は,式がFP厳密 (FP-strict)かどうかに依存する。

すべてのコンパイル時の定数式(15.28)は,FP厳密とする。 もし式がコンパイル時の定数式でないならば,その式を含むすべてのクラス宣言,インタフェース宣言,及びメソッド宣言を考慮する。 もしすべてのそのような宣言が strictfp 修飾子をもつならば,その式はFP厳密とする。

もしクラス,インタフェース又はメソッド X,が strictfp と宣言されるならば,XX の中の任意のクラス,インタフェース,メソッド,コンストラクタ,インスタンス初期化子,静的初期化子又は変数初期化子は,FP厳密 (FP-strict)であると呼ぶ。

式がコンパイル時の定数式でない及びそれが strictfp 修飾子をもつ任意の宣言の中に出現しない場合に限り,その式がFP厳密でないとする。

FP厳密式の中では,すべての計算途中の値が単精度数値集合又は倍精度数値集合の要素でなければならない。 なぜなら,すべてのFP厳密式の結果は,単精度及び倍精度を使用して表現されるオペランドについてのIEEE 754算術規則によって予測される結果でなければいけないことを意味するからである。 FP厳密でない式の中では,中間結果を表すために拡張指数範囲を使用する実装をある程度認めることができる。 これは,おおまかに言えば,単精度数値集合又は倍精度数値集合を排他的に使用するとオーバフロー又はアンダフローを引き起こすかもしれない状況下における最終的な影響は,計算が"正しい解答"を出すかもしれないことである。

15.5 式及び実行時検査

もし式の型がプリミティブ型ならば,その式の値は同様にプリミティブ型とする。 しかし,もし式の型が参照型ならば,被参照オブジェクトのクラスは,たとえその値が null 以外のオブジェクトの参照であろうと,コンパイル時にわかるとは限らない。 Javaプログラム言語では,被参照オブジェクトの実クラスが,式の型から推測できないような方法でプログラムの実行に影響することがある。

ここに列挙した最初の二つの場合では,決して型エラーを検出すべきではない。 したがって,実行時の型エラーは次のような状況でだけ起こりうる。

15.6 評価の正常完了及び中途完了

あらゆる式には,ある計算段階で実行される評価の正常モードがある。 以降の節では,それぞれの種類の式について評価の正常モードを示す。 例外を投げずにすべての段階が実行されるならば,この式は正常完了した (complete normally)と呼ばれる。

しかし,もしある式の評価が例外を投げるならば,その式は中途完了した (complete abruptly)と呼ばれる。 中途完了には,常にそれに関連する理由を持ち,それは与えられた値をもつ throw 節とする。

実行時例外は,次のようにあらかじめ定義された演算子によって投げられる。

もしメソッド本体の実行を中途完了させる例外が発生するならば,メソッド呼出しの式でも例外が投げられるようにすることができる。 また,もしコンストラクタを中途完了させる例外が発生したならば,クラスインスタンス生成式でも例外が投げられるようにすることができる。 また,式の評価中に様々なリンク及び仮想計算機のエラーが発生するかもしれない。 本来,そのようなエラーを予測及び処理するのは難しい。

もし例外が発生するならば,評価の正常モードのすべての段階が完了する前に,一つ又はそれ以上の式の評価が終了する。 そのような式は中途完了したと呼ぶ。 "正常完了する"と"中途完了する"という言葉は文(14.1)の実行にも適用される。 文は様々な理由で中途完了するが,例外が投げられるからとは限らない。

もし式の評価が副式の評価を必要とするならば,副式の中途完了は常に即座に同じ理由でその式自身の中途完了を引き起こし,評価の正常モードのその後のすべての段階は実行されない。

15.7 評価順序

Java言語は,演算子のオペランドが特定の評価順序 (evaluation order),すなわち左から右に評価されることを保証する。

コードがこの規定に依存しないことを推奨する。 最も外側の演算のように,それぞれの式がせいぜい一つの副作用しか含まない時及び式を左から右に評価した結果,どの例外が起こるかにコードがまったく依存しない時,コードは通常はより明確になる。

15.7.1 左辺オペランドを最初に評価

二項演算子の左辺オペランドは,右辺オペランドの部分が評価される前にすべて評価される。 例えば,左辺オペランドが変数への代入を含み,右辺オペランドがその同じ変数の参照を含むならば,参照の値は代入が先に発生した事実を反映する。

すなわち,次の例は,

class Test {
	public static void main(String[] args) {
		int i = 2;
		int j = (i=3) * i;
		System.out.println(j);
	}
}
次のように表示する。

9
9 の代わりに 6 を表示するのは許されない。

もし演算子が複合代入演算子(15.26.2)であるならば,左辺オペランドの評価は,左辺オペランドが指示する変数の記憶及び予想される結合した演算で使用するためにその変数の値を取得し保存することの両方を含む。 そして,例えば,次のようなテストプログラムは,

class Test {
	public static void main(String[] args) {
		int a = 9;
		a += (a = 3);	// first example
		System.out.println(a);
		int b = 9;
		b = b + (b = 3);	// second example
		System.out.println(b);
	}
}
次のように表示する。

12
12
なぜなら,二つのどちらの代入文も,加算の右辺オペランドが評価される前に左辺オペランドの値,すなわち 9 を取得及び保存し,その後変数を 3 に設定しているからである。 どちらの例も,6 という結果を生成するのは許されない。 ANSI/ISO規格のCでは,これらの例のどちらも振る舞いが規定されていないことに注意すること。

もし二項演算子の左辺オペランドの評価が中途完了すれば,右辺オペランドはまったく評価されない。

したがって,次のようなテストプログラムでは,

class Test {
	public static void main(String[] args) {
		int j = 1;
		try {
			int i = forgetIt() / (j = 2);
		} catch (Exception e) {
			System.out.println(e);
			System.out.println("Now j = " + j);
		}
	}
	static int forgetIt() throws Exception {
		throw new Exception("I'm outta here!");
	}
}
次のように表示する。

java.lang.Exception: I'm outta here!
Now j = 1
なぜなら,右辺オペランドを評価し,2j への埋込み代入が行われる前に,演算子 / の左辺オペランド forgetIt() が例外を投げるからである。

15.7.2 演算前にオペランド評価

Javaプログラム言語では,演算自身の任意の部分を実行する前に,演算子のあらゆるオペランド(条件演算子 &&|| 及び ? : を除く)が完全に評価されることを保証する。

もし二項演算子が整数除算 / (15.17.2)又は整数剰余 % (15.17.3)であるならば,その実行は ArithmeticException を起こすかもしれない。 しかし,二項演算子の両オペランドが評価され,評価が正常完了した場合に限り,この例外が投げられる。

したがって,例えば次のようなプログラムは,

class Test {
	public static void main(String[] args) {
		int divisor = 0;
		try {
			int i = 1 / (divisor * loseBig());
		} catch (Exception e) {
			System.out.println(e);
		}
	}
	static int loseBig() throws Exception {
		throw new Exception("Shuffle off to Buffalo!");
	}
}
常に次のように表示する。

java.lang.Exception: Shuffle off to Buffalo!
しかし,次のように表示されない。

java.lang.ArithmeticException: / by zero
なぜなら,その実装が除算演算が確かにゼロによる除算の例外を引き起こすことを検出又は推測できるかもしれない場合でさえも,loseBig の呼出しが終了する前にゼロによる除算の例外の送信を含む除算演算のどの部分も実行されないからである。

15.7.3 括弧及び優先順位に従う評価

Javaプログラム言語の実装は,括弧により明示的に及び演算子優先順位により暗黙に指示される評価の順番に注意しなければならない。 たとえ,(17.におけるスレッドの実行モデルを使用して)複数のスレッドで実行する場合でも,すべての可能な計算値において,置換式が値及び観測可能な副作用において同等であると証明することができない限り,ある実装が式をより都合のよい計算順序に書き直すために,結合則のような代数的同一性を利用してはいけない。

浮動小数点計算の場合,この規則は無限大及び数字でない(NaN)値においても適用される。 例えば,!(x<y)x>=y に書き直すことはできない。 この理由は,xy 又は両方がNaNならば,これらの式は異なった値をもつからである。

特に,数学的に結合性をもつ浮動小数点計算も計算論的には結合的でない。 単純にそのような計算の順序を変えてはならない。

例えば,Javaコンパイラが 4.0*x*0.52.0*x と書き直すのは正しくない。 ここでは丸め操作は問題にならないが,それは1番目の式は無限大(オーバフローによる)を生成するが,2番目の式は有限の結果を生むような大きな値 x が存在しうるからである。

したがって,例えば次のようなテストプログラムは,

strictfp class Test {
	public static void main(String[] args) {
		double d = 8e+307;
		System.out.println(4.0 * d * 0.5);
		System.out.println(2.0 * d);
	}
}
次のように表示する。

Infinity
1.6e+308
なぜなら,最初の式はオーバフローするが,2番目はそうならないからである。

対照的に,Javaプログラム言語においては,整数加算及び乗算はおそらく結合可能である

例えば,ab 及び c が局所変数である場合(ここでは,単に複数のスレッド及び volatile 値を含む問題が発生しないと仮定する。),a+b+c は,(a+b)+c 又は a+(b+c) のどちらとして評価されても,常に同じ答えを生成する。 式 b+c がそのコードの近くで起こるならば,賢いコンパイラはこの共通の副式を使用することができるかもしれない。

15.7.4 引数リストにおける左から右への評価

メソッド,コンストラクタ呼出し又はクラスインスタンス生成式において,複数の引数式が括弧内にコンマによって区切られて現れることができる。 それぞれの引数式は,その右にあるどんな引数式よりも前に完全に評価される。

したがって,

class Test {
	public static void main(String[] args) {
		String s = "going, ";
		print3(s, s, s = "gone");
	}
	static void print3(String a, String b, String c) {
		System.out.println(a + b + c);
	}
}
この例は常に次のように表示する。

going, going, gone
なぜなら,文字列 s への "gone" の代入は,print3 の最初の二つの引数が評価された後だからである。

引数式の評価が中途完了するならば,それより右の引数式は一切評価されない。

したがって,次の例は,

class Test {
	static int id;
	public static void main(String[] args) {
		try {
			test(id = 1, oops(), id = 3);
		} catch (Exception e) {
			System.out.println(e + ", id=" + id);
		}
	}
	static int oops() throws Exception {
		throw new Exception("oops");
	}
	static int test(int a, int b, int c) {
		return a + b + c;
	}
}
次のように表示する。

java.lang.Exception: oops, id=1
なぜなら,id への 3 の代入が実行されないからである。

15.7.5 他の式の評価順序

幾つかの式に対する評価順序は,これらの一般的規則に完全にはあてはまらない。 なぜなら,これらの式が,規定しなければならないような例外的な状況を時々生じさせることがあるからである。 特に次の種類の式に対する評価順序の詳細な例を参照すること。

15.8 一次式

一次式は最も単純な種類の式の大部分を含み,それ以外のすべて,すなわちリテラル,クラスリテラル,フィールドアクセス,メソッド呼出し及び配列アクセスは一次式によって構成される。 また括弧で括った式も,構文的に一次式として扱われる。

15.8.1 字句リテラル

リテラル(3.10)は固定された,不変の値を意味する。

便宜上,3.10の次の生成規則を繰り返す。

リテラルの型は,次のように決定される。

字句リテラルの評価は常に正常完了する。

15.8.2 クラスリテラル

クラスリテラルは,`.'とトークンクラスが後に続くクラス,インタフェース,配列又はプリミティブ型の名前から成る式とする。 クラスリテラルの型はClassとする。 現在のインスタンスのクラスのクラスローダを定義することで,名前付き型(又はvoid)のためのClassオブジェクトが定義されているものとして評価する。

15.8.3 this

キーワード this は,インスタンスメソッド,インスタンス初期化子又は コンストラクタの本体の中,又はクラスのインスタンス変数の初期化子の中だけで使用してよい。 もしそれ以外の場所で現れれば,コンパイル時エラーが発生する。

一次式として使用される時,キーワード this は値を指示する。 これは,インスタンスメソッドが呼び出されたオブジェクト(15.12)又は生成されたオブジェクトへの参照とする。 this の型は,内部でキーワード this が出現するクラス C とする。 実行時に,実際に参照するオブジェクトのクラスはクラス C 又は C の下位クラスとなる。

次の例では,

class IntVector {
	int[] v;
	boolean equals(IntVector other) {
		if (this == other)
			return true;
		if (v.length != other.v.length)
			return false;
		for (int i = 0; i < v.length; i++)
			if (v[i] != other.v[i])
				return false;
		return true;
	}
}
クラス IntVector は,二つのベクトルを比較するメソッド equals を実装する。 もしベクトル other が,equals メソッドを呼び出したのと同じベクトルオブジェクトとするならば,その検査では大きさと値の比較を省略できる。 equals メソッドは,other オブジェクトへの参照と this を比較して,この検査を実装する。

キーワード thisは,コンストラクタ本体(8.8.5)の先頭に現れる明示的なコンストラクタ呼出しの文で使用されることもある。

15.8.4 限定this

明示的にキーワード this で限定することで,どの字句的に取り囲むインスタンスも参照することができる。

C が,ClassName で指示されると仮定する。 また C が限定されたこの式が現れるクラスの n 番目の字句的に取り囲むクラスであるというように,n は整数であると仮定する。 ClassName.this という形式の式の値は,this (8.1.2)の n 番目に字句的に取り囲むインスタンスとする。 その式の型は C とする。 もし現在のクラスがクラス C の内部クラス又は C 自身でないならば,コンパイル時エラーが発生する。

15.8.5 括弧で括った式

括弧で括った式は,型が内包された式の型であり及び実行時の値が内包された式の値である一次式とする。 もし,内包された式が変数を指示するならば,括弧で括った式もその変数を指示する。

括弧は,型 float 又は double の式の値に対する数値集合(4.2.3)の選択に決して影響しない。

15.9 クラスインスタンス生成式

クラスインスタンス生成式は,クラスのインスタンスである新しいオブジェクトを生成するために使用される。

クラスインスタンス生成式は,次の二つの形式をもつ。

  • 非限定クラスインスタンス生成式 は,キーワード new で始まる。 非限定クラスインスタンス生成式は,クラスが最上位クラス(7.6), メンバクラス(8.59.5),局所クラス(14.3)又は匿名クラス(15.9.5)かどうかにかかわらず,クラスのインスタンスを生成するために使用してよい。
  • 限定クラスインスタンス生成式 は,Primary ではじまる。 限定クラスインスタンス生成式は,内部メンバクラス及びそれらの匿名下位クラスのインスタンスの生成を可能にする。 非限定及び限定クラスインスタンス生成式は,オプションでクラス本体で終了してもよい。 そのようなクラスインスタンス生成式は,匿名クラス (15.9.5)を宣言し,そのインスタンスを生成する。

    クラスのインスタンスがクラスインスタンス生成式によって生成される時,クラスが インスタンス化 されたと呼ぶ。 クラスのインスタンス化は,インスタンス化するクラス,新たに作成したインスタンスの取囲みインスタンス(存在するならば),新しいインスタンスを生成するために呼び出さねばならないコンストラクタ,及びそのコンストラクタに渡さねばならない実引数の決定を含む。

    15.9.1 インスタンス化されるクラスの決定

    もしクラスインスタンス生成式がクラス本体で終了するならば,インスタンス化されたクラスは匿名クラスとする。 したがって,次のように決定される。

    もしクラスインスタンス生成式が匿名クラスを宣言しないならば,

    クラスインスタンス生成式の型は,インスタンス化されるクラス型とする。

    15.9.2 取囲みインスタンスの決定

    C がインスタンス化されたクラスであり及び i が生成されたインスタンスであると仮定する。 もし,C が内部クラスであるならば,i は直接的に取り囲むインスタンスを持ってよい。 i の直接的に取り囲むインスタンス(8.1.2)は,次のように決定される。

    さらに,もし C が匿名クラスならば,CS の直接的上位クラスは内部クラスであり,i は次のように決定される S に関しては直接的に取り囲むインスタンスを持ってよい。

    15.9.3 コンストラクタと引数の選択

    C はインスタンス化されたクラス型と仮定する。 Ci のインスタンスを生成するために,C のコンストラクタはコンパイル時に次の規則によって選択される。

    呼び出されたコンストラクタが匿名コンストラクタの場合,そのクラスインスタンス生成式の型は匿名クラス型でよいことに注意すること。

    15.9.4 クラスインスタンス生成式の実行時評価

    実行時には,クラスインスタンス生成式の評価は次のようになる。

    最初に,もしクラスインスタンス生成式が限定クラスインスタンス生成式であるならば,その限定する基本式が評価される。 もしその限定する式を評価したら null になるならば,NullPointerException が投げられ,及びそのクラスインスタンス生成式は中途完了する。 もしその限定する式が中途完了するならば,そのクラスインスタンス生成式は同じ理由で中途完了する。

    次に,新しいクラスインスタンスのためにスペースが割り当てられる。 もしオブジェクトを割り当てるためのスペースが不十分ならば,そのクラスインスタンス生成式の評価は,OutOfMemoryError (15.9.6)を投げて,中途完了する。

    その新しいオブジェクトは,その規定されたクラス型とそのすべての上位クラスで宣言されるすべてのフィールドの新しいインスタンスを含む。 新しいフィールドインスタンスが生成されるときは,そのデフォルト値(4.5.5)に初期化される。

    次に,そのコンストラクタに対する実引数が,左から右に評価される。 もし実引数評価のいずれかが中途完了するならば,その右側の実引数式は評価されず,及びクラスインスタンス生成式も同じ理由で中途完了する。

    次に,その規定されたクラス型の選択されたコンストラクタが呼び出される。 これは,そのクラス型の各上位クラスに対して少なくとも一つのコンストラクタを呼び出す。 この過程は明示的なコンストラクタ呼出し文(8.6)によって指示でき,及び12.5で詳細に示す。

    クラスインスタンス生成式の値は,その規定されたクラスの新しく作り出されたオブジェクトへの参照とする。 式が評価される毎に,新しいオブジェクトが生成される。

    15.9.5 匿名クラス宣言

    匿名クラス宣言は,コンパイラによって,クラスインスタンス生成式から自動的に導き出される。

    匿名クラスは,abstract (8.1.1.1)では決してない。 匿名クラスは,常に内部クラス(8.1.2)であるが,static (8.1.1, 8.5.2)では決してない。 匿名クラスは,常に暗黙の final (8.1.1.2)とする。

    15.9.5.1 匿名コンストラクタ

    匿名クラスは,明示的に宣言されたコンストラクタをもつことはできない。 その代りに,コンパイラはその匿名クラスのために自動的に 匿名コンストラクタ を用意しなければならない。 直接的上位クラス S をもつ匿名クラス C の匿名コンストラクタの形式は,次に従う。

    すべての場合で,匿名コンストラクタのthrows節は,匿名コンストラクタの中に含まれる明示的な上位クラスコンストラクタ呼出し文によって投げられるすべての検査例外及びその匿名クラスのどのインスタンス初期化子又はインスタンス変数初期化子によって投げられるすべての検査例外を列挙しなければならない。

    その匿名クラスのシグネチャは,アクセス不可能型(例えば,もしそのような型が cs の上位クラスコンストラクタのシグネチャに現れる場合)を参照できることに注意すること。 これは,それ自身は,コンパイル時又は実行時のどちらかでエラーを起こさない。

    15.9.6 例: 評価順序及びメモリ不足の検出

    もしクラスインスタンス生成式の評価で生成操作の実行にメモリが不十分なことがわかれば,OutOfMemoryError が投げられる。 この検査は実引数式を評価する前に行う。

    例えば,そのテストプログラムは,

    class List {
    	int value;
    	List next;
    	static List head = new List(0);
    	List(int n) { value = n; next = head; head = this; }
    }
    class Test {
    	public static void main(String[] args) {
    		int id = 0, oldid = 0;
    		try {
    			for (;;) {
    				++id;
    				new List(oldid = id);
    			}
    		} catch (Error e) {
    			System.out.println(e + ", " + (oldid==id));
    		}
    	}
    }
    
    次のように表示する。

    java.lang.OutOfMemoryError: List, false
    
    なぜなら,その実引数式 oldid = id が評価される前に,メモリ不足条件が検出されたからである。

    これを,次元式(15.10.3)を評価した後にメモリ不足状態が検出された場合に対する配列生成式(15.10)の処理と比較すること。

    15.10 配列生成式

    配列インスタンス生成式は,新しい配列(10.)を生成するために使用される。

    配列生成式は,要素が PrimitiveType 又は TypeName によって規定された型である新しい配列であるオブジェクトを生成する。 TypeName は,abstract クラス型(8.1.1.1)又はインタフェース型(9.)さえも含む,どのような名前付き参照型を指定してもよい。

    その生成式の型は,new キーワード,任意の DimExpr 式及び配列初期化子が削除されたその生成式の複写によって指示されうる配列型とする。 例えば,次の生成式の型は,

    new double[3][3][]
    
    次の通りとする。

    double[][][]
    
    DimExpr の中のそれぞれの次元式の型は整数型でなければならず,そうでなければコンパイル時エラーが発生する。 それぞれの式は,単項数値昇格(5.6.1)を受ける。 その昇格型は int でなければならず,そうでなければコンパイル時エラーが発生する。 特に,これは次元式の型は long であってはならないことを意味する。

    もし配列初期化子が用意されているならば,10.6で示すように,新しく割り当てられた配列は配列初期化子によって用意された値で初期化される。

    15.10.1 配列生成式の実行時評価

    実行時に,配列生成式の評価は次のように実行される。 もし次元式がないならば,配列初期化子が存在しなければならない。 その配列初期化子の値は,配列生成式の値とする。 そうでなければ,次のように評価される。

    最初に次元式が左から右に評価される。 もし式の評価が中途完了するならば,その右の式は評価されない。

    次に次元式の値が検査される。 もし DimExpr 式の値がゼロより小さければ,NegativeArraySizeException が投げられる。

    次にスペースが新しい配列に割り当てられる。 もしその配列を割り当てるためのスペースが不足しているならば,OutOfMemoryError を投げて,配列生成式の評価は中途完了する。

    もし単一の DimExpr が現れるならば,一次元配列がその規定された長さで生成されて,及び配列の各構成要素はそのデフォルト値(4.5.5)に初期化される。

    もし配列生成式が N 個の DimExpr 式を含むならば,それは配列の含まれた配列を生成するために,深さ N-1の入れ子のループの組を実行する。

    例えば,次の宣言は,

    float[][] matrix = new float[3][3];
    
    動作においては次に等しい。

    float[][] matrix = new float[3][];
    for (int d = 0; d < matrix.length; d++)
    	matrix[d] = new float[3];
    
    及び,

    Age[][][][][] Aquarius = new Age[6][10][8][12][];
    
    は次に等しい。

    Age[][][][][] Aquarius = new Age[6][][][][];
    for (int d1 = 0; d1 < Aquarius.length; d1++) {
    	Aquarius[d1] = new Age[10][][][];
    	for (int d2 = 0; d2 < Aquarius[d1].length; d2++) {
    		Aquarius[d1][d2] = new Age[8][][];
    		for (int d3 = 0; d3 < Aquarius[d1][d2].length; d3++) {
    			Aquarius[d1][d2][d3] = new Age[12][];
    		}
    	}
    }
    
    dd1d2 及び d3 は,まだ局所的に宣言されていない名前に置換される。 このように,単一の new 式は,実際に長さ6の一つの配列,長さ10の六つの配列,長さ8の6 x 10 = 60の配列及び長さ12の6 x 10 x 8 = 480の配列を生成する。 この例では,5番目の次元が残されており,これは空参照だけに初期化された実際の配列要素(Age オブジェクトへの参照)を含む配列とする。 これらの配列は,後で次のような別のコードで代入することができる。

    Age[] Hair = { new Age("quartz"), new Age("topaz") };
    Aquarius[1][9][6][9] = Hair;
    
    多次元配列では,それぞれのレベルで同じ長さの配列をもつ必要はない。

    したがって,三角行列は,次のように生成してよい。

    float triang[][] = new float[100][];
    for (int i = 0; i < triang.length; i++)
    	triang[i] = new float[i+1];
    

    15.10.2 例: 配列生成評価順序

    配列生成式(15.10)の中には,それぞれの括弧の中に一つ以上の次元式があってもよい。 それぞれの次元式は,その右の次元式の前に完全に評価される。

    したがって,

    class Test {
    	public static void main(String[] args) {
    		int i = 4;
    		int ia[][] = new int[i][i=3];
    		System.out.println(
    			"[" + ia.length + "," + ia[0].length + "]");
    	}
    }
    
    は次のように表示する。

    [4,3]
    
    なぜなら,2番目の次元式が i3 を代入する前に,最初の次元が計算されて 4 になるからである。

    もし,次元式の評価が中途完了するならば,その右に現れる次元式の部分は一切評価されない。 したがって,次の例は,

    class Test {
    	public static void main(String[] args) {
    		int[][] a = { { 00, 01 }, { 10, 11 } };
    		int i = 99;
    		try {
    			a[val()][i = 1]++;
    		} catch (Exception e) {
    			System.out.println(e + ", i=" + i);
    		}
    	}
    	static int val() throws Exception {
    		throw new Exception("unimplemented");
    	}
    }
    
    次のように表示する。

    java.lang.Exception: unimplemented, i=99
    
    なぜなら i1 を代入する代入式は決して実行されないからである。

    15.10.3 例: 配列生成及びメモリ不足の検出

    もし配列生成式の評価で生成操作を実行するためにメモリが不十分であることを検出するならば,OutOfMemoryError が投げられる。 この検査は,すべての次元式の評価が正常完了した後にだけ起こる。

    例えば,次のテストプログラムは,

    class Test {
    	public static void main(String[] args) {
    		int len = 0, oldlen = 0;
    		Object[] a = new Object[0];
    		try {
    			for (;;) {
    				++len;
    				Object[] temp = new Object[oldlen = len];
    				temp[0] = a;
    				a = temp;
    			}
    		} catch (Error e) {
    			System.out.println(e + ", " + (oldlen==len));
    		}
    	}
    }
    
    次のように表示する。

    java.lang.OutOfMemoryError, true
    
    なぜなら,その次元式の oldlen = len が評価された後に,メモリ不足状態が検出されるからである。

    これと,実引数式(15.9.6)を評価する前に,メモリ不足状態を検出するクラスインスタンス生成式(15.9)を比較すること。

    15.11 フィールドアクセス式

    フィールドアクセス式は,オブジェクト又は配列のフィールドにアクセスでき,式又は特別なキーワード super のどちらかの値への参照とする (単純名を使用して現在のインスタンス又はクラスのフィールドを参照することもできる。 6.5.6を参照すること。)。

    フィールドアクセス式の意味は,限定名(6.6)と同じ規則を使用して決定されるが,式がパッケージ,クラス型又はインタフェース型を指示することができないという制限がある。

    15.11.1 一次式によるフィールドアクセス

    Primary の型は参照型 T でなければならず,そうでなければコンパイル時エラーが発生する。 そのフィールドアクセス式の意味は,次のように決定される。

    実行時に,参照される実際のオブジェクトのクラスではなく,その Primary 式の型だけが,使用するフィールドを決定する際に使用されることに注意すること。

    すなわち,次の例は,

    class S { int x = 0; }
    class T extends S { int x = 1; }
    class Test {
    	public static void main(String[] args) {
    		T t = new T();
    		System.out.println("t.x=" + t.x + when("t", t));
    		S s = new S();
    		System.out.println("s.x=" + s.x + when("s", s));
    		s = t;
    		System.out.println("s.x=" + s.x + when("s", s));
    	}
    	static String when(String name, Object t) {
    		return " when " + name + " holds a "
    			+ t.getClass() + " at run time.";
    	}
    }
    
    次のような出力を生成する。

    t.x=1 when t holds a class T at run time.
    s.x=0 when s holds a class S at run time.
    s.x=0 when s holds a class T at run time.
    
    この最終行は,実際にはアクセスされるフィールドは,その被参照オブジェクトの実行時のクラスに依存しないことを示す。 s がクラス T のオブジェクトに対する参照を持っていたとしても,その式 s.x はクラス S のフィールド x を参照する。なぜなら,式 s の型が S だからである。 クラス T のオブジェクトは,x と命名された二つのフィールドを含む。 一つはクラス T に対してであり,もう一つはその上位クラス S に対してとする。

    フィールドアクセスのために動的に検索しないので,簡単な実装でもプログラムは効率良く実行される。 遅延束縛と上書きの機能を利用できるが,インスタンスメソッドが使用されるときに限られる。 そのフィールドにアクセスするインスタンスメソッドを使用した次の同じ例について考える。

    class S { int x = 0; int z() { return x; } }
    class T extends S { int x = 1; int z() { return x; } }
    class Test {
    	public static void main(String[] args) {
    		T t = new T();
    		System.out.println("t.z()=" + t.z() + when("t", t));
    		S s = new S();
    		System.out.println("s.z()=" + s.z() + when("s", s));
    		s = t;
    		System.out.println("s.z()=" + s.z() + when("s", s));
    	}
    	static String when(String name, Object t) {
    		return " when " + name + " holds a "
    			+ t.getClass() + " at run time.";
    	}
    }
    
    この出力は次の通りである。

    t.z()=1 when t holds a class T at run time.
    s.z()=0 when s holds a class S at run time.
    s.z()=1 when s holds a class T at run time.
    
    この最終行は,実際にはアクセスされるメソッドが被参照オブジェクトの実行時クラスに依存することを示す。 s がクラス T のオブジェクトに対する参照を持っているとき,その式 s.z() は,s の型が S であるにもかかわらず,クラス T のメソッド z を参照する。 クラス T のメソッド z は,クラス S のメソッド z を上書きする。

    次の例は,例外を引き起こさずに,クラス(static)変数にアクセスするために空参照が使用される例を示す。

    class Test {
    	static String mountain = "Chocorua";
    	static Test favorite(){
    		System.out.print("Mount ");
    		return null;
    	}
    	public static void main(String[] args) {
    		System.out.println(favorite().mountain);
    	}
    }
    
    コンパイルした後に実行すると,次のように表示される。

    Mount Chocorua
    
    favorite() の結果が null であるにもかかわらず,NullPointerException は投げられない。 その表示される"Mount"は,その値でなく型だけがアクセスするフィールドの決定に使用されたにもかかわらず,実際には Primary 式が完全に評価されたことを示している(なぜなら,そのフィールド mountainstatic だからである。)。

    15.11.2 super による上位クラスメンバのアクセス

    キーワード super を使用する特別な形式は,インスタンスメソッド,コンストラクタ又はクラスのインスタンス変数の初期化子の中でだけ有効とする。 これはキーワード this を使用してよい状況とまったく同じ状況である(15.8.3)。 クラス Object は上位クラスを持たないので,super を含む形式は Object では使用してはいけない。 もし super がクラス Object に現れるならば,コンパイル時エラーが発生する。

    フィールドアクセス式 super.name がクラス C の中に現れ,及び C の直接的上位クラスがクラス S であると仮定する。 それならば super.name は,まさにそれが式 ((S)this).name のように扱われる。 したがって,それは現在のオブジェクトの name という名前のフィールドを参照するが,しかし現在のオブジェクトは上位クラスのインスタンスとして見られる。 したがって,たとえそのフィールドがクラス C の中の name というフィールド宣言によって隠ぺいされたとしても,クラス S で見える name という名前のフィールドにアクセスできる。

    super の使用方法を,次の例で示す。

    interface I { int x = 0; }
    class T1 implements I { int x = 1; }
    class T2 extends T1 { int x = 2; }
    class T3 extends T2 {
    	int x = 3;
    	void test() {
    		System.out.println("x=\t\t"+x);
    		System.out.println("super.x=\t\t"+super.x);
    		System.out.println("((T2)this).x=\t"+((T2)this).x);
    		System.out.println("((T1)this).x=\t"+((T1)this).x);
    		System.out.println("((I)this).x=\t"+((I)this).x);
    	}
    }
    class Test {
    	public static void main(String[] args) {
    		new T3().test();
    	}
    }
    
    これは,次の出力を生成する。

    x=		3
    super.x=		2
    ((T2)this).x=	2
    ((T1)this).x=	1
    ((I)this).x=	0
    
    クラス T3 の中では,その式 super.x はまさに次のようであるかのように扱われる。

    ((T2)this).x
    
    フィールドアクセス式 T.super.name がクラス C の中に現れ,及び T が指示するクラスの直接の上位クラスは全限定名が S であるクラスであると仮定する。 それならば,T.super.name は,それが式((S)T.this).name であるかのように厳密に処理される。

    したがって,式 T.super.name は,S という名前のクラスの中で見ることができる name という名前のフィールドに,そのフィールドが T という名前のクラスの中で name という名前のフィールドを宣言することで隠ぺいされた場合でさえも,アクセスできる。

    もし Tが指示するクラスがその現在のクラスの字句的に取り囲むクラスでない場合には,コンパイル時エラーが発生する。

    15.12 メソッド呼出し式

    メソッド呼出し式は,クラス又はインスタンスメソッドを呼出すために使用される。

    便宜上,15.9ArgumentList の定義を繰り返す。

    コンパイル時のメソッド名の解決は,メソッドオーバロードの可能性のため,フィールド名の解決より複雑になる。 実行時のメソッドの呼出しもまた,インスタンスメソッドの上書きの可能性のため,フィールドの参照より複雑になる。

    あるメソッド呼出し式により呼び出されるメソッドの決定は,幾つかの段階を含む。 次の三つの章は,メソッド呼出しのコンパイル時の処理を示す。 さらにメソッド呼出し式の型の決定は,15.12.3で示す。

    15.12.1 コンパイル時ステップ 1: 探索すべきクラス又はインタフェースの決定

    コンパイル時のメソッド呼出し処理の最初の段階は,呼び出すメソッドの名前と,その名前のメソッドの定義を調べるクラス又はインタフェースを明らかにする。 次のとおり,左括弧に先行する形式に応じて,幾つかの考慮すべき場合がある。

    15.12.2 コンパイル時ステップ 2: メソッドシグネチャの決定

    2番目の段階は,メソッド呼出しのために前の段階において決定されたクラス又はインタフェースを検索する。 この段階は,適用可能 (applicable) 及び参照可能 (accessible) なメソッド宣言,すなわち,その与えられた実引数で正しく呼び出されることができる宣言を配置するために,そのメソッドの名前及び実引数式の型を使用する。 そのときには最も特殊 (most specific) なものが選択される場合は,そのようなメソッド宣言が複数存在してよい。 最も特殊なメソッド宣言の記述子(シグネチャと返却値の型)は,実行時にメソッドディスパッチを行うために使用される。

    15.12.2.1 適用可能及び参照可能なメソッドの探索

    メソッド宣言は,次の二つが成り立つ場合に限り,メソッド呼出しに対して 適用可能 (applicable) とする。

    15.12.1で示された過程により決定されたクラス又はインタフェースが,このメソッド呼出しに適用可能なすべてのメソッド宣言について検索される。 つまり,上位クラス及び上位インタフェースから継承したメソッド定義は,この検索に含まれる。

    メソッド宣言がメソッド呼出しから参照可能 (accessible) (6.6)かどうかは,そのメソッド宣言のアクセス修飾子(public,なし,protected 又は private )及びメソッド呼出しの現れる場所に依存する。

    もしクラス又はインタフェースが適用可能及び参照可能なメソッド宣言を持たないならば,コンパイル時エラーが発生する。

    次の例題プログラムにおいては,

    public class Doubler {
    	static int two() { return two(1); }
    	private static int two(int i) { return 2*i; }
    }
    class Test extends Doubler {	
    	public static long two(long j) {return j+j; }
    	public static void main(String[] args) {
    		System.out.println(two(3));
    		System.out.println(Doubler.two(3)); // compile-time error
    	}
    }
    
    クラス Doubler 内のメソッド呼出し two(1) に対し,two と命名された二つの参照可能なメソッドがあるが,2番目だけが適用可能であり,したがってそれが実行時に呼び出される。 クラス Test 内のメソッド呼出し two(3) に対しては二つの適用可能なメソッドがあるが,クラス Test 内のものだけがアクセス可能であり,したがって実行時に呼び出される(その実引数3は型 long に変換される。)。 メソッド呼出し Doubler.two(3) に対しては,クラス Test ではなくクラス Doubler が,two と命名されたメソッドのために検索される。 しかし,唯一の適用可能なメソッドがアクセス可能ではないので,このメソッド呼出しはコンパイル時エラーを発生する。

    別の例を次に示す。

    class ColoredPoint {
    	int x, y;
    	byte color;
    	void setColor(byte color) { this.color = color; }
    }
    class Test {
    	public static void main(String[] args) {
    		ColoredPoint cp = new ColoredPoint();
    		byte color = 37;
    		cp.setColor(color);
    		cp.setColor(37);	// compile-time error
    	}
    }
    
    ここで,コンパイル時に適用可能なメソッドが見つからないために,setColor の2番目の呼出しでコンパイル時エラーが発生する。 リテラル 37 の型は int であり,メソッド呼出し変換では intbyte 型に変換できない。 その変数 color の初期化で使用される代入変換は,int 型から byte 型へ定数の暗黙の変換を実行する。 なぜなら,その値 37byte 型で表すのに十分小さいから許されている。 しかし,メソッド呼出し変換ではそのような変換は認められない。

    しかし,もしメソッド setColorbyte の代わりに int をとるように宣言されていたならば,メソッド呼出しは両方とも正しい。 つまり,最初の呼出しはメソッド呼出し変換が byte から int へ拡張する変換を許すために可能とする。 しかし,縮小キャストが setColor の本体には必要とする。

    	void setColor(int color) { this.color = (byte)color; }
    

    15.12.2.2 最も特殊なメソッドの選択

    もし複数のメソッドがメソッド呼出しに対してアクセス可能及び適用可能ならば,実行時メソッドディスパッチのための記述子にその内の一つを与える必要がある。 Javaプログラム言語は,最も特殊な (most specific) メソッドを選ぶという規則を用いている。

    非公式な直観としては,もし最初のメソッドによって処理されるあらゆる呼出しがコンパイル時型エラーなしに他に渡されることができるならば,そのメソッド宣言はより特殊とする。

    正確な定義を次に示す。 m を名前とすると,m と命名された二つのメソッド宣言があり,それぞれが n 個の仮引数をもつと仮定する。 もし一つの宣言がクラス又はインタフェース T 内に現れ,その仮引数の型は T1, . . . , Tn であり,さらにもう一方の宣言がクラス又はインタフェース U 内に現れ,その仮引数の型は U1,. . . , Un とする。 それならば,次が共に成り立つ時に限り,T の中で宣言されたメソッド m は,U の中で宣言されたメソッド m よりも特殊 (more specific)とする。

    もしメソッドが適用可能かつアクセス可能で,その他には,より特殊で適用可能及びアクセス可能なメソッドがないならば,最大限に特殊 (maximally specific) であると呼ばれる。

    もしちょうど一個の最大限に特殊なメソッドがあるならば,それは実際に 最も特殊 (the most specific)なメソッドとする。 それは必然的に,適用可能でアクセス可能な他のどのメソッドよりも特殊とする。 したがって,それは15.12.3で示されたように,さらにコンパイル時の検査が必要とする。

    二つ又はそれ以上の最大限に特殊なメソッド宣言があるために,どのメソッドも最も特殊ではないことがある。 この場合を,次に示す。

    15.12.2.3 例: オーバロードのあいまいさ

    次の例について考える。

    class Point { int x, y; }
    class ColoredPoint extends Point { int color; }
    
    class Test {
    	static void test(ColoredPoint p, Point q) {
    		System.out.println("(ColoredPoint, Point)");
    	}
    	static void test(Point p, ColoredPoint q) {
    		System.out.println("(Point, ColoredPoint)");
    	}
    	public static void main(String[] args) {
    		ColoredPoint cp = new ColoredPoint();
    		test(cp, cp);	// compile-time error
    	}
    }
    
    この例はコンパイル時にエラーを生じさせる。 この問題は,適用可能かつアクセス可能な test の二つの宣言があり,どちらも他方より特殊ではないことである。 それゆえ,このメソッド呼出しはあいまいとする。

    もし test の3番目の宣言を加えるならば,

    	static void test(ColoredPoint p, ColoredPoint q) {
    		System.out.println("(ColoredPoint, ColoredPoint)");
    	}
    
    これは他の二つより特殊になるので,このメソッド呼出しはもはやあいまいではない。

    15.12.2.4 例: 返却値の型の無視

    別の例として,次を考える。

    class Point { int x, y; }
    class ColoredPoint extends Point { int color; }
    class Test {
    	static int test(ColoredPoint p) {
    		return p.color;
    	}
    	static String test(Point p) {
    		return "Point";
    	}
    	public static void main(String[] args) {
    		ColoredPoint cp = new ColoredPoint();
    		String s = test(cp);	// compile-time error
    	}
    }
    
    ここで,メソッド test の最も特殊な宣言は,型 ColoredPoint を仮引数にとるものとする。 メソッドの返却値の型は int であり,int は割り当て変換によって String には変換できないためにコンパイル時エラーが発生する。 この例は,メソッドの返却値の型はオーバロードされたメソッドの解決には関与しないことを示す。 String を返却する2番目の test メソッドは,この例のプログラムをエラーなしにコンパイルできるようにする返却値の型をもつ場合でさえも,選択されない。

    15.12.2.5 例: コンパイル時の解決

    コンパイル時に,最も適用可能なメソッドが選ばれる。 その記述子は,実行時にどのメソッドが実際に実行されるかを決定する。 もし新しいメソッドがクラスに追加されるならば,たとえ再コンパイルすれば新たなメソッドが選ばれるようになるとしても,そのクラスの古い定義でコンパイルされたソースコードはその新しいメソッドを使用しない。

    例として,二つのコンパイル単位を考える。一つは クラスPoint とし,

    package points;
    public class Point {
    	public int x, y;
    	public Point(int x, int y) { this.x = x; this.y = y; }
    	public String toString() { return toString(""); }
    	public String toString(String s) {
    		return "(" + x + "," + y + s + ")";
    	}
    }
    
    そしてもう一つはクラスColoredPointとする。

    package points;
    public class ColoredPoint extends Point {
    	public static final int
    		RED = 0, GREEN = 1, BLUE = 2;
    	public static String[] COLORS =
    		{ "red", "green", "blue" };
    	public byte color;
    	public ColoredPoint(int x, int y, int color) {
    		super(x, y); this.color = (byte)color;
    	}
    	/** Copy all relevant fields of the argument into
    		    this ColoredPoint object. */
    	public void adopt(Point p) { x = p.x; y = p.y; }
    	public String toString() {
    		String s = "," + COLORS[color];
    		return super.toString(s);
    	}
    }
    
    ここで ColoredPoint を用いる3番目のコンパイル単位を考える。

    import points.*;
    class Test {
    	public static void main(String[] args) {
    		ColoredPoint cp =
    			new ColoredPoint(6, 6, ColoredPoint.RED);
    		ColoredPoint cp2 =
    			new ColoredPoint(3, 3, ColoredPoint.GREEN);
    		cp.adopt(cp2);
    		System.out.println("cp: " + cp);
    	}
    }
    
    この出力は次の通りである。

    cp: (3,3,red)
    
    クラス Test を実装したアプリケーションプログラマは,単語 green を期待していた。 なぜなら,実引数 ColoredPointcolor フィールドを持ち,color は"適切なフィールド"のように見えるからである(もちろん,パッケージ Points に対するドキュメントはもっと正確であるべきとする。)。

    ところで,adopt のメソッド呼出しのために最も特殊なメソッド(実際には,唯一の適用可能なメソッド)は一個の仮引数のメソッドを示すシグネチャを持ち,その仮引数は Point 型であることに注意すること。 このシグネチャは,コンパイラにより生成される Test クラスのバイナリ表現の一部となり,実行時にメソッド呼出しに使用される。

    プログラマがこのソフトウェアエラーを報告し,points パッケージの管理者が,相応の考慮の後,クラス ColoredPoint に次のメソッドを追加することにより訂正することを決めたと仮定する。

    public void adopt(ColoredPoint p) {
    	adopt((Point)p); color = p.color;
    }
    
    もしアプリケーションプログラマが,Test の古いバイナリファイルを ColoredPoint の新しいバイナリファイルとともに実行するならば,出力は次のように変化しない。

    cp: (3,3,red)
    
    なぜなら Test の古いバイナリファイルが,メソッド呼出し cp.adopt(cp2) に関係する"仮引数一個,型は Point; void"という記述子をまだ持っているからである。 もし Test のソースコードを再コンパイルするならば,コンパイラが今度は二つの適用可能な adopt メソッドがあり,最も特殊なメソッドのシグネチャが"仮引数一個,型は ColoredPoint; void"であることを発見する。 プログラムを実行すると,今度は望ましい出力を生成する。

    cp: (3,3,green)
    
    この問題についてよく考えると,points パッケージの管理者は,ColoredPoint クラスを,まだ ColoredPoint の実引数で起動される古いコードのために古い adopt メソッドに防御的なコードを追加することにより,新たにコンパイルされたコードと古いコードの両方で動作するように修正することができる。

    public void adopt(Point p) {
    	if (p instanceof ColoredPoint)
    		color = ((ColoredPoint)p).color;
    	x = p.x; y = p.y;
    }
    
    理想的には,ソースコードは,それが依存しているコードが変更されたときに常に再コンパイルされるべきとする。 しかし,異なるクラスが異なる組織で管理されている環境では,これはいつも実現可能ではない。 クラスの機能変更の問題に対して注意深く考慮する防御的プログラミングは,改良されたコードをより頑強にすることができる。 バイナリ互換性と型の機能変更に関する詳細な議論については13.を参照すること。

    15.12.3 コンパイル時ステップ3: 選ばれたメソッドの適切性

    もしメソッド呼出しのための最も特殊なメソッド宣言があるならば,それはそのメソッド呼出しのためのコンパイル時宣言 (compile-time declaration) と呼ばれる。 コンパイル時宣言においては,さらに次の三つの検査を行わねばならない。

    次のコンパイル時情報が,実行時の使用のためにそのメソッド呼出しと関連づけられる。

    もしそのメソッド呼出しに対するコンパイル時宣言が void でないならば,そのメソッド呼出しの式の型はコンパイル時宣言で規定される返却値の型とする。

    15.12.4 メソッド呼出しの実行時評価

    実行時に,メソッド呼出しは五つの段階を必要とする。 最初に,ターゲットへの参照 (target reference) を計算してよい。 2番目に,その引数式が評価される。 3番目に,呼び出されるメソッドのアクセス可能性が検査される。 4番目に,メソッドが実行される実際のコードを探す。 5番目に,新しい活性化フレームが作成され,必要なら同期処理が実行され,制御がメソッドコードに移される。

    15.12.4.1 ターゲット参照の計算(必要な場合)

    MethodInvocation (15.12)の四つの生成規則のどれに関係するのかによって,幾つかの考えられる事例が存在する。

    15.12.4.2 実引数の評価

    実引数は,左から右に順に評価される。 もしどれかの実引数式の評価が中途完了するならば,その右にある実引数の評価は行われず,同じ理由でそのメソッド呼出しも中途完了する。

    15.12.4.3 型及びメソッドのアクセス可能性の検査

    C はメソッド呼出しを含むクラスであり,及び T はそのメソッド呼出しの限定型(13.1)であり,及び m はコンパイル時に定義されるそのメソッドの名前(15.12.3)だと仮定する。 Javaプログラム言語の実装は,そのメソッド m が型 T の中にまだ存在することを,リンクの一部として,保証しなければならない。 もしこれが真でないならば,NoSuchMethodError (これは IncompatibleClassChangeError の下位クラスである)が発生する。 もしその呼出しのモードが interface ならば,その実装はターゲットの参照型がその規定されたインタフェースを既に実装しているかについても検査しなければならない。 もしそのターゲットの参照型がまだインタフェースを実装していないならば,IncompatibleClassChangeError が発生する。

    その実装は,リンクの間に,その型 T 及びそのメソッド m がアクセス可能であることも保証しなければならない。 型 T に対しては,次の通りとする。

    メソッド m に対して,次の通りとする。

    T 又は m がアクセス不可能な場合,IllegalAccessError が発生する(12.3)。

    15.12.4.4 呼出しメソッドの検索

    メソッド検索のための戦略は,呼出しのモードに依存する。

    もし呼出しのモードが static であるならば,ターゲットの参照は必要なく,上書きも許されない。 クラス T のメソッド m が呼び出されるメソッドになる。

    そうでなければ,インスタンスメソッドが呼び出され,ターゲットの参照が存在する。 もしターゲット参照が null であるならば,この時点で NullPointerException が投げられる。 そうでなければ,そのターゲットの参照は ターゲットオブジェクト (target object) を参照すると言い,及び呼び出されたメソッドで this キーワードの値として使用される。 呼出しのモードのための他の四つの可能性を検討する。

    もし呼出しのモードが nonvirtual であるならば, 上書きは許されない。 クラス T のメソッド m が呼び出されるべきとする。

    そうでなければ,呼出しのモードは interfacevirtual 又は super のいずれかになり,上書きされるかもしれない。 その場合,動的なメソッド検索 (dynamic method lookup) が使用される。 動的な検索の過程はクラス S から開始され,次のように決定される。

    動的なメソッド検索では,メソッド m を探すために,クラス S,及び必要ならばクラス S の上位クラスを検索する次の手順を使用する。

    X が,そのメソッド呼出しのターゲット参照のコンパイル時型だと仮定する。

    1. もしクラス S がコンパイル時(15.12.3)に決定されるメソッド呼出しに必要なのと同じ(同じ数の仮引数,同じ仮引数型及び同じ返却値の型)記述子をもつ m という名前の非抽象メソッドの宣言を含むならば,
      • もし呼出しモードが super 又は interface であるならば,そのメソッドを呼び出し,手続きは終了する。
      • もし呼出しモードが virtual であり,及び S 内の宣言が X.m を上書き(8.4.6.1)するならば,S 内で宣言したメソッドが呼び出されるメソッドであり,手続きは終了する。
    2. そうでなければ,もし S が上位クラスをもつならば,この同じ検索手順が S の代りに S の直接上位クラスを使用して再帰的に実行される。 呼び出されるメソッドは,この検索手順の再帰呼出しの結果とする。
    上記の手順は,常に非抽象でアクセス可能なメソッドを呼び出すために見つけ出し,適切にコンパイルされたプログラム内のすべてのクラス及びインタフェースを提供する。 しかし,もしこれ以外の事例であるならば,さまざまなエラーが発生するかもしれない。 この環境下のJava仮想計算機の振る舞いの規定は,The Java Virtual Machine Specification, Second Edition によって定義される。

    ここで明示的に示した動的検索過程が,例えば,クラスごとのメソッドディスパッチ表の生成と使用又は効率的なディスパッチのために使用されるクラスごとの他のコンストラクタの生成の副作用として,しばしば暗黙的に実装されるということには注意を要する。

    15.12.4.5 フレームの生成,同期及び制御の移行

    あるクラス S のメソッド m が呼び出されると確認されたとする。

    次に,新しい 活性化フレーム (activation frame) が作成される。 これは局所変数用の領域,呼び出されるメソッドが使用するスタック,その他すべての実装に必要な情報一覧(スタックポインタ,プログラムカウンタ,前の活性化フレームへの参照など)のための領域とともに,ターゲット参照(存在する場合)及び実引数値(存在する場合)を含む。 もし,そのような活性化フレームを生成するのに利用可能な十分なメモリがないならば,OutOfMemoryError が投げられる。

    新しく作り出された活性化フレームは,現在の活性化フレームになる。 これにより,実引数値を新たに作り出された対応するメソッドの仮引数変数に割当てて,もし存在すれば this として利用可能なターゲット参照を作成する。

    各実引数値がそれに対応する仮引数変数に割り当てられる前に,任意の要求された値集合変換(5.1.8)を含むメソッド呼出し変換(5.3)がおこなわれる。 もしそのメソッド mnative メソッドであり,必要なネイティブの実装依存なバイナリコードがロードされていない,又はそうでなくても動的にリンクすることができないならば,UnsatisfiedLinkError が投げられる。

    もしそのメソッド msynchronized 宣言されていないならば,制御は呼び出されるメソッド m の本体に移る。

    もしそのメソッド m がsynchronized宣言されているならば,制御の移動の前にオブジェクトがロック設定されなければならない。 カレントスレッドがロックを獲得するまでは,それ以上処理を進めることはできない。 もしターゲット参照があるならば,ロック設定されなければならない。 そうでなければ,そのメソッド m が存在するクラス S のための Class オブジェクトがロック設定されなければならない。 そして,制御は呼び出されるメソッド m の本体に移される。 オブジェクトは正常完了,中途完了に関わらず,メソッド本体の実行が終了したとき,自動的にロック解除される。 ロック設定及びロック解除は,ちょうどそのメソッドの本体がsynchronized文(14.18)の中に埋め込まれているかのように振る舞う。

    15.12.4.6 例: ターゲット参照及び静的メソッド

    ターゲットの参照が計算され,及び呼出しのモードが static なのでそれが捨てられるとき,その参照が null であるかどうかは検査されない。

    class Test {
    	static void mountain() {
    		System.out.println("Monadnock");
    	}
    	static Test favorite(){
    		System.out.print("Mount ");
    		return null;
    	}
    	public static void main(String[] args) {
    		favorite().mountain();
    	}
    }
    
    これは次のように表示される。

    Mount Monadnock
    
    ここで favoritenull を返すが,NullPointerException は投げられない。

    15.12.4.7 例: 評価の順序

    インスタンスメソッド呼出し(15.12)の一部として,呼び出されるオブジェクトを指示する式がある。 この式はメソッド呼出しの実引数式のどの部分よりも前に完全に評価される。

    したがって,例えば,次の例は

    class Test {
    	public static void main(String[] args) {
    		String s = "one";
    		if (s.startsWith(s = "two"))
    			System.out.println("oops");
    	}
    }
    
    ".startsWith"の前の s は,実引数である s="two" よりも先に最初に評価される。 したがって,ターゲット参照としての文字列 "one" への参照は,局所変数sが文字列 "two" への参照に変更される前に記憶される。 その結果,startsWith メソッドは,実引数 "two" を持ったターゲットオブジェクト "one" に対して呼び出され,文字列 "one""two" で始まらないので,呼出しの結果は false とする。 したがって,テストプログラムは oops を表示しない。

    15.12.4.8 例: 上書き

    次の例において,

    class Point {
    	final int EDGE = 20;
    	int x, y;
    	void move(int dx, int dy) {
    		x += dx; y += dy;
    		if (Math.abs(x) >= EDGE || Math.abs(y) >= EDGE)
    			clear();
    	}
    	void clear() {
    		System.out.println("\tPoint clear");
    		x = 0; y = 0;
    	}
    }
    class ColoredPoint extends Point {
    	int color;
    	void clear() {
    		System.out.println("\tColoredPoint clear");
    		super.clear();
    		color = 0;
    	}
    }
    
    下位クラス ColoredPoint は,その上位クラス Point で定義された抽象化 clear を継承する。 これは clear メソッドを自分のメソッドで上書きし,それは super.clear の形式を用いて上位クラスの clear メソッドを呼び出している。

    clear の呼出しのターゲットオブジェクトが ColoredPoint であるときには常にこのメソッドが呼び出される。 さらに this のクラスは ColoredPoint であるとき,Point の中の move メソッドが codeColoredPointclear メソッドを呼び出し,次のテストプログラムは,

    class Test {
    	public static void main(String[] args) {
    		Point p = new Point();
    		System.out.println("p.move(20,20):");
    		p.move(20, 20);
    		ColoredPoint cp = new ColoredPoint();
    		System.out.println("cp.move(20,20):");
    		cp.move(20, 20);
    		p = new ColoredPoint();
    		System.out.println("p.move(20,20), p colored:");
    		p.move(20, 20);
    	}
    }
    
    次のように表示する。

    p.move(20,20):
    	Point clear
    cp.move(20,20):
    	ColoredPoint clear
    	Point clear
    p.move(20,20), p colored:
    	ColoredPoint clear
    	Point clear
    
    上書きは,"遅延束縛された自己参照"と呼ばれることがある。 この例では,Point.move (これは本当は this.clear の構文上の略記である)の中の clear への参照は,(コンパイル時に this の型に基づいて)"早く"選択されたメソッドではなく,(実行時に this で参照されるオブジェクトの実行時クラスに基づいて)"遅く"選択されたメソッドを呼び出すことを意味する。 これはプログラマに抽象化を拡張する強力な方法を提供するとともに,オブジェクト指向プログラミングの重要な考えでもある。

    15.12.4.9 例: super を用いたメソッド呼出し

    上位クラスの上書きされたインスタンスメソッドは,その直接の上位クラスのメンバにアクセスするためにキーワード super を使用することによって,そのメソッド呼出しを含むクラス中のどの上書き宣言も無視してアクセスしてもよい。

    インスタンス変数にアクセスするとき,superthis (15.11.2)をキャストしたものに等しいが,この等価性はメソッド呼出しに当てはまらない。 これを次の例で示す。

    class T1 {
    	String s() { return "1"; }
    }
    class T2 extends T1 {
    	String s() { return "2"; }
    }
    class T3 extends T2 {
    	String s() { return "3"; }
    	void test() {
    		System.out.println("s()=\t\t"+s());
    		System.out.println("super.s()=\t"+super.s());
    		System.out.print("((T2)this).s()=\t");
    			System.out.println(((T2)this).s());
    		System.out.print("((T1)this).s()=\t");
    			System.out.println(((T1)this).s());
    	}
    }
    class Test {
    	public static void main(String[] args) {
    		T3 t3 = new T3();
    		t3.test();
    	}
    }
    
    この次の出力を生成する。

    s()=		3
    super.s()=	2
    ((T2)this).s()=	3
    ((T1)this).s()=	3
    
    T1 及び T2 へのキャストは,呼び出されるメソッドを変更しない。 なぜなら,呼び出されるべきインスタンスメソッドは,this によって参照されるオブジェクトの実行時のクラスによって選択されるからである。 キャストはオブジェクトのクラスを変えない。 したがって,そのクラスがその規定された型と互換性があるかどうかを検査するだけとする。

    15.13 配列アクセス式

    配列アクセス式は配列の構成要素である変数を参照する。

    配列アクセス式は,左角括弧の前の 配列参照式 (array reference expression) 及び角括弧の中の インデクス式 (index expression) の二つの副式からなる。 配列参照式は,名前又は配列生成式(15.10)でない一次式でもよいことに注意すること。

    配列参照式の型は配列型(構成要素の型が T の配列をT[] と呼ぶ)でなければならない。 そうでなければ,コンパイル時エラーが発生する。 したがって,配列アクセス式の型は T とする。

    インデクス式は,単項数値昇格(5.6.1)を受け,その昇格された型は int でなければならない。

    配列参照の結果は型 T の変数,すなわち,インデクス式の値によって選択される配列中の変数とする。 この結果として生じる変数は,その配列の構成要素であり,配列参照が final 変数から得られたとしても,決して final とは考えられない。

    15.13.1 配列アクセスの実行時評価

    配列アクセス式は次の手順を用いて評価される。

    15.13.2 例: 配列アクセス評価順序

    配列アクセスでは,その角括弧の左の式は,角括弧の中の式のどの部分よりも前に完全に評価される。 例えば,(あきらかに病的な)式 a[(a=b)[3]] では,式 a が式(a=b)[3]よりも前に完全に評価される。 これは,式 (a=b)[3] が評価される間,式 a の元の値が取得及び記憶されていることを意味する。 したがって,a の元の値によって参照されたこの配列は,b によって参照されており及び今は a によっても参照されているもう一つの配列(たぶん同じ配列)の 3 番目の要素の値によってインデクスされる。

    したがって,次の例は,

    class Test {
    	public static void main(String[] args) {
    		int[] a = { 11, 12, 13, 14 };
    		int[] b = { 0, 1, 2, 3 };
    		System.out.println(a[(a=b)[3]]);
    	}
    }
    
    次のように表示する。

    14
    
    なぜならその病的な式の値は,a[b[3]]a[3] 又は 14 と等価であるからである。

    もしその角括弧の左の式の評価が中途完了するならば,その角括弧の中の式のどの部分も評価されない。 したがって,次の例は,

    class Test {
    	public static void main(String[] args) {
    		int index = 1;
    		try {
    			skedaddle()[index=2]++;
    		} catch (Exception e) {
    			System.out.println(e + ", index=" + index);
    		}
    	}
    	static int[] skedaddle() throws Exception {
    		throw new Exception("Ciao");
    	}
    }
    
    次のように表示する。

    java.lang.Exception: Ciao, index=1
    
    なぜなら index への 2 の埋込み代入は決して起こらないからである。

    もしその配列参照式が配列への参照の代わりに null を生成するならば,実行時に NullPointerException が投げられる。 しかし,その配列参照式のすべての部分が評価され,これらの評価が正常完了したあとに限る。 したがって,次の例は,

    class Test {
    	public static void main(String[] args) {
    		int index = 1;
    		try {
    			nada()[index=2]++;
    		} catch (Exception e) {
    			System.out.println(e + ", index=" + index);
    		}
    	}
    	static int[] nada() { return null; }
    }
    
    次のように表示する。

    java.lang.NullPointerException, index=2
    
    なぜなら index への 2 の埋込み代入は,空ポインタの検査の前に起こるからである。 関係する例として,次のプログラムは,

    class Test {
    	public static void main(String[] args) {
    		int[] a = null;
    		try {
    			int i = a[vamoose()];
    			System.out.println(i);
    		} catch (Exception e) {
    			System.out.println(e);
    		}
    	}
    	static int vamoose() throws Exception {
    		throw new Exception("Twenty-three skidoo!");
    	}
    }
    
    常に次のように表示する。

    java.lang.Exception: Twenty-three skidoo!
    
    NullPointerExceptionは決して起きない。 なぜなら,左辺のオペランドの値が null かどうかの検査を含むそのインデクス操作が起こる前に,そのインデクス式が完全に評価されるからである。

    15.14 後置式

    後置式は ++ 及び -- の後置演算子の使用を含む。 また,15.8で議論したように,名前を一次式とは考えずに,文法におけるあいまいさを避けるために区別して扱う。 それらは,ここに限れば後置式の優先順位のレベルで交換可能になる。

    15.14.1 後置増分演算子 ++

    ++ 演算子があとに続いた後置式は後置増分式とする。 その後置式の結果は数値型の変数でなければならない。 そうでなければコンパイル時エラーが発生する。 その後置増分式の型は,その変数の型とする。 後置増分式の結果は,変数ではなく,値とする。

    実行時に,もしそのオペランド式の評価が中途完了するならば,その後置増分式も同じ理由で中途完了し,増分は起こらない。 そうでなければ,値 1 が変数の値に加えられ,その合計がその変数に記憶される。 その加算の前に,二項数値昇格(5.6.2)がその値 1 及びその変数の値に実行される。 必要ならば,その合計は,記憶される前にその変数の型へのプリミティブ型の縮小変換(5.1.3)によって縮小される。 その後置増分式の値は,その新しい値が記憶されるの変数の値とする。

    上記の二項数値昇格は,数値集合変換(5.1.8)を含むかもしれないことに注意すること。 必要ならば,数値集合変換は,その加算に対して,それが変数に記憶される前に適用される。

    final と宣言された変数を加算することはできない。 なぜなら,final 変数のアクセスが式として使用されたとき,その結果は値であり,変数ではないからである。 したがって,それを後置増分演算子のオペランドとして使用することはできない。

    15.14.2 後置減分演算子 --

    -- 演算子があとに続いた後置式は後置減分式とする。 その後置式の結果は数値型の変数でなければならない。 そうでなければコンパイル時エラーが発生する。 その後置減分式の型は,その変数の型とする。 後置減分式の結果は,変数ではなく値とする。

    実行時に,もしオペランド式の評価が中途完了するならば,その後置減分式も同じ理由で中途完了し,減分は起こらない。 そうでなければ,値 1 が変数の値から減じられ,その差分がその変数に記憶される。 その減算の前に,二項数値昇格(5.6.2)がその値 1 及びその変数の値に実行される。 必要ならば,その差分は,その新しい値が記憶される前に,その変数の型へのプリミティブ型の縮小変換(5.1.3)によって縮小される。 後置減分式の値は,新しい値が記憶されるの変数の値とする。

    上記の二項数値昇格は,数値集合変換(5.1.8)を含むかもしれないことに注意すること。 必要ならば,数値集合変換は,その差分に対して,それが変数に記憶される前に適用される。

    final と宣言された変数を減算することはできない。 なぜなら,final 変数のアクセスが式として使用されたとき,その結果は値であり,変数ではないからである。 したがって,それを後置減分演算子のオペランドとして使用することはできない。

    15.15 単項演算子

    単項演算子 (unary operators) は,+-++--~! 及びキャスト演算子を含む。 単項演算子をもつ式は,右から左へグループ化される。 つまり,-~x は,-(~x) と同じ意味とする。

    15.16の次の生成規則を,便宜上ここで繰り返す。

    15.15.1 前置増分演算子 ++

    ++ 演算子が先行した単項式は前置増分式とする。 その単項式の結果は数値型の変数でなければならない。 そうでなければ,コンパイル時エラーが発生する。 その前置増分式の型は,その変数の型とする。 その前置増分式の結果は,変数ではなく値とする。

    実行時に,もしそのオペランド式の評価が中途完了するならば,前置増分式は同じ理由で中途完了し,加算は起こらない。 そうでなければ,その値 1 がその変数の値に加えられ,その合計はその変数に記憶される。 その加算の前に,二項数値昇格(5.6.2)が,その値 1 及びその変数の値に実行される。 必要ならば,その合計は,記憶される前に,その変数の型へのプリミティブ型の縮小変換(5.1.3)によって縮小される。 その前置増分式の値は,その新しい値が記憶されたの変数の値とする。

    上記の二項数値昇格は,数値集合変換(5.1.8)を含むかもしれないことに注意すること。 必要ならば,数値集合変換は,その加算に対して,それが変数に記憶される前に適用される。

    final と宣言された変数を加算することはできない。 なぜなら,final 変数へのアクセスが式として使用されたとき,その結果は値であり,変数ではないからである。 したがって,それを前置増分演算子のオペランドとして使用することはできない。

    15.15.2 前置減分演算子 --

    -- 演算子が先行した単項式は前置減分式とする。 その単項式の結果が数値型の変数でなければならない。 そうでなければ,コンパイル時エラーが発生する。 その前置減分式の型は,その変数の型とする。 その前置減分式の結果は,変数ではなく,値とする。

    実行時に,もしオペランド式の評価が中途完了するならば,前置減分式は同じ理由で中途完了し,減算は起こらない。 そうでなければ,その値 1 がその変数の値から引かれ,その差分はその変数に記憶される。 その減算の前に,二項数値表現(5.6.2)がその値 1 及びその変数の値に実行される。 必要ならば,その差分は,記憶される前に,その変数の型へのプリミティブ型の縮小変換(5.1.3)によって縮小される。 その前置減分式の値は,その新しい値が記憶されたの変数の値とする。

    上記の二項数値昇格は,値集合変換(5.1.8)を含むかもしれないことに注意すること。 必要ならば,数値集合変換は,その減算に対して,それが変数に記憶される前に適用される。 final と宣言された変数を減算することはできない。 なぜなら,final 変数へのアクセスが式として使用されたとき,その結果は値であり,変数ではないからである。 したがって,それを前置増分演算子のオペランドとして使用することはできない。

    15.15.3 単項プラス演算子 +

    単項 + 演算子式のオペランドの型はプリミティブ数値型でなければならない。 そうでなければ,コンパイル時エラーが発生する。 単項数値昇格(5.6.1)がそのオペランドに実行される。 その単項加算式の型は,そのオペランドの昇格された型とする。 そのオペランドの結果が変数だとしても,その単項加算式の結果は変数ではなく値とする。

    実行時に,その単項加算式の値はそのオペランドの昇格された値とする。

    15.15.4 単項マイナス演算子 -

    単項 - 演算子式のオペランドの型は,プリミティブ数値型でなければならない。 そうでなければ,コンパイル時エラーが発生する。 単項数値昇格(5.6.1)が,そのオペランドに実行される。 その単項マイナス式の型は,昇格されたオペランドの型とする。

    単項数値昇格は,数値集合変換(5.1.8)を実行することに注意すること。 昇格されたオペランド値がどの数値集合から引き出されても,単項否定演算が実行され,及びその結果が同じ数値集合から引き出される。 その単項否定演算が実行され,及びその結果がそれと同じ数値集合から引き出される。 したがって,その結果は,さらに数値集合変換がおこなわれる。

    実行時に,その単項マイナス式の値は,そのオペランドの昇格した値の算術的否定とする。

    整数値において,否定はゼロからの減算と同じとする。 Javaプログラム言語は,整数において2の補数表現を使用する。 2の補数の範囲は対称ではないので,最大の負の int 又は long の否定は最大の負の数となる。 この場合はオーバフローが起こるが,例外は投げられない。 すべての整数値 x において,-x(~x)+1 に等しい。

    浮動小数点値において,否定はゼロからの引き算と同じでない。 なぜなら,もし x+0.0 ならば,0.0-x+0.0 に等しいが,-x-0.0 に等しいからである。 単項マイナスは,単に浮動小数点の符号を逆にする。 特に重要な場合を以下に示す。

    15.15.5 ビット単位の補数演算子 ~

    単項 ~ 式のオペランドの型は,プリミティブの整数的な型でなければならない。 そうでなければ,コンパイル時エラーが発生する。 単項数値昇格(5.6.1)は,そのオペランドに対して実行される。 単項のビット単位の補数式の型は,そのオペランドの昇格された型とする。

    実行時に,単項のビット単位の補数式の値は,そのオペランドの昇格された値のビット単位の補数となる。 すべての場合において,~x(-x)-1 に等しいことに注意すること。

    15.15.6 論理的な補数演算子 !

    単項 ! 演算子のオペランド式の型は boolean でなければならない。 そうでなければ,コンパイル時エラーが発生する。 単項論理補数式の型は boolean とする。

    実行時に,その単項論理補数式は,もしそのオペランドの値が false ならば true となり,もしそのオペランドの値が true ならば false となる。

    15.16 キャスト式

    キャスト式は,実行時にある数値型の値を別の数値型の同様な値に変換すること,コンパイル時にある式の型が boolean かどうかを確認すること,又は実行時にある参照値が規定された参照型と互換性のあるクラスのオブジェクトを参照しているかどうかを検査することなどを実行する。 UnaryExpressionUnaryExpressionNotPlusMinus の間の区別の議論については,15.15を参照すること。

    キャスト式の型は,括弧内に出現する名前の型とする (括弧及び括弧が含む型をキャスト演算子 (cast operator) と呼ぶことがある。)。 キャスト式の結果は,そのオぺランドの式の結果が変数だとしても,変数ではなく値とする。

    キャスト演算子は,型 float 又は double の値に対する数値集合(4.2.3)の選択には効果がない。 その結果,FP厳密(15.4)ではない式の中の型 float へのキャストは,その値を単精度数値集合の要素に変換する必要はなく,及びFP厳密でない式の中の型 double へのキャストは,その値を倍精度数値集合の要素に変換する必要はない。

    実行時に,オぺランドの値をキャスト変換(5.5)によって,そのキャスト演算子で規定された型に変換する。

    Java言語では,すべてのキャストが許されるわけではない。 あるキャストは,コンパイル時エラーを生じる。 例えば,プリミティブ値は,参照型にキャストしてはいけない。 あるキャストは,コンパイル時に実行時に常に正しいことを保証できる。 例えば,あるクラス型の値を,その上位クラスの型に変換することは常に正しい。 このようなキャストは,実行時に特別な動作を要求しないほうがよい。 最後に,あるキャストは,常に正しいか常に正しくないかをコンパイル時に特定できない。 そのようなキャストは,実行時に検査が必要とする。 もし受け入れられないキャストを実行時に検出したならば,ClassCastException が投げられる。

    15.17 乗除演算子

    演算子 */ 及び % は,乗除演算子 (multiplicative operators) と呼ぶ。 これらは同じ優先順位をもち,構文的に左結合とする(左から右にグループ化する。)。

    乗除演算子のオぺランドのそれぞれの型は,プリミティブ数値型でなければならない。 そうでなければ,コンパイル時エラーが発生する。 そのオぺランドに対して,二項数値昇格が実行される(5.6.2)。 乗除式の型は,そのオぺランドの昇格された型とする。 もしこの昇格された型が int 又は long であるならば,整数演算を実行する。 もしこの昇格された型が float 又は double であるならば,浮動小数点演算を実行する。

    二項数値昇格が,数値集合変換(5.1.8)を実行することに注意すること。

    15.17.1 乗算演算子 *

    二項 * 演算子は乗算を実行し,そのオぺランドの積を生成する。 もしオぺランドの式が副作用をもたないならば,乗算は可換的演算とする。 オぺランドがすべて同じ型のとき,整数乗算は結合的であるが,浮動小数点乗算は結合的ではない。

    もし整数乗算がオーバフローしたならば,その結果は数学的な積を十分大きな2の補数形式で表現したときの低位ビットとする。 その結果として,もしオーバフローが発生したならば,その結果の符号は二つのオぺランド値の数学的積の符号と同じでないかもしれない。

    浮動小数点乗算の結果は,次のようにIEEE 754の算術の規則に従う。

    オーバフロー,アンダフロー又は情報の損失が発生するかもしれないという事実にもかかわらず,乗算演算子 * は決して実行時例外を投げない。

    15.17.2 除算演算子 /

    二項 / 演算子は除算を実行し,そのオぺランドの商を生成する。 その左辺オぺランドは被除数であり,その右辺オぺランドは除数とする。

    整数除算は結果を 0 方向に丸める。 つまり,二項数値昇格(5.6.2)の後の整数のオぺランド n 及び d に対して生成される商は,を満足しながら,その大きさが可能な限り大きい整数値 q とする。 さらに,であって,n 及び d が同じ符号をもつとき,q は正とする。 しかし,であって,n 及び d が反対の符号をもつとき,q は負とする。 この規則を満足しない特別な場合が一つだけ存在する。 もしその被除数がその型に対して可能な最大の大きさの負の整数であり,その除数が -1 であるならば,整数オーバフローが発生し,その結果はその被除数と同じとする。 オーバフローにもかかわらず,この場合は例外は投げられない。 一方,整数の除算における除数の値が 0 ならば,ArithmeticException が投げられる。

    浮動小数点の除算の結果は,IEEE算術の規定によって次のように決定される。

    オーバフロー,アンダフロー,ゼロによる除算又は情報の損失が発生するかもしれないという事実にもかかわらず,浮動小数点除算演算子 / の評価は決して実行時例外を投げない。

    15.17.3 剰余演算子 %

    二項 % 演算子は,暗黙の除算によってそのオぺランドの剰余を生成する。 その左辺オぺランドは被除数であり,及びその右辺オぺランドは除数とする。

    C及びC++では,剰余演算子は整数的なオぺランドだけを受け入れるが,Javaプログラム言語では浮動小数点のオペランドも受け入れる。

    二項数値昇格(5.6.2)の後の整数のオぺランドに対する剰余演算は,(a/b)*b+(a%b)a に等しいように結果を生成する。 この恒等式は,被除数をその型に対する可能な最大の大きさの負の整数であり,及び除数が -1 (その剰余は 0)である特別な場合においても成立する。 この規則から,その剰余演算の結果は,その被除数が負である場合に限り負であり,その被除数が正である場合に限り正であることが求められる。 さらに,その結果の大きさは常にその除数の大きさより小さい。 もし整数の剰余演算子に対する除数の値が 0 であるならば,ArithmeticException が投げられる。

    5%3 の結果は 2		(5/3 の結果は 1であることに注意すること)
    5%(-3) の結果は 2		(5/(-3) の結果は -1であることに注意すること)
    (-5)%3 の結果は -2		((-5)/3 の結果は -1であることに注意すること)
    (-5)%(-3) の結果は -2	((-5)/(-3) の結果は 1であることに注意すること)
    
    % 演算子によって計算される浮動小数点剰余演算の結果は,IEEE 754で定義された剰余演算によって生成される結果とは同じではない。 IEEE 754の剰余演算は,切り捨て除算ではなく,丸め除算によって計算し,及びその振舞いは通常の整数剰余演算子とは同じではない。 代わりに,Javaプログラム言語では,整数剰余演算子と同様に振る舞うように,浮動小数点演算に対する % 演算を定義する。 これは,Cライブラリ関数の fmod に相当する。 IEEE 754の剰余演算は,Javaのライブラリルーチンの Math.IEEEremainder (20.11.14)によって計算できる。 浮動小数点演算の結果は,IEEE算術の規則によって次のように決定される。 右辺のオペランドが 0 になるにもかかわらず,浮動小数点剰余演算子 % の評価は決して実行時例外を投げない。 オーバフロー,アンダフロー及び精度の損失は発生しない。

    次に例を示す。

    5.0%3.0 の結果は 2.0
    5.0%(-3.0) の結果は 2.0
    (-5.0)%3.0 の結果は -2.0
    (-5.0)%(-3.0) の結果は -2.0
    

    15.18 加減演算子

    演算子 + 及び -加減演算子 (additive operators) と呼ばれる。 これらは同じ優先順位をもち,構文的に左結合とする(左から右にグループ化する。)。

    もし + 演算子のいずれかのオぺランドの型が String であるならば,その演算は文字列の連結とする。

    そうでなければ,+ 演算子の各オペランドの型は,プリミティブ数値型でなければならない。 そうでなければ,コンパイル時エラーが発生する。

    すべての場合において,二項 - 演算子の各オペランドは,プリミティブ数値型でなければならない。 そうでなければ,コンパイル時エラーが発生する。

    15.18.1 文字列連結演算子 +

    もし一つのオペランドの式だけが型 String であるならば,実行時に文字列を生成するために,他方のオペランドに対して文字列変換を実行する。 その結果は,二つのオペランドの文字列を連結して新たに作成した String オブジェクトへの参照とする。 新たに作成した文字列の中では,左辺オぺランドの文字が右辺オぺランドの文字に先行する。

    15.18.1.1 文字列変換

    文字列変換 (string conversion) によって,いかなる型も String に変換できる。 最初に,プリミティブ型 T の値 x を,適切なクラスインスタンス生成式への実引数にその値を与えたかのように参照値に変換される。 次に,この参照値は文字列変換によって型 String に変換される。

    この後は,参照値だけを考慮する必要がある。 もしその参照が null であるならば,それは文字列 "null" (四つのASCII文字 null)に変換される。 そうでなければ,その変換は,その参照されているオブジェクトのメソッド toString を実引数なしで呼び出したかのように実行される。 しかし,もしメソッド toString の呼出し結果が null であるならば,文字列 "null" が代わりに使用される。

    toString メソッドは,基本クラス Object によって定義されている。 多くのクラスがそれを上書きしており,特にBooleanCharacterIntegerLongFloatDouble 及び String がある。

    15.18.1.2 文字列連結の最適化

    処理系は,中間的な String オブジェクトの作成及び廃棄を避けるために,変換及び連結を一段階で実行してもよく,Javaコンパイラは,繰り返される文字列連結の性能向上を目的として,式の評価によって作成される中間的な String オブジェクトの数を減らすために,StringBuffer クラス又は同様の技術を使用してもよい。

    プリミティブ型に対しては,処理系はプリミティブ型から文字列に直接変換することによって,ラッパーオブジェクトの作成を最適化してもよい。

    15.18.1.3 文字列連結の例

    式の例を次に示す。

    "The square root of 2 is " + Math.sqrt(2)
    
    これは次の結果を生成する。

    "The square root of 2 is 1.4142135623730952"
    
    + 演算子は,たとえそれが文字列連結又は加算を表現するために型解析によって後から決定されても,構文的に左結合とする。 望む結果を得るためには,注意が要求されることもある。 例えば,次の式は,

    a + b + c
    
    常に次の式を意味するとみなす。

    (a + b) + c
    
    したがって,次の式の結果は
    1 + 2 + " fiddlers"
    
    次の通りとする。
    "3 fiddlers"
    
    しかし,次の式の結果は,
    "fiddlers " + 1 + 2
    
    次の通りとする。
    "fiddlers 12"
    
    ここで少し面白い例を次に示す。
    class Bottles {
    	static void printSong(Object stuff, int n) {
    		String plural = (n == 1) ? "" : "s";
    		loop: while (true) {
    			System.out.println(n + " bottle" + plural
    				+ " of " + stuff + " on the wall,");
    			System.out.println(n + " bottle" + plural
    				+ " of " + stuff + ";");
    			System.out.println("You take one down "
    				+ "and pass it around:");
    			--n;
    			plural = (n == 1) ? "" : "s";
    			if (n == 0)
    				break loop;
    			System.out.println(n + " bottle" + plural
    				+ " of " + stuff + " on the wall!");
    			System.out.println();
    		}
    		System.out.println("No bottles of " +
    				stuff + " on the wall!");
    	}
    }
    
    メソッド printSong はある童謡の替え歌を印刷する。 stuff として人気のある値は "pop" 及び "beer" を含む。 n としても最も人気がある値は 100 とする。 ここで,Bottles.printSong("slime", 3) の結果の出力を次に示す。

    3 bottles of slime on the wall,
    3 bottles of slime;
    You take one down and pass it around:
    2 bottles of slime on the wall!
    
    2 bottles of slime on the wall,
    2 bottles of slime;
    You take one down and pass it around:
    1 bottle of slime on the wall!
    
    1 bottle of slime on the wall,
    1 bottle of slime;
    You take one down and pass it around:
    No bottles of slime on the wall!
    
    このコードの中では,複数形の"bottles"よりも単数形の"bottle"が適切なときは,単数形を注意深く条件にしたがって生成することに注意すること。 次のような長い文字列定数の分割するために,文字列連結演算子を使用した方法にも注意すること。

    "You take one down and pass it around:"
    
    これを,ソースコード中では,不便な長い行を避けるために二つに分割している。

    15.18.2 数値型加減演算子 ( + 及び - )

    二項 + 演算子は,二つのオぺランドが数値型のときに加算を実行し,そのオぺランドの和を生成する。 二項 - 演算子は,減算を実行し,二つの数値オぺランドの差を生成する。

    そのオペランドに対して二項数値昇格が実行される (5.6.2)。 数値オぺランドに対する加減式の型は,そのオぺランドの昇格された型とする。 この昇格された型が int 又は long ならば,整数算術が実行される。 この昇格された型が float 又は double ならば,浮動小数点算術が実行される。

    二値数値昇格は数値集合変換(5.1.8)を実行することに注意すること。

    もしそのオぺランドの式が副作用をもたないならば,加算は可換的な演算とする。 オペランドがすべて同じ型のとき,整数加算は結合的であるが,浮動小数点加算は結合的ではない。

    もし整数の加算がオーバフローしたならば,その結果は数学的な和を十分大きな2の補数形式で表現したときの低位ビットとする。 オーバフローが発生すれば,その結果の符号は二つのオぺランド値の数学的和の符号と同じではない。

    浮動小数点加算の結果は,次のIEEE 754算術の規則を用いて決定される。

    二項 - 演算子は,数値型の二つのオぺランドに適用したときに減算を実行し,そのオぺランドの差を生成する。 その左辺オぺランドは被減数であり,及びその右辺オぺランドは減数とする。 整数及び浮動小数点減算の両方に対して,常に a-ba+(-b) と同じ結果を生成する。

    整数値についてはゼロからの減算は符号反転と同じであるが,浮動小数点オぺランドについては,ゼロからの減算は符号反転と同じではないことに注意すること。 なぜなら,x+0.0 ならば 0.0-x+0.0 に等しいが,-x-0.0 に等しいからである。 オーバフロー,アンダフロー又は情報の損失が発生するかもしれない事実にもかかわらず,数値加減演算子の評価は決して実行時例外を投げない。

    15.19 シフト演算子

    シフト演算子 (shift operators) は,左シフト <<,符号付き右シフト >>,及び符号なし右シフト >>>を含む。 それらは構文的に左結合とする(左から右にグループ化する。)。 シフト演算子の左辺オぺランドはシフトされる値であり,右辺オぺランドはシフト幅を規定する。

    シフト演算子のオぺランドのそれぞれの型は,プリミティブな整数的な型でなければならない。 そうでなければ,コンパイル時エラーが発生する。 そのオペランドに対して二項数値昇格(5.6.2)は実行しないが,単項数値昇格(5.6.1)は各オぺランドに対して個々に実行される。 そのシフト演算式の型は,その左辺オぺランドの昇格された型とする。

    もしその左辺オぺランドの昇格された型が int であるならば,その右辺オぺランドの下位5ビットだけをシフト幅として使用する。 それはその右辺オペランドが,マスク値 0x1f を用いたビット単位のAND演算子 & (15.22.1)に従うかのようにとする。 したがって,実際に使用するシフト幅は0から31までの範囲とする。

    もしその左辺オぺランドの昇格された型が long であるならば,その右辺オぺランドの下位6ビットだけをシフト幅として使用する。 それはその右辺オペランドが,マスク値 0x3f を用いたビット単位のAND演算子 & (15.22.1)に従うかのようにとする。 したがって,実際に使用されるシフト幅は0から63までの範囲とする。

    実行時には,シフト演算はその左辺オぺランド値の2の補数の整数表現に対して実行される。

    n<<s の値は,ns ビット位置だけ左にシフトした値とする。 これは(オーバフローが発生したとしても)2の s 乗の乗算に等しい。

    n>>s の値は,n を符号拡張を伴って s ビット位置だけ右にシフトした値とする。 この結果の値は とする。 n の非負数の値に対しては,これは整数除算演算子/によって計算される,2の s 乗の切り捨て整数除算に等しい。

    n>>>s の値は,ゼロ拡張を伴ってns ビット位置分右にシフトした値とする。 もし n が正であるならば,その結果は n>>s と同じとする。 もし nが負であるならば,その結果はその左辺オぺランドの型が int ならば式 (n>>s)+(2<<~s) と等しく,その左辺オぺランドの型が long ならば式 (n>>s)+(2L<<~s)に等しい。 追加された項 (2<<~s) 又は (2L<<~s) は,伝播された符号ビットを除去する (シフト演算子の右辺オぺランドの暗黙のマスクのために,シフト幅としての ~s は,int 値をシフトするときは 31-s に等しく,long 値をシフトするときは 63-s に等しいことに注意すること。)。

    15.20 関係演算子

    関係演算子 (relational operators) は,構文的に左結合とする(左から右にグループ化する。)。 しかし,この事実は実用的ではない。 例えば,a<b<c(a<b)<c と構文解析するが,これは常にコンパイル時エラーとする。 なぜなら,a<b の型は常に boolean であり,及び<boolean 値に対する演算子ではないからである,

    関係式の型は,常に boolean とする。

    15.20.1 数値比較演算子 <, <=, >, 及び >=

    数値比較演算子のそれぞれのオぺランドの型は,プリミティブ数値型でなければならない。 そうでなければ,コンパイル時エラーが発生する。 そのオぺランドに対して二項数値昇格が実行される (5.6.2)。 もしそのオペランドの昇格された型が int 又は long であるならば,符号付き整数比較が実行される。 もしこの昇格された型が float 又は double であるならば,浮動小数点比較が実行される。

    二値数値昇格は,数値集合変換(5.1.8)を実行することに注意すること。 それらの値を表現するためにどの数値集合を用いても,比較は浮動小数点値に対して正確に実行される。

    浮動小数点比較の結果は,IEEE 754標準の規定によって決定されるように,次のとおりとする。

    浮動小数点数に対するこれらの考察にしたがって,整数オぺランド又はNaN以外の浮動小数点オぺランドに対して,次の規則が成立する。

    15.20.2 型比較演算子 instanceof

    instanceof 演算子の RelationalExpression オぺランドの型は,参照型又は空型でなければならない。 そうでなければ,コンパイル時エラーが発生する。 instanceof 演算子の後に記述する ReferenceType は,参照型又は空型でなければならない。 そうでなければ,コンパイル時エラーが発生する。

    実行時に,instanceof演算子の結果は,もし RelationalExpression の値が null でなく,その参照が例外 ClassCastException を投げずに ReferenceType にキャスト(15.16)できるならば,trueとする。 そうでなければ,その結果は false とする。

    もし RelationalExpressionReferenceType へのキャストがコンパイル時エラーとして拒否されれば,instanceof 関係式も同様にコンパイル時エラーを生じる。 このような状況では,instanceof 式の結果は決して true ではない。

    次のサンプルプログラムを考える。

    class Point { int x, y; }
    class Element { int atomicNumber; }
    class Test {
    	public static void main(String[] args) {
    		Point p = new Point();
    		Element e = new Element();
    		if (e instanceof Point) {	// compile-time error
    			System.out.println("I get your point!");
    			p = (Point)e;	// compile-time error
    		}
    	}
    }
    
    この例は,二つのコンパイル時エラーを生じる。 キャスト (Point)eは 間違っている。 なぜなら Element のインスタンス及びその可能な下位クラス(ここでは示されていない)は,Point の下位クラスのインスタンスになることができないからとする。 instanceof 式もまさしく同じ理由で間違っている。 一方,もしPointが次のようにElementの下位クラス(この例では明らかに奇妙な表記である)ならば,

    class Point extends Element { int x, y; }
    
    実行時検査を要するが,キャストは可能であり,そのinstanceof式は意味があり妥当となる。 そのキャスト (Point)e は決して例外を投げない。 なぜなら,もし値 e が型 Point に正しくキャストできないならば,そのキャストは実行されないからとする。

    15.21 等価演算子

    等価演算子は,構文的に左結合とする(左から右にグループ化する。)。 しかし,この事実は本質的には決して実用的ではない。 例えば,a==b==c(a==b)==c と構文解析する。 a==b の結果の型は,常に boolean であり,したがって c は型 boolean でなければならない。 そうでなければ,コンパイル時エラーが発生する。 つまり,a==b==c は ,a, b 及び c がすべて等しいかどうかを検査しない

    == (等価)及び != (不等価)演算子は,優先順位が低いという点を除いては関係演算子と類似している。 したがって,a<b==c<d は,a<b 及び c<d が同じ真値をもつときには,常に trueとする。

    等価演算子は,数値型の二つのオぺランド,型 boolean の二つのオぺランド又はそれぞれが参照型若しくは空型の二つのオぺランドを比較するために使用してよい。 そうでなければ,コンパイル時エラーが発生する。 等価式の型は,常に boolean とする。

    すべての場合において,a!=b!(a==b) と同じ結果を生成する。 もしオぺランド式が副作用をもたないならば,その等価演算子は可換的とする。

    15.21.1 数値等価演算子 == 及び !=

    もし等価演算子の両方のオぺランドがプリミティブ数値型であるならば,そのオぺランドに対して二項数値昇格(5.6.2)が実行される。 もしその昇格された型が int 又は long であるならば,整数等価試験が実行される。 もしその昇格された型が float 又は double であるならば,浮動小数点等価試験が実行される。

    二値数値昇格は,数値集合変換(5.1.8)を実行することに注意すること。 比較は,それらの値を表現するためにどの数値集合数値集合を用いても,浮動小数点値を正確に処理する。

    浮動小数点等価試験は,IEEE 754標準の規則にしたがって,次のように実行される。

    浮動小数点数に対するこれらの考察にしたがって,整数オぺランド又はNaN以外の浮動小数点オぺランドに対して,次の規則が成立する。

    15.21.2 論理型等価演算子 == 及び !=

    もし等価演算子のオペランドが両方とも型 boolean であるならば,その演算は論理型等価とする。 boolean 等価演算子は,結合的とする。

    == の結果は,もしそのオぺランドが両方とも true 又は 両方とも true であるならば,true とする。 そうでなければ,その結果は false とする。

    != の結果は,もしそのオぺランドが両方とも true 又は両方とも true であるならば,false とする。 そうでなければ,その結果は true とする。 したがって,!= は,論理型オぺランドに適用するときに ^(15.22.2)と同様に振る舞う。

    15.21.3 参照型等価演算子 == 及び !=

    もし等価演算子のオペランドの型が両方ともに参照型又は空であるならば,その演算はオブジェクト等価とする。

    もしどちらか一方のオぺランドの型を他方のオぺランドの型にキャスト変換(5.5)によって変換することが不可能であるならば,コンパイル時エラーが発生する。 その二つのオぺランドの実行時の値は,必然的に不等価とする。

    実行時には,== の結果は,そのオぺランドの値が両方とも null,両方とも同じオブジェクト若しくは配列を参照しているならば,true とする。 そうでなければ,その結果はfalseとする。

    != の結果は,もしそのオぺランドの値が両方とも null 又は両方とも同じオブジェクト若しくは配列を参照していれば,false とする。 そうでなければ,その結果はtrueとする。

    == は,型 String への参照の比較に使用してもよいが,そのような等価試験は,その二つのオぺランドが同じオブジェクト String を参照しているかどうかを決定する。 もしそのオぺランドが違うオブジェクト String であるならば,それらが同じ文字の並びを含んでいたとしても,その結果はfalseとする。 二つの文字列 s 及び t の内容は,メソッド呼出し s.equals(t) によって試験することができる。 3.10.5も参照すること。

    15.22 ビット単位及び論理演算子

    ビット単位の演算子 (bitwise operators) 及び 論理演算子 (logical operators) は,AND演算子 &,XOR演算子 ^ 及びOR演算子| を含む。 これらの演算子は,異なる優先度をもち,& は最大の優先度をもち,| は最小の優先度をもつ。 これらの演算子は各々構文的に左結合とする(各々左から右へとグループ化する。)。 各々の演算子は,もしそのオペランド式がどんな副作用ももたなければ,可換的とする。 各々の演算子は結合的とする。

    ビット単位及び論理演算子は,数値型の二つのオペランド又は型 boolean の二つのオペランドを比較するのに使用してよい。 他のすべての場合はコンパイル時エラーになる。

    15.22.1 整数値ビット単位演算子 &^及び|

    演算子 &^ 又は | の両オペランドがプリミティブ整数型であるとき,最初にオペランドに二項数値昇格(5.6.2)を実行する。 ビット単位の演算子式の型は,そのオペランドの昇格された型とする。

    &に関しては,その結果値はオペランド値のビット単位のANDとする。

    ^に関しては,その結果値はオペランド値のビット単位のXORとする。

    |に関しては,その結果値はオペランド値のビット単位のORとする。

    例えば,式 0xff00&0xf0f0 の結果は 0xf000 とする。 0xff00^0xf0f0 の結果は 0x0ff0 とする。 0xff00|0xf0f0 の結果は 0xfff0 とする。

    15.22.2 論理型論理演算子 &^及び|

    演算子 &^ 及び | の両方のオペランドが型booleanであるとき,そのビット単位の演算子式の型は型 boolean とする。

    & に関しては,もし両方のオペランド値が true であるならば,その結果値は true とする。 そうでなければ,その結果値は false とする。

    ^ に関しては,もしそのオペランド値が異なっているならば,その結果値はtrueとする。 そうでなければ,その結果値はfalseとする。

    | に関しては,もし両方のオペランド値が false であるならば,その結果値は false とする。 そうでなければ,結果値は true とする。

    15.23 条件AND演算子 &&

    && 演算子は,& (15.22.2)と類似しているが,しかしその左辺オペランド値が true である場合に限り,その右辺オペランドを評価する。 それは構文的に左結合とする(左から右へとグループ化する。)。 副作用及び結果値の両方に関して,完全に結合的とする。 つまり,任意の式 ab 及び c に対して,式 ((a)&&(b))&&(c) の評価は,同じ副作用が同じ順序で発生し,式 (a)&&((b)&&(c)) の評価と同じ結果を生じる。

    && のそれぞれのオペランドは,型 boolean でなければならない。 そうでなければ,コンパイル時エラーが発生する。 条件AND式の型は,常に boolean とする。

    実行時に,左辺オペランド式を最初に評価する。 もしその値が false であるならば,条件AND式の値は false であり,その右辺オペランド式は評価されない。 もしその左辺オペランドの値が true であるならば,その右辺オペランド式を評価し,その値は条件AND式の値とする。 したがって,&& は,boolean オペランドに対して& と同じ結果を計算する。 その右辺オペランド式を,常にではなく条件的に評価するという点だけが異なる。

    15.24 条件OR演算子 ||

    演算子 ||| (15.22.2) と類似しているが,その左辺オペランドの値が false の場合に限り,その右辺オペランドを評価する。 それは構文的には左結合とする(左から右へとグループ化する。)。 副作用及び結果値の両方に関して,完全に結合的とする。 つまり,任意の式 ab 及び c に対して,式 ((a)||(b))||(c) の評価は,同じ副作用が同じ順序で発生し,式 (a)||((b)||(c)) の評価と同じ結果を生じる。

    || のそれぞれのオペランドは,型 boolean でなければならない。 そうでなければ,コンパイル時エラーが発生する。 条件OR式の型は,常に boolean とする。

    実行時には,その左辺オペランド式が最初に評価される。 もしその値が true であるならば,その条件OR式の値は true であり,その右辺オペランド式は評価しない。 もしその左辺オペランドの値が false であるならば,その右辺オペランド式を評価し,その値は条件OR式の値になる。

    したがって,||boolean オペランドに対して | と同じ結果を計算する。 その右辺オペランド式を常にではなく条件的に評価する点だけが異なる。

    15.25 条件演算子 ? :

    条件演算子 ? : は,二つの式のどちらを評価するのがよいかを決定するために,一つの式の論理値を使用する。

    条件演算子は,構文的には右結合とする(右から左へとグループ化する。)。 そこで,a?b:c?d:e?f:g は,a?b:(c?d:(e?f:g)) と同じことを意味する。

    条件演算子は,三つのオペランド式をもつ。 1番目と2番目の式との間に ? が現れ,2番目と3番目の式の間に : が現れる。

    最初の式は,型 boolean でなければならない。 そうでなければ,コンパイル時エラーが発生する。

    条件演算子は,数値型の2番目と3番目のオペランドの選択,型 boolean の2番目と3番目のオペランドの選択,又はそれぞれが参照型か空型のいずれかである2番目と3番目のオペランドの選択に使用してよい。 他のすべての場合には,コンパイル時エラーになる。

    void メソッドの呼出しは,2番目のオペランド式も3番目のオペランド式も許されないことに注意すること。 実際に,条件式は,void メソッドの起動が出現するかもしれないいかなる文脈にも出現することは許されない(14.8)。

    条件式の型は,次のように決定される。

    実行時には,その条件式の最初のオペランド式が最初に評価される。 したがって,その boolean 値は,2番目又は3番目のオペランド式のどちらを選択するために使用される。

    その選択されたオペランド式を評価し,その結果の値は上述した規則によって決定される条件式の型に変換される。 選択されないオペランド式は,条件式のこの特定の評価のためには評価しない。

    15.26 代入演算子

    12個の代入演算子 (assignment operators) がある。 すべてが構文的に右結合とする(右から左へとグループ化する。)。 したがって,a=b=ca=(b=c) を意味する。 これは c の値を b に割り当て,次に b の値を a に割り当てる。

    代入演算子の最初のオペランドの結果は変数でなければならない。 そうでなければ,コンパイル時エラーが発生する。 このオペランドは,現在のオブジェクト又はクラスの局所変数又はフィールドの名前付けされた変数であってもよく,フィールドアクセス(15.11)又は配列アクセス(15.13)から生じ得るような計算された変数であってもよい。 代入式の型はその変数の型とする。

    実行時には,その代入式の結果は代入が起こった後の変数の値とする。 代入式の結果自身は,変数ではない。

    final と宣言された変数は,(それが未初期化最終変数(4.5.4)でない限り)代入できない。 なぜならば,final 変数のアクセスが式として使用されるときに,その結果は値であり変数ではないので,代入演算子の最初のオペランドとして使用することはできない。

    15.26.1 単純代入演算子 =

    もし代入変換(5.2)によって,その右辺オペランドの型をその変数の型に変換できないならば,コンパイル時エラーが発生する。

    実行時には,式は次に述べる二つの方法の中の一つで評価する。 もしその左辺オペランド式が配列アクセス式でないならば,次の三段階が要求される。

    もしその左辺オペランド式が配列アクセス式(15.13)であるならば,次の多くの段階を要求する。

    そうでなければ,その右辺オペランドの参照値が,選択された配列の構成要素に記憶され。

    配列の構成要素への代入規則を,次のプログラム例で示す。

    class ArrayReferenceThrow extends RuntimeException { }
    class IndexThrow extends RuntimeException { }
    class RightHandSideThrow extends RuntimeException { }
    class IllustrateSimpleArrayAssignment {
    	static Object[] objects = { new Object(), new Object() };
    	static Thread[] threads = { new Thread(), new Thread() };
    	static Object[] arrayThrow() {
    		throw new ArrayReferenceThrow();
    	}
    	static int indexThrow() { throw new IndexThrow(); }
    	static Thread rightThrow() {
    		throw new RightHandSideThrow();
    	}
    	static String name(Object q) {
    		String sq = q.getClass().getName();
    		int k = sq.lastIndexOf('.');
    		return (k < 0) ? sq : sq.substring(k+1);
    	}
    	static void testFour(Object[] x, int j, Object y) {
    		String sx = x == null ? "null" : name(x[0]) + "s";
    		String sy = name(y);
    		System.out.println();
    		try {
    			System.out.print(sx + "[throw]=throw => ");
    			x[indexThrow()] = rightThrow();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print(sx + "[throw]=" + sy + " => ");
    			x[indexThrow()] = y;
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print(sx + "[" + j + "]=throw => ");
    			x[j] = rightThrow();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print(sx + "[" + j + "]=" + sy + " => ");
    			x[j] = y;
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    	}
    	public static void main(String[] args) {
    		try {
    			System.out.print("throw[throw]=throw => ");
    			arrayThrow()[indexThrow()] = rightThrow();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print("throw[throw]=Thread => ");
    			arrayThrow()[indexThrow()] = new Thread();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print("throw[1]=throw => ");
    			arrayThrow()[1] = rightThrow();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print("throw[1]=Thread => ");
    			arrayThrow()[1] = new Thread();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		testFour(null, 1, new StringBuffer());
    		testFour(null, 1, new StringBuffer());
    		testFour(null, 9, new Thread());
    		testFour(null, 9, new Thread());
    		testFour(objects, 1, new StringBuffer());
    		testFour(objects, 1, new Thread());
    		testFour(objects, 9, new StringBuffer());
    		testFour(objects, 9, new Thread());
    		testFour(threads, 1, new StringBuffer());
    		testFour(threads, 1, new Thread());
    		testFour(threads, 9, new StringBuffer());
    		testFour(threads, 9, new Thread());
    	}
    }
    
    このプログラムは,次のように表示する。

    throw[throw]=throw => ArrayReferenceThrow
    throw[throw]=Thread => ArrayReferenceThrow
    throw[1]=throw => ArrayReferenceThrow
    throw[1]=Thread => ArrayReferenceThrow
    null[throw]=throw => IndexThrow
    null[throw]=StringBuffer => IndexThrow
    null[1]=throw => RightHandSideThrow
    null[1]=StringBuffer => NullPointerException
    null[throw]=throw => IndexThrow
    null[throw]=StringBuffer => IndexThrow
    null[1]=throw => RightHandSideThrow
    null[1]=StringBuffer => NullPointerException
    null[throw]=throw => IndexThrow
    null[throw]=Thread => IndexThrow
    null[9]=throw => RightHandSideThrow
    null[9]=Thread => NullPointerException
    null[throw]=throw => IndexThrow
    null[throw]=Thread => IndexThrow
    null[9]=throw => RightHandSideThrow
    null[9]=Thread => NullPointerException
    Objects[throw]=throw => IndexThrow
    Objects[throw]=StringBuffer => IndexThrow
    Objects[1]=throw => RightHandSideThrow
    Objects[1]=StringBuffer => Okay!
    Objects[throw]=throw => IndexThrow
    Objects[throw]=Thread => IndexThrow
    Objects[1]=throw => RightHandSideThrow
    Objects[1]=Thread => Okay!
    Objects[throw]=throw => IndexThrow
    Objects[throw]=StringBuffer => IndexThrow
    Objects[9]=throw => RightHandSideThrow
    Objects[9]=StringBuffer => ArrayIndexOutOfBoundsException
    Objects[throw]=throw => IndexThrow
    Objects[throw]=Thread => IndexThrow
    Objects[9]=throw => RightHandSideThrow
    Objects[9]=Thread => ArrayIndexOutOfBoundsException
    Threads[throw]=throw => IndexThrow
    Threads[throw]=StringBuffer => IndexThrow
    Threads[1]=throw => RightHandSideThrow
    Threads[1]=StringBuffer => ArrayStoreException
    Threads[throw]=throw => IndexThrow
    Threads[throw]=Thread => IndexThrow
    Threads[1]=throw => RightHandSideThrow
    Threads[1]=Thread => Okay!
    Threads[throw]=throw => IndexThrow
    Threads[throw]=StringBuffer => IndexThrow
    Threads[9]=throw => RightHandSideThrow
    Threads[9]=StringBuffer => ArrayIndexOutOfBoundsException
    Threads[throw]=throw => IndexThrow
    Threads[throw]=Thread => IndexThrow
    Threads[9]=throw => RightHandSideThrow
    Threads[9]=Thread => ArrayIndexOutOfBoundsException
    
    この多くの中で最も興味深い場合は,最後から13番目とする。

    Threads[1]=StringBuffer => ArrayStoreException
    
    これは StringBuffer への参照を,構成要素が型 Thread である配列に記憶しようとして,ArrayStoreExceptionを投げたことを示す。 そのコードは,コンパイル時には正しい型とする。 その代入は,型 Object[] の左辺及び型 Object の右辺をもつ。 実行時には,メソッド testFour への最初の実引数は,"Threadの配列"のインスタンスへの参照であり,及び3番目の実引数は,クラス StringBuffer のインスタンスへの参照とする。

    15.26.2 複合代入演算子

    += を除くすべての複合代入演算子は,両方のオペランドがプリミティブ型であることを要求される。 +=に対しては,もしその左辺オペランドが型 String であれば,その右辺オペランドはどの型でもよい。

    形式 E1op=E2 の複合代入式は,もとの式は E1 を一度だけ評価する点を除き,E1=(T)((E1)op(E2)) に等価とする。 ここで,TE1の型とする。 型 T への暗黙のキャストは,等値変換(5.1.1)又はプリミティブ型の縮小変換(5.1.3)のどちらでもよいことに注意すること。 例えば,次のコードは正しい。

    
    short x = 3;
    x += 4.6;
    
    及び,これは次の例とは同等であるので,x は値 7 をもつ。

    
    short x = 3;
    x = (short)(x + 4.6);
    
    実行時に,その式は二つの方法の中の一つで評価される。 もしその左辺オペランド式が配列アクセス式でないならば,次の四つの段階が要求される。

    もしその左辺オペランド式が配列アクセス式(15.13)であるならば,次の多くの段階を要求する。

    そうでなければ,その二項演算の String の結果を,その配列の構成要素に記憶する。

    配列の構成要素への複合代入の規則を,次のプログラム例で示す。

    class ArrayReferenceThrow extends RuntimeException { }
    class IndexThrow extends RuntimeException { }
    class RightHandSideThrow extends RuntimeException { }
    class IllustrateCompoundArrayAssignment {
    	static String[] strings = { "Simon", "Garfunkel" };
    	static double[] doubles = { Math.E, Math.PI };
    	static String[] stringsThrow() {
    		throw new ArrayReferenceThrow();
    	}
    	static double[] doublesThrow() {
    		throw new ArrayReferenceThrow();
    	}
    	static int indexThrow() { throw new IndexThrow(); }
    	static String stringThrow() {
    		throw new RightHandSideThrow();
    	}
    	static double doubleThrow() {
    		throw new RightHandSideThrow();
    	}
    	static String name(Object q) {
    		String sq = q.getClass().getName();
    		int k = sq.lastIndexOf('.');
    		return (k < 0) ? sq : sq.substring(k+1);
    	}
    	static void testEight(String[] x, double[] z, int j) {
    		String sx = (x == null) ? "null" : "Strings";
    		String sz = (z == null) ? "null" : "doubles";
    		System.out.println();
    		try {
    			System.out.print(sx + "[throw]+=throw => ");
    			x[indexThrow()] += stringThrow();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print(sz + "[throw]+=throw => ");
    			z[indexThrow()] += doubleThrow();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print(sx + "[throw]+=\"heh\" => ");
    			x[indexThrow()] += "heh";
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print(sz + "[throw]+=12345 => ");
    			z[indexThrow()] += 12345;
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print(sx + "[" + j + "]+=throw => ");
    			x[j] += stringThrow();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print(sz + "[" + j + "]+=throw => ");
    			z[j] += doubleThrow();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print(sx + "[" + j + "]+=\"heh\" => ");
    			x[j] += "heh";
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print(sz + "[" + j + "]+=12345 => ");
    			z[j] += 12345;
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    	}
    	public static void main(String[] args) {
    		try {
    			System.out.print("throw[throw]+=throw => ");
    			stringsThrow()[indexThrow()] += stringThrow();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print("throw[throw]+=throw => ");
    			doublesThrow()[indexThrow()] += doubleThrow();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print("throw[throw]+=\"heh\" => ");
    			stringsThrow()[indexThrow()] += "heh";
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print("throw[throw]+=12345 => ");
    			doublesThrow()[indexThrow()] += 12345;
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print("throw[1]+=throw => ");
    			stringsThrow()[1] += stringThrow();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print("throw[1]+=throw => ");
    			doublesThrow()[1] += doubleThrow();
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print("throw[1]+=\"heh\" => ");
    			stringsThrow()[1] += "heh";
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		try {
    			System.out.print("throw[1]+=12345 => ");
    			doublesThrow()[1] += 12345;
    			System.out.println("Okay!");
    		} catch (Throwable e) { System.out.println(name(e)); }
    		testEight(null, null, 1);
    		testEight(null, null, 9);
    		testEight(strings, doubles, 1);
    		testEight(strings, doubles, 9);
    	}
    }
    
    このプログラムは,次のように表示する。

    throw[throw]+=throw => ArrayReferenceThrow
    throw[throw]+=throw => ArrayReferenceThrow
    throw[throw]+="heh" => ArrayReferenceThrow
    throw[throw]+=12345 => ArrayReferenceThrow
    throw[1]+=throw => ArrayReferenceThrow
    throw[1]+=throw => ArrayReferenceThrow
    throw[1]+="heh" => ArrayReferenceThrow
    throw[1]+=12345 => ArrayReferenceThrow
    null[throw]+=throw => IndexThrow
    null[throw]+=throw => IndexThrow
    null[throw]+="heh" => IndexThrow
    null[throw]+=12345 => IndexThrow
    null[1]+=throw => NullPointerException
    null[1]+=throw => NullPointerException
    null[1]+="heh" => NullPointerException
    null[1]+=12345 => NullPointerException
    null[throw]+=throw => IndexThrow
    null[throw]+=throw => IndexThrow
    null[throw]+="heh" => IndexThrow
    null[throw]+=12345 => IndexThrow
    null[9]+=throw => NullPointerException
    null[9]+=throw => NullPointerException
    null[9]+="heh" => NullPointerException
    null[9]+=12345 => NullPointerException
    Strings[throw]+=throw => IndexThrow
    doubles[throw]+=throw => IndexThrow
    Strings[throw]+="heh" => IndexThrow
    doubles[throw]+=12345 => IndexThrow
    Strings[1]+=throw => RightHandSideThrow
    doubles[1]+=throw => RightHandSideThrow
    Strings[1]+="heh" => Okay!
    doubles[1]+=12345 => Okay!
    Strings[throw]+=throw => IndexThrow
    doubles[throw]+=throw => IndexThrow
    Strings[throw]+="heh" => IndexThrow
    doubles[throw]+=12345 => IndexThrow
    Strings[9]+=throw => ArrayIndexOutOfBoundsException
    doubles[9]+=throw => ArrayIndexOutOfBoundsException
    Strings[9]+="heh" => ArrayIndexOutOfBoundsException
    doubles[9]+=12345 => ArrayIndexOutOfBoundsException
    
    この多くの中で最も興味深い場合は,最後から11番目及び12番目とする。

    Strings[1]+=throw => RightHandSideThrow
    doubles[1]+=throw => RightHandSideThrow
    
    これらは,例外を投げることができる右辺が実際に例外を投げた場合で,さらに多くの中で唯一の場合とする。 これは,本当に,空配列参照値及び領域外インデクス値の検査後に,その右辺オペランドの評価が起こることを示している。

    次のプログラムは,右辺を評価する前に複合代入の左辺の値が保存される事実を示す。

    class Test {
    	public static void main(String[] args) {
    		int k = 1;
    		int[] a = { 1 };
    		k += (k = 4) * (k + 2);
    		a[0] += (a[0] = 4) * (a[0] + 2);
    		System.out.println("k==" + k + " and a[0]==" + a[0]);
    	}
    }
    
    このプログラムは,次のように表示する。
    k==25 and a[0]==25
    
    その右辺オペランド (k=4)*(k+2) が評価される前に,k の値 1 が複合代入演算子 += により保存される。 この右辺オペランドの評価は,4k に割り当て,k+2 を値 6 と計算し,46 をかけて 24 を得る。 これに保存した値 1 を加えて,25 を得る。 さらにこの値が演算子 += によって k に記憶される。 同じ分析が,a[0] を使用する場合に適用される。 結局,次の文は,

    k += (k = 4) * (k + 2);
    a[0] += (a[0] = 4) * (a[0] + 2);
    
    次の文と正確に同じように振舞う。

    k = k + (k = 4) * (k + 2);
    a[0] = a[0] + (a[0] = 4) * (a[0] + 2);
    

    15.27 式

    式 (Expression) は,次のように任意の代入式とする。

    C及びC++と異なり,Javaプログラム言語にはコンマ演算子は存在しない。

    15.28 定数式

    コンパイル時の定数式 (constant expression) は,次のものだけを使用して構成されるプリミティブ型の値又は String を表す式とする。

    コンパイル時定数式は,switch文(14.10)内のcase内で使用し,代入変換(5.2)に対して特別な意味をもつ。

    コンパイル時の定数式は,FP厳密であることを考慮しなくてよい非定数式中に出現した場合でさえも,常にFP厳密(15.4)として処理される。

    定数式の例を次に示す。

    true
    (short)(1*2*3*4*5*6)
    Integer.MAX_VALUE / 2
    2.0 * Math.PI
    "The integer " + Long.MAX_VALUE + " is mighty big."
    

    目次 | | | 索引 Java言語規定
    第2版