| 目次 | 前 | 次 | 索引 | Java言語規定 第2版 |
プログラムの仕事のほとんどは,変数への代入などの副作用か,より大きい式において引数又はオペランドとして使用されるか,文の中の実行系列に影響を与える値のいずれか又は両方のために,式 (expressions) を評価することで行われる。
式の評価は副作用を発生させることがある。 なぜなら,式が埋込み代入,増分演算子,減分演算子及びメソッド呼出しを含むかもしれないからである。
値を返さないメソッド,すなわち void (8.4)を宣言するメソッドを呼び出すメソッド呼出し(15.12)に限り,式は何も指示しない。
そのような式は式文(14.8)としてだけ用いられる。
なぜなら,ある式が現れ得る他のあらゆる文脈は,何かを指示するためにその式を必要とするからである。
また,メソッド呼出しの式文は,結果を生成するメソッドを呼び出してよい。
この場合,メソッドが返す値は捨てられる。
値集合変換 (5.1.8) は,値を生成するすべての式の結果に適用される。
おのおのの式は,何らかの(クラス又はインタフェース)型の宣言に現れる。 すなわちフィールド初期化子,静的初期化子,コンストラクタ宣言,又はメソッドのためのコードに現れる。
もし型 float 又は型 double の変数の値がこのように使用されるならば,値集合変換(5.1.8)がその変数の値に適用される。
変数に記憶された値が変数の型に対して常に互換であるように,式の値は式の型に対して常に代入互換(5.2)とする。 すなわち,型が T である式の値は,常に型 T の変数への代入に適している。
型が final 宣言されたクラス型 F である式は,空参照又はそのクラスが F 自身であるオブジェクトのいずれかの値をもつことが保証されることに注意すること。
なぜなら,final 型は下位クラスを持たないからである。
float 又は double であるならば,その式の値が,どの値集合(4.2.3)から導き出されるかについて疑問があるだろう。
これは値集合変換(5.1.8)の規則によって決定される。
すなわち,これらの規則は,式がFP厳密 (FP-strict)かどうかに依存する。
すべてのコンパイル時の定数式(15.28)は,FP厳密とする。
もし式がコンパイル時の定数式でないならば,その式を含むすべてのクラス宣言,インタフェース宣言,及びメソッド宣言を考慮する。
もしすべてのそのような宣言が strictfp 修飾子をもつならば,その式はFP厳密とする。
もしクラス,インタフェース又はメソッド X,が strictfp と宣言されるならば,X と X の中の任意のクラス,インタフェース,メソッド,コンストラクタ,インスタンス初期化子,静的初期化子又は変数初期化子は,FP厳密 (FP-strict)であると呼ぶ。
式がコンパイル時の定数式でない及びそれが strictfp 修飾子をもつ任意の宣言の中に出現しない場合に限り,その式がFP厳密でないとする。
FP厳密式の中では,すべての計算途中の値が単精度数値集合又は倍精度数値集合の要素でなければならない。 なぜなら,すべてのFP厳密式の結果は,単精度及び倍精度を使用して表現されるオペランドについてのIEEE 754算術規則によって予測される結果でなければいけないことを意味するからである。 FP厳密でない式の中では,中間結果を表すために拡張指数範囲を使用する実装をある程度認めることができる。 これは,おおまかに言えば,単精度数値集合又は倍精度数値集合を排他的に使用するとオーバフロー又はアンダフローを引き起こすかもしれない状況下における最終的な影響は,計算が"正しい解答"を出すかもしれないことである。
null 以外のオブジェクトの参照であろうと,コンパイル時にわかるとは限らない。
Javaプログラム言語では,被参照オブジェクトの実クラスが,式の型から推測できないような方法でプログラムの実行に影響することがある。
o.m(...) に用いられる特定のメソッドは,型 o であるクラス又はインタフェースの一部のメソッドに基づいて選択される。
インスタンスメソッドでは,o の実行時値によって参照されるオブジェクトのクラスが関与する。
なぜなら,親クラスですでに宣言された特定のメソッドを下位クラスが上書きしているかもしれないので,その場合はこの上書きしているメソッドが呼び出されるからである
(この上書きしているメソッドは,元々上書きされたメソッド m をさらに呼び出すことを選択するかもしれないし,しないかもしれない。)。
instanceof 演算子(15.20.2)。
型が参照型である式は,その式の実行時値が参照するオブジェクトのクラスが他の参照型と代入互換(5.2)かを調べるために,instanceof を用いて検査されるかもしれない。
[] を T[] の下位型として扱うことを許す。
しかし,これはキャストに対して実行される検査と同様に,配列の構成要素への代入に対する実行時検査を必要とする。
catch 節の正式な仮引数の型の instanceof である場合に限り,例外は catch 節によって捕らえられる。
ClassCastException が投げられる。
ArrayStoreException が投げられる。
catch ハンドラ(11.3)によっても捕らえられない時。
この場合は,最初に例外に出会った制御のスレッドは,そのスレッドグループに対し uncaughtException メソッドを呼び出し,終了する。
しかし,もしある式の評価が例外を投げるならば,その式は中途完了した (complete abruptly)と呼ばれる。
中途完了には,常にそれに関連する理由を持ち,それは与えられた値をもつ throw 節とする。
実行時例外は,次のようにあらかじめ定義された演算子によって投げられる。
OutOfMemoryError を投げる。
ArrayNegativeSizeException を投げる(15.10)。
null であるならば,フィールドアクセス(15.11)は NullPointerException を投げる。
null であるならば,インスタンスメソッドを呼び出すメソッド呼出し式(15.12)は NullPointerException を投げる。
null であるならば,配列アクセス(15.13)は NullPointerException を投げる。
length より大きい又は等しいならば,配列アクセス(15.13)は,IndexOutOfBoundsException を投げる。
ClassCastException を投げる。
ArithmeticException を投げる。
ArrayStoreException を投げる。
もし例外が発生するならば,評価の正常モードのすべての段階が完了する前に,一つ又はそれ以上の式の評価が終了する。 そのような式は中途完了したと呼ぶ。 "正常完了する"と"中途完了する"という言葉は文(14.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
2 の j への埋込み代入が行われる前に,演算子 / の左辺オペランド forgetIt() が例外を投げるからである。&&,|| 及び ? : を除く)が完全に評価されることを保証する。
もし二項演算子が整数除算 / (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 の呼出しが終了する前にゼロによる除算の例外の送信を含む除算演算のどの部分も実行されないからである。
浮動小数点計算の場合,この規則は無限大及び数字でない(NaN)値においても適用される。
例えば,!(x<y) は x>=y に書き直すことはできない。
この理由は,x,y 又は両方がNaNならば,これらの式は異なった値をもつからである。
特に,数学的に結合性をもつ浮動小数点計算も計算論的には結合的でない。 単純にそのような計算の順序を変えてはならない。
例えば,Javaコンパイラが 4.0*x*0.5 を 2.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);
}
}
次のように表示する。
なぜなら,最初の式はオーバフローするが,2番目はそうならないからである。Infinity 1.6e+308
対照的に,Javaプログラム言語においては,整数加算及び乗算はおそらく結合可能である。
例えば,a,b 及び c が局所変数である場合(ここでは,単に複数のスレッド及び volatile 値を含む問題が発生しないと仮定する。),a+b+c は,(a+b)+c 又は a+(b+c) のどちらとして評価されても,常に同じ答えを生成する。
式 b+c がそのコードの近くで起こるならば,賢いコンパイラはこの共通の副式を使用することができるかもしれない。
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 の代入が実行されないからである。
Primary: PrimaryNoNewArray ArrayCreationExpression PrimaryNoNewArray: Literal Type . class void . class this ClassName.this ( Expression ) ClassInstanceCreationExpression FieldAccess MethodInvocation ArrayAccess
便宜上,3.10の次の生成規則を繰り返す。
Literal: IntegerLiteral FloatingPointLiteral BooleanLiteral CharacterLiteral StringLiteral NullLiteralリテラルの型は,次のように決定される。
L 又は l で終わる整数リテラルの型は long とする。
また他の整数リテラルの型は int とする。
F 又は f で終わる浮動小数点リテラルの型は float であり,及びその値は単精度数値集合(4.2.3)の要素でなければならない。
またその他の浮動小数点リテラルの型は double であり,及びその値は倍精度数値集合の要素でなければならない。
boolean とする。
char とする。
String とする。
null の型は空型とする。またその値は空参照とする。
thisthis は,インスタンスメソッド,インスタンス初期化子又は
コンストラクタの本体の中,又はクラスのインスタンス変数の初期化子の中だけで使用してよい。
もしそれ以外の場所で現れれば,コンパイル時エラーが発生する。
一次式として使用される時,キーワード 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)の先頭に現れる明示的なコンストラクタ呼出しの文で使用されることもある。
thisthis で限定することで,どの字句的に取り囲むインスタンスも参照することができる。
C が,ClassName で指示されると仮定する。
また C が限定されたこの式が現れるクラスの n 番目の字句的に取り囲むクラスであるというように,n は整数であると仮定する。
ClassName.this という形式の式の値は,this (8.1.2)の n 番目に字句的に取り囲むインスタンスとする。
その式の型は C とする。
もし現在のクラスがクラス C の内部クラス又は C 自身でないならば,コンパイル時エラーが発生する。
括弧は,型 float 又は double の式の値に対する数値集合(4.2.3)の選択に決して影響しない。
ClassInstanceCreationExpression: new ClassOrInterfaceType ( ArgumentListopt ) ClassBodyopt Primary.new Identifier ( ArgumentListopt ) ClassBodyopt ArgumentList: Expression ArgumentList , Expressionクラスインスタンス生成式は,次の二つの形式をもつ。
new で始まる。
非限定クラスインスタンス生成式は,クラスが最上位クラス(7.6), メンバクラス(8.5,9.5),局所クラス(14.3)又は匿名クラス(15.9.5)かどうかにかかわらず,クラスのインスタンスを生成するために使用してよい。
クラスのインスタンスがクラスインスタンス生成式によって生成される時,クラスが インスタンス化 されたと呼ぶ。 クラスのインスタンス化は,インスタンス化するクラス,新たに作成したインスタンスの取囲みインスタンス(存在するならば),新しいインスタンスを生成するために呼び出さねばならないコンストラクタ,及びそのコンストラクタに渡さねばならない実引数の決定を含む。
final クラスであるならば,コンパイル時エラーが発生する。
もし T がインスタンスの名前であるならば,T によって命名されたインタフェースを実装した Object の匿名の直接的下位クラスが宣言される。
どちらの場合も,下位クラスの本体はクラスインスタンス生成式の中で定められた ClassBody とする。
final の内部クラス(8.1.2)の単純名ではない場合,コンパイル時エラーが発生する。
もし T があいまい(8.5)であるならば,コンパイル時エラーが発生する。
T によって命名されたクラスの匿名の直接的下位クラスが宣言される。
サブクラスの本体は,クラスインスタンス生成式の中で定められた ClassBody とする。
インスタンス化されたクラスは,匿名下位クラスとする。
abstract ではないクラスを命名しなければならない。そうでなければ,コンパイル時エラーが発生する。
この場合,インスタンス化されたクラスは ClassOrInterfaceType で指示される。
abstract 内部クラス(8.1.2)Tの単純名(6.2)でないならば,コンパイル時エラーが発生する。
もし Identifier があいまい(8.5)ならば,この場合もまたコンパイル時エラーが発生する。
インスタンス化されたクラスは,Identifier で指示される。
this とする。
this (8.1.2)の n 番目の字句的に取り囲むインスタンスとする。
this. の n 番目の字句的に取り囲むインスタンスとする。
this の n 番目の字句的に取り囲むインスタンスとする。
this の n 番目の字句的に取り囲むインスタンスとする。
最初に,もしクラスインスタンス生成式が限定クラスインスタンス生成式であるならば,その限定する基本式が評価される。
もしその限定する式を評価したら null になるならば,NullPointerException が投げられ,及びそのクラスインスタンス生成式は中途完了する。
もしその限定する式が中途完了するならば,そのクラスインスタンス生成式は同じ理由で中途完了する。
次に,新しいクラスインスタンスのためにスペースが割り当てられる。
もしオブジェクトを割り当てるためのスペースが不十分ならば,そのクラスインスタンス生成式の評価は,OutOfMemoryError (15.9.6)を投げて,中途完了する。
その新しいオブジェクトは,その規定されたクラス型とそのすべての上位クラスで宣言されるすべてのフィールドの新しいインスタンスを含む。 新しいフィールドインスタンスが生成されるときは,そのデフォルト値(4.5.5)に初期化される。
次に,そのコンストラクタに対する実引数が,左から右に評価される。 もし実引数評価のいずれかが中途完了するならば,その右側の実引数式は評価されず,及びクラスインスタンス生成式も同じ理由で中途完了する。
次に,その規定されたクラス型の選択されたコンストラクタが呼び出される。 これは,そのクラス型の各上位クラスに対して少なくとも一つのコンストラクタを呼び出す。 この過程は明示的なコンストラクタ呼出し文(8.6)によって指示でき,及び12.5で詳細に示す。
クラスインスタンス生成式の値は,その規定されたクラスの新しく作り出されたオブジェクトへの参照とする。 式が評価される毎に,新しいオブジェクトが生成される。
匿名クラスは,abstract (8.1.1.1)では決してない。
匿名クラスは,常に内部クラス(8.1.2)であるが,static (8.1.1, 8.5.2)では決してない。
匿名クラスは,常に暗黙の final (8.1.1.2)とする。
super(...)の形式の明示的なコンストラクタ呼出し(8.8.5.1)から成り,その実引数はコンストラクタの実引数であり,それが宣言された順序とする。super(...)の形式の明示的なコンストラクタ呼出し(8.8.5.1)から成り,及びその実引数はコンストラクタの後に続く実引数であり,それが宣言された順序とする。
その匿名クラスのシグネチャは,アクセス不可能型(例えば,もしそのような型が cs の上位クラスコンストラクタのシグネチャに現れる場合)を参照できることに注意すること。 これは,それ自身は,コンパイル時又は実行時のどちらかでエラーを起こさない。
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)の処理と比較すること。
ArrayCreationExpression: new PrimitiveType DimExprs Dimsopt new TypeName DimExprs Dimsopt new PrimitiveType Dims ArrayInitializer new TypeName Dims ArrayInitializer
DimExprs: DimExpr DimExprs DimExpr DimExpr: [ Expression ] Dims: [ ] Dims [ ]配列生成式は,要素が PrimitiveType 又は TypeName によって規定された型である新しい配列であるオブジェクトを生成する。 TypeName は,
abstract クラス型(8.1.1.1)又はインタフェース型(9.)さえも含む,どのような名前付き参照型を指定してもよい。
その生成式の型は,new キーワード,任意の DimExpr 式及び配列初期化子が削除されたその生成式の複写によって指示されうる配列型とする。
例えば,次の生成式の型は,
次の通りとする。new double[3][3][]
DimExpr の中のそれぞれの次元式の型は整数型でなければならず,そうでなければコンパイル時エラーが発生する。 それぞれの式は,単項数値昇格(5.6.1)を受ける。 その昇格型はdouble[][][]
int でなければならず,そうでなければコンパイル時エラーが発生する。
特に,これは次元式の型は long であってはならないことを意味する。もし配列初期化子が用意されているならば,10.6で示すように,新しく割り当てられた配列は配列初期化子によって用意された値で初期化される。
最初に次元式が左から右に評価される。 もし式の評価が中途完了するならば,その右の式は評価されない。
次に次元式の値が検査される。
もし 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][];
}
}
}
d,d1,d2 及び 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];
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 + "]");
}
}
は次のように表示する。
なぜなら,2番目の次元式が[4,3]
i に 3 を代入する前に,最初の次元が計算されて 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
i に 1 を代入する代入式は決して実行されないからである。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)を比較すること。
super のどちらかの値への参照とする
(単純名を使用して現在のインスタンス又はクラスのフィールドを参照することもできる。
6.5.6を参照すること。)。
FieldAccess: Primary . Identifier super . Identifier ClassName . super . Identifierフィールドアクセス式の意味は,限定名(6.6)と同じ規則を使用して決定されるが,式がパッケージ,クラス型又はインタフェース型を指示することができないという制限がある。
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 式が完全に評価されたことを示している(なぜなら,そのフィールド mountain が static だからである。)。
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 という名前のフィールドにアクセスできる。
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が指示するクラスがその現在のクラスの字句的に取り囲むクラスでない場合には,コンパイル時エラーが発生する。
MethodInvocation: MethodName ( ArgumentListopt ) Primary . Identifier ( ArgumentListopt ) super . Identifier ( ArgumentListopt ) ClassName . super . Identifier ( ArgumentListopt )便宜上,15.9のArgumentList の定義を繰り返す。
ArgumentList: Expression ArgumentList , Expressionコンパイル時のメソッド名の解決は,メソッドオーバロードの可能性のため,フィールド名の解決より複雑になる。 実行時のメソッドの呼出しもまた,インスタンスメソッドの上書きの可能性のため,フィールドの参照より複雑になる。
あるメソッド呼出し式により呼び出されるメソッドの決定は,幾つかの段階を含む。 次の三つの章は,メソッド呼出しのコンパイル時の処理を示す。 さらにメソッド呼出し式の型の決定は,15.12.3で示す。
. Identifier という形式の限定名ならば,メソッドの名前はその Identifier であり,及び検索するクラスは,その TypeName で命名されたものとする。
もし TypeName がクラスではなくインタフェースの名前ならば,コンパイル時エラーが発生する。
なぜなら,この形式は static メソッドだけを呼び出せるが,インタフェースは static メソッドを持たないからである。
. Identifier という形式であり,そのメソッドの名前はその Identifier であり,及び検索するクラス又はインタフェースは,FieldName によって命名されたフィールドの宣言された型とする。
. Identifier であるならば,そのメソッドの名前はその Identifier であり,及び検索されるクラス又はインタフェースは,その Primary の型とする。
super . Identifier であるならば,メソッドの名前は Identifier であり,及び検索されるクラスは,その宣言がそのメソッド呼出しを含むクラスの上位クラスとする。
T がメソッド呼出しを直接的に含む型宣言だと仮定すると,次のような状況のいずれかが発生する場合に,コンパイル時エラーになる。
. super . Identifier であるならば,そのメソッドの名前は Identifier であり,及び検索されるクラスは ClassName で指示されるクラス C の上位クラスとする。
もし C が現在のクラスの字句的に取り囲むクラスでないならば,コンパイル時エラーが発生する。
もし C がクラス Object であるなら,コンパイル時エラーが発生する。
もし次のような状況のいずれかであるならば,コンパイル時エラーが発生する。
int の定数が,決して暗黙のうちに byte,short 又は char に縮小されないということを除いて,代入変換(5.2)と同じとする。
メソッド宣言がメソッド呼出しから参照可能 (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 であり,メソッド呼出し変換では int は byte 型に変換できない。
その変数 color の初期化で使用される代入変換は,int 型から byte 型へ定数の暗黙の変換を実行する。
なぜなら,その値 37 は byte 型で表すのに十分小さいから許されている。
しかし,メソッド呼出し変換ではそのような変換は認められない。
しかし,もしメソッド setColor が byte の代わりに int をとるように宣言されていたならば,メソッド呼出しは両方とも正しい。
つまり,最初の呼出しはメソッド呼出し変換が byte から int へ拡張する変換を許すために可能とする。
しかし,縮小キャストが setColor の本体には必要とする。
void setColor(int color) { this.color = (byte)color; }
非公式な直観としては,もし最初のメソッドによって処理されるあらゆる呼出しがコンパイル時型エラーなしに他に渡されることができるならば,そのメソッド宣言はより特殊とする。
正確な定義を次に示す。 m を名前とすると,m と命名された二つのメソッド宣言があり,それぞれが n 個の仮引数をもつと仮定する。 もし一つの宣言がクラス又はインタフェース T 内に現れ,その仮引数の型は T1, . . . , Tn であり,さらにもう一方の宣言がクラス又はインタフェース U 内に現れ,その仮引数の型は U1,. . . , Un とする。 それならば,次が共に成り立つ時に限り,T の中で宣言されたメソッド m は,U の中で宣言されたメソッド m よりも特殊 (more specific)とする。
もしメソッドが適用可能かつアクセス可能で,その他には,より特殊で適用可能及びアクセス可能なメソッドがないならば,最大限に特殊 (maximally specific) であると呼ばれる。
もしちょうど一個の最大限に特殊なメソッドがあるならば,それは実際に 最も特殊 (the most specific)なメソッドとする。 それは必然的に,適用可能でアクセス可能な他のどのメソッドよりも特殊とする。 したがって,それは15.12.3で示されたように,さらにコンパイル時の検査が必要とする。
二つ又はそれ以上の最大限に特殊なメソッド宣言があるために,どのメソッドも最も特殊ではないことがある。 この場合を,次に示す。
abstract を宣言しないのならば,それは最も特殊なメソッドとする。
abstract を宣言する。
最も特殊なメソッドは,その最大限に特殊なメソッドの中から勝手に選択される。
しかし,その最も特殊なメソッドは,検査例外がそれぞれの最大限に特殊なメソッドのthrows節の中で宣言された場合に限り,その例外を投げると考えられる。
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 の二つの宣言があり,どちらも他方より特殊ではないことである。
それゆえ,このメソッド呼出しはあいまいとする。
static void test(ColoredPoint p, ColoredPoint q) {
System.out.println("(ColoredPoint, ColoredPoint)");
}
これは他の二つより特殊になるので,このメソッド呼出しはもはやあいまいではない。
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 メソッドは,この例のプログラムをエラーなしにコンパイルできるようにする返却値の型をもつ場合でさえも,選択されない。
例として,二つのコンパイル単位を考える。一つは クラス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 を期待していた。
なぜなら,実引数 ColoredPoint は color フィールドを持ち,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.を参照すること。
this (15.8.3)が定義されない場所で,インスタンスメソッドを呼び出すために使用できないからである。)。
TypeName . Identifier の形式の MethodName をもつならば,そのコンパイル時宣言は static でなければならない。
もし,そのメソッド呼出しがインスタンスメソッドに対するものであるならば,コンパイル時エラーが発生する
(この理由は,この形式のメソッド呼出しはインスタンスメソッド内で this として機能するオブジェクトへの参照を指定できないからである)。
super . Identifier 形式の MethodName をもつ場合には,
super . Identifier 形式の MethodName をもつならば,
abstract であるならば,コンパイル時エラーが発生する。
void ならば,メソッド呼出しは最上位の式でなければならない。
つまり,式文(14.8)又は
for 文(14.13)の ForInit 又は ForUpdate 部分の中の Expression であるか,又はコンパイル時エラーが発生する
(その理由は,そのようなメソッド呼出しは値を返さないので,値が必要でない場合にだけ使用されるべきであるからである。)。
void。
static 修飾子を含むならば,呼出しのモードは static とする。
nonvirtual とする。
super . Identifier の形又は ClassName.super.Identifier の形式であるならば,呼出しのモードは super とする。
interface とする。
virtual とする。
void でないならば,そのメソッド呼出しの式の型はコンパイル時宣言で規定される返却値の型とする。
static であるならば,ターゲットへの参照はない。
this の n 番目の字句的に取り囲む型宣言(8.1.2)であるように,n が整数であると仮定する。
したがって,そのターゲットの参照は,this の n 番目の字句的に取り囲むインスタンス(8.1.2)とする。
もし this の n 番目の字句的に取り囲むインスタンス(8.1.2)が存在しなければ,コンパイル時エラーが発生する。
super を含む MethodInvocation に3番目の生成規則が適用されるならば,そのターゲットへの参照は this の値とする。
NoSuchMethodError (これは IncompatibleClassChangeError の下位クラスである)が発生する。
もしその呼出しのモードが interface ならば,その実装はターゲットの参照型がその規定されたインタフェースを既に実装しているかについても検査しなければならない。
もしそのターゲットの参照型がまだインタフェースを実装していないならば,IncompatibleClassChangeError が発生する。その実装は,リンクの間に,その型 T 及びそのメソッド m がアクセス可能であることも保証しなければならない。 型 T に対しては,次の通りとする。
public ならば,T はアクセス可能とする。
protected ならば,T は C が T の下位クラスである場合に限り,アクセス可能とする。
public であるならば,m はアクセス可能とする(インタフェースのすべてのメンバは public である(9.2)。)。
protected であるならば,T が C と同一パッケージに属するか,又は C が T 又は T の下位クラスである場合に限り,mはアクセス可能とする。
private ならば,C が T であるか,又はCがTを取り囲むか,又はTがCを取り囲むか,又はT及びCの両方が3番目のクラスに取り囲まれる場合に限り,m はアクセス可能とする。
IllegalAccessError が発生する(12.3)。
もし呼出しのモードが static であるならば,ターゲットの参照は必要なく,上書きも許されない。
クラス T のメソッド m が呼び出されるメソッドになる。
そうでなければ,インスタンスメソッドが呼び出され,ターゲットの参照が存在する。
もしターゲット参照が null であるならば,この時点で NullPointerException が投げられる。
そうでなければ,そのターゲットの参照は ターゲットオブジェクト (target object) を参照すると言い,及び呼び出されたメソッドで this キーワードの値として使用される。
呼出しのモードのための他の四つの可能性を検討する。
もし呼出しのモードが nonvirtual であるならば, 上書きは許されない。
クラス T のメソッド m が呼び出されるべきとする。
そうでなければ,呼出しのモードは interface,virtual 又は super のいずれかになり,上書きされるかもしれない。
その場合,動的なメソッド検索 (dynamic method lookup) が使用される。
動的な検索の過程はクラス S から開始され,次のように決定される。
interface 又は virtual であるならば,クラス S が初めにターゲットオブジェクトの実際の実行時クラス R となる。
これは,ターゲットオブジェクトが配列のインスタンスである場合でも正しい
(呼出しのモードが interface であれば, R は T を実装する必要があり,呼出しのモードが virtual であれば,R は T 又は T の下位クラスである必要があることに注意すること)。
super であるならば,S は最初はメソッド呼出しの限定型(13.1)とする。
X が,そのメソッド呼出しのターゲット参照のコンパイル時型だと仮定する。
super 又は interface であるならば,そのメソッドを呼び出し,手続きは終了する。
virtual であり,及び S 内の宣言が X.m を上書き(8.4.6.1)するならば,S 内で宣言したメソッドが呼び出されるメソッドであり,手続きは終了する。
ここで明示的に示した動的検索過程が,例えば,クラスごとのメソッドディスパッチ表の生成と使用又は効率的なディスパッチのために使用されるクラスごとの他のコンストラクタの生成の副作用として,しばしば暗黙的に実装されるということには注意を要する。
次に,新しい 活性化フレーム (activation frame) が作成される。
これは局所変数用の領域,呼び出されるメソッドが使用するスタック,その他すべての実装に必要な情報一覧(スタックポインタ,プログラムカウンタ,前の活性化フレームへの参照など)のための領域とともに,ターゲット参照(存在する場合)及び実引数値(存在する場合)を含む。
もし,そのような活性化フレームを生成するのに利用可能な十分なメモリがないならば,OutOfMemoryError が投げられる。
新しく作り出された活性化フレームは,現在の活性化フレームになる。
これにより,実引数値を新たに作り出された対応するメソッドの仮引数変数に割当てて,もし存在すれば this として利用可能なターゲット参照を作成する。
各実引数値がそれに対応する仮引数変数に割り当てられる前に,任意の要求された値集合変換(5.1.8)を含むメソッド呼出し変換(5.3)がおこなわれる。
もしそのメソッド m が native メソッドであり,必要なネイティブの実装依存なバイナリコードがロードされていない,又はそうでなくても動的にリンクすることができないならば,UnsatisfiedLinkError が投げられる。
もしそのメソッド m が synchronized 宣言されていないならば,制御は呼び出されるメソッド m の本体に移る。
もしそのメソッド m がsynchronized宣言されているならば,制御の移動の前にオブジェクトがロック設定されなければならない。
カレントスレッドがロックを獲得するまでは,それ以上処理を進めることはできない。
もしターゲット参照があるならば,ロック設定されなければならない。
そうでなければ,そのメソッド m が存在するクラス S のための Class オブジェクトがロック設定されなければならない。
そして,制御は呼び出されるメソッド m の本体に移される。
オブジェクトは正常完了,中途完了に関わらず,メソッド本体の実行が終了したとき,自動的にロック解除される。
ロック設定及びロック解除は,ちょうどそのメソッドの本体がsynchronized文(14.18)の中に埋め込まれているかのように振る舞う。
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
favorite は null を返すが,NullPointerException は投げられない。
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 を表示しない。
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 メソッドが codeColoredPoint の clear メソッドを呼び出し,次のテストプログラムは,
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 で参照されるオブジェクトの実行時クラスに基づいて)"遅く"選択されたメソッドを呼び出すことを意味する。
これはプログラマに抽象化を拡張する強力な方法を提供するとともに,オブジェクト指向プログラミングの重要な考えでもある。super を用いたメソッド呼出しsuper を使用することによって,そのメソッド呼出しを含むクラス中のどの上書き宣言も無視してアクセスしてもよい。
インスタンス変数にアクセスするとき,super は this (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 によって参照されるオブジェクトの実行時のクラスによって選択されるからである。
キャストはオブジェクトのクラスを変えない。
したがって,そのクラスがその規定された型と互換性があるかどうかを検査するだけとする。
ArrayAccess: ExpressionName [ Expression ] PrimaryNoNewArray [ Expression ]配列アクセス式は,左角括弧の前の 配列参照式 (array reference expression) 及び角括弧の中の インデクス式 (index expression) の二つの副式からなる。 配列参照式は,名前又は配列生成式(15.10)でない一次式でもよいことに注意すること。
配列参照式の型は配列型(構成要素の型が T の配列をT[] と呼ぶ)でなければならない。
そうでなければ,コンパイル時エラーが発生する。
したがって,配列アクセス式の型は T とする。
インデクス式は,単項数値昇格(5.6.1)を受け,その昇格された型は int でなければならない。
配列参照の結果は型 T の変数,すなわち,インデクス式の値によって選択される配列中の変数とする。
この結果として生じる変数は,その配列の構成要素であり,配列参照が final 変数から得られたとしても,決して final とは考えられない。
null ならば,NullPointerException が投げられる。
IndexOutOfBoundsException が投げられる。
final 変数だとしても,決して final とみなされないことに注意すること。) 。
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.8で議論したように,名前を一次式とは考えずに,文法におけるあいまいさを避けるために区別して扱う。
それらは,ここに限れば後置式の優先順位のレベルで交換可能になる。
PostfixExpression: Primary ExpressionName PostIncrementExpression PostDecrementExpression
++PostIncrementExpression: PostfixExpression ++
++ 演算子があとに続いた後置式は後置増分式とする。
その後置式の結果は数値型の変数でなければならない。
そうでなければコンパイル時エラーが発生する。
その後置増分式の型は,その変数の型とする。
後置増分式の結果は,変数ではなく,値とする。
実行時に,もしそのオペランド式の評価が中途完了するならば,その後置増分式も同じ理由で中途完了し,増分は起こらない。
そうでなければ,値 1 が変数の値に加えられ,その合計がその変数に記憶される。
その加算の前に,二項数値昇格(5.6.2)がその値 1 及びその変数の値に実行される。
必要ならば,その合計は,記憶される前にその変数の型へのプリミティブ型の縮小変換(5.1.3)によって縮小される。
その後置増分式の値は,その新しい値が記憶される前の変数の値とする。
上記の二項数値昇格は,数値集合変換(5.1.8)を含むかもしれないことに注意すること。 必要ならば,数値集合変換は,その加算に対して,それが変数に記憶される前に適用される。
final と宣言された変数を加算することはできない。
なぜなら,final 変数のアクセスが式として使用されたとき,その結果は値であり,変数ではないからである。
したがって,それを後置増分演算子のオペランドとして使用することはできない。
--PostDecrementExpression: PostfixExpression --
-- 演算子があとに続いた後置式は後置減分式とする。
その後置式の結果は数値型の変数でなければならない。
そうでなければコンパイル時エラーが発生する。
その後置減分式の型は,その変数の型とする。
後置減分式の結果は,変数ではなく値とする。
実行時に,もしオペランド式の評価が中途完了するならば,その後置減分式も同じ理由で中途完了し,減分は起こらない。
そうでなければ,値 1 が変数の値から減じられ,その差分がその変数に記憶される。
その減算の前に,二項数値昇格(5.6.2)がその値 1 及びその変数の値に実行される。
必要ならば,その差分は,その新しい値が記憶される前に,その変数の型へのプリミティブ型の縮小変換(5.1.3)によって縮小される。
後置減分式の値は,新しい値が記憶される前の変数の値とする。
上記の二項数値昇格は,数値集合変換(5.1.8)を含むかもしれないことに注意すること。 必要ならば,数値集合変換は,その差分に対して,それが変数に記憶される前に適用される。
final と宣言された変数を減算することはできない。
なぜなら,final 変数のアクセスが式として使用されたとき,その結果は値であり,変数ではないからである。
したがって,それを後置減分演算子のオペランドとして使用することはできない。
+,-,++,--,~,! 及びキャスト演算子を含む。
単項演算子をもつ式は,右から左へグループ化される。
つまり,-~x は,-(~x) と同じ意味とする。
UnaryExpression: PreIncrementExpression PreDecrementExpression + UnaryExpression - UnaryExpression UnaryExpressionNotPlusMinus PreIncrementExpression: ++ UnaryExpression PreDecrementExpression: -- UnaryExpression UnaryExpressionNotPlusMinus: PostfixExpression ~ UnaryExpression ! UnaryExpression CastExpression15.16の次の生成規則を,便宜上ここで繰り返す。
CastExpression: ( PrimitiveType ) UnaryExpression ( ReferenceType ) UnaryExpressionNotPlusMinus
++++ 演算子が先行した単項式は前置増分式とする。
その単項式の結果は数値型の変数でなければならない。
そうでなければ,コンパイル時エラーが発生する。
その前置増分式の型は,その変数の型とする。
その前置増分式の結果は,変数ではなく値とする。
実行時に,もしそのオペランド式の評価が中途完了するならば,前置増分式は同じ理由で中途完了し,加算は起こらない。
そうでなければ,その値 1 がその変数の値に加えられ,その合計はその変数に記憶される。
その加算の前に,二項数値昇格(5.6.2)が,その値 1 及びその変数の値に実行される。
必要ならば,その合計は,記憶される前に,その変数の型へのプリミティブ型の縮小変換(5.1.3)によって縮小される。
その前置増分式の値は,その新しい値が記憶された後の変数の値とする。
上記の二項数値昇格は,数値集合変換(5.1.8)を含むかもしれないことに注意すること。 必要ならば,数値集合変換は,その加算に対して,それが変数に記憶される前に適用される。
final と宣言された変数を加算することはできない。
なぜなら,final 変数へのアクセスが式として使用されたとき,その結果は値であり,変数ではないからである。
したがって,それを前置増分演算子のオペランドとして使用することはできない。
---- 演算子が先行した単項式は前置減分式とする。
その単項式の結果が数値型の変数でなければならない。
そうでなければ,コンパイル時エラーが発生する。
その前置減分式の型は,その変数の型とする。
その前置減分式の結果は,変数ではなく,値とする。
実行時に,もしオペランド式の評価が中途完了するならば,前置減分式は同じ理由で中途完了し,減算は起こらない。
そうでなければ,その値 1 がその変数の値から引かれ,その差分はその変数に記憶される。
その減算の前に,二項数値表現(5.6.2)がその値 1 及びその変数の値に実行される。
必要ならば,その差分は,記憶される前に,その変数の型へのプリミティブ型の縮小変換(5.1.3)によって縮小される。
その前置減分式の値は,その新しい値が記憶された後の変数の値とする。
上記の二項数値昇格は,値集合変換(5.1.8)を含むかもしれないことに注意すること。
必要ならば,数値集合変換は,その減算に対して,それが変数に記憶される前に適用される。
final と宣言された変数を減算することはできない。
なぜなら,final 変数へのアクセスが式として使用されたとき,その結果は値であり,変数ではないからである。
したがって,それを前置増分演算子のオペランドとして使用することはできない。
++ 演算子式のオペランドの型はプリミティブ数値型でなければならない。
そうでなければ,コンパイル時エラーが発生する。
単項数値昇格(5.6.1)がそのオペランドに実行される。
その単項加算式の型は,そのオペランドの昇格された型とする。
そのオペランドの結果が変数だとしても,その単項加算式の結果は変数ではなく値とする。実行時に,その単項加算式の値はそのオペランドの昇格された値とする。
-- 演算子式のオペランドの型は,プリミティブ数値型でなければならない。
そうでなければ,コンパイル時エラーが発生する。
単項数値昇格(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 に等しいからである。
単項マイナスは,単に浮動小数点の符号を逆にする。
特に重要な場合を以下に示す。
~~ 式のオペランドの型は,プリミティブの整数的な型でなければならない。
そうでなければ,コンパイル時エラーが発生する。
単項数値昇格(5.6.1)は,そのオペランドに対して実行される。
単項のビット単位の補数式の型は,そのオペランドの昇格された型とする。
実行時に,単項のビット単位の補数式の値は,そのオペランドの昇格された値のビット単位の補数となる。
すべての場合において,~x は (-x)-1 に等しいことに注意すること。
!! 演算子のオペランド式の型は boolean でなければならない。
そうでなければ,コンパイル時エラーが発生する。
単項論理補数式の型は boolean とする。
実行時に,その単項論理補数式は,もしそのオペランドの値が false ならば true となり,もしそのオペランドの値が true ならば false となる。
boolean かどうかを確認すること,又は実行時にある参照値が規定された参照型と互換性のあるクラスのオブジェクトを参照しているかどうかを検査することなどを実行する。
CastExpression: ( PrimitiveType Dimsopt ) UnaryExpression ( ReferenceType ) UnaryExpressionNotPlusMinusUnaryExpression と UnaryExpressionNotPlusMinus の間の区別の議論については,15.15を参照すること。
キャスト式の型は,括弧内に出現する名前の型とする (括弧及び括弧が含む型をキャスト演算子 (cast operator) と呼ぶことがある。)。 キャスト式の結果は,そのオぺランドの式の結果が変数だとしても,変数ではなく値とする。
キャスト演算子は,型 float 又は double の値に対する数値集合(4.2.3)の選択には効果がない。
その結果,FP厳密(15.4)ではない式の中の型 float へのキャストは,その値を単精度数値集合の要素に変換する必要はなく,及びFP厳密でない式の中の型 double へのキャストは,その値を倍精度数値集合の要素に変換する必要はない。
実行時に,オぺランドの値をキャスト変換(5.5)によって,そのキャスト演算子で規定された型に変換する。
Java言語では,すべてのキャストが許されるわけではない。
あるキャストは,コンパイル時エラーを生じる。
例えば,プリミティブ値は,参照型にキャストしてはいけない。
あるキャストは,コンパイル時に実行時に常に正しいことを保証できる。
例えば,あるクラス型の値を,その上位クラスの型に変換することは常に正しい。
このようなキャストは,実行時に特別な動作を要求しないほうがよい。
最後に,あるキャストは,常に正しいか常に正しくないかをコンパイル時に特定できない。
そのようなキャストは,実行時に検査が必要とする。
もし受け入れられないキャストを実行時に検出したならば,ClassCastException が投げられる。
*,/ 及び % は,乗除演算子 (multiplicative operators) と呼ぶ。
これらは同じ優先順位をもち,構文的に左結合とする(左から右にグループ化する。)。
MultiplicativeExpression: UnaryExpression MultiplicativeExpression * UnaryExpression MultiplicativeExpression / UnaryExpression MultiplicativeExpression % UnaryExpression乗除演算子のオぺランドのそれぞれの型は,プリミティブ数値型でなければならない。 そうでなければ,コンパイル時エラーが発生する。 そのオぺランドに対して,二項数値昇格が実行される(5.6.2)。 乗除式の型は,そのオぺランドの昇格された型とする。 もしこの昇格された型が
int 又は long であるならば,整数演算を実行する。
もしこの昇格された型が float 又は double であるならば,浮動小数点演算を実行する。二項数値昇格が,数値集合変換(5.1.8)を実行することに注意すること。
** 演算子は乗算を実行し,そのオぺランドの積を生成する。
もしオぺランドの式が副作用をもたないならば,乗算は可換的演算とする。
オぺランドがすべて同じ型のとき,整数乗算は結合的であるが,浮動小数点乗算は結合的ではない。もし整数乗算がオーバフローしたならば,その結果は数学的な積を十分大きな2の補数形式で表現したときの低位ビットとする。 その結果として,もしオーバフローが発生したならば,その結果の符号は二つのオぺランド値の数学的積の符号と同じでないかもしれない。
浮動小数点乗算の結果は,次のようにIEEE 754の算術の規則に従う。
* は決して実行時例外を投げない。// 演算子は除算を実行し,そのオぺランドの商を生成する。
その左辺オぺランドは被除数であり,その右辺オぺランドは除数とする。
整数除算は結果を 0 方向に丸める。
つまり,二項数値昇格(5.6.2)の後の整数のオぺランド n 及び d に対して生成される商は,
を満足しながら,その大きさが可能な限り大きい整数値 q とする。
さらに,
であって,n 及び d が同じ符号をもつとき,q は正とする。
しかし,
であって,n 及び d が反対の符号をもつとき,q は負とする。
この規則を満足しない特別な場合が一つだけ存在する。
もしその被除数がその型に対して可能な最大の大きさの負の整数であり,その除数が -1 であるならば,整数オーバフローが発生し,その結果はその被除数と同じとする。
オーバフローにもかかわらず,この場合は例外は投げられない。
一方,整数の除算における除数の値が 0 ならば,ArithmeticException が投げられる。
浮動小数点の除算の結果は,IEEE算術の規定によって次のように決定される。
/ の評価は決して実行時例外を投げない。%% 演算子は,暗黙の除算によってそのオぺランドの剰余を生成する。
その左辺オぺランドは被除数であり,及びその右辺オぺランドは除数とする。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算術の規則によって次のように決定される。
によって定義される。
ここで,q は,
が負の場合に限り負の整数,
が正の場合に限り正の整数であり,及びその大きさは,n 及び d の真の数学的な商の大きさを超えない範囲で,可能な限り大きい。
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
+ 及び - は加減演算子 (additive operators) と呼ばれる。
これらは同じ優先順位をもち,構文的に左結合とする(左から右にグループ化する。)。
AdditiveExpression: MultiplicativeExpression AdditiveExpression + MultiplicativeExpression AdditiveExpression - MultiplicativeExpressionもし
+ 演算子のいずれかのオぺランドの型が String であるならば,その演算は文字列の連結とする。
そうでなければ,+ 演算子の各オペランドの型は,プリミティブ数値型でなければならない。
そうでなければ,コンパイル時エラーが発生する。
すべての場合において,二項 - 演算子の各オペランドは,プリミティブ数値型でなければならない。
そうでなければ,コンパイル時エラーが発生する。
+String であるならば,実行時に文字列を生成するために,他方のオペランドに対して文字列変換を実行する。
その結果は,二つのオペランドの文字列を連結して新たに作成した String オブジェクトへの参照とする。
新たに作成した文字列の中では,左辺オぺランドの文字が右辺オぺランドの文字に先行する。String に変換できる。
最初に,プリミティブ型 T の値 x を,適切なクラスインスタンス生成式への実引数にその値を与えたかのように参照値に変換される。
boolean であるならば,new Boolean(x) を使用する。
char であるならば,new Character(x) を使用する。
byte,short 又は intであるならば,new Integer(x) を使用する。
long であるならば,new Long(x) を使用する。
float であるならば,new Float(x) を使用する。
double であるならば,new Double(x) を使用する。
String に変換される。
この後は,参照値だけを考慮する必要がある。
もしその参照が null であるならば,それは文字列 "null" (四つのASCII文字 n,u,l,l)に変換される。
そうでなければ,その変換は,その参照されているオブジェクトのメソッド toString を実引数なしで呼び出したかのように実行される。
しかし,もしメソッド toString の呼出し結果が null であるならば,文字列 "null" が代わりに使用される。
toString メソッドは,基本クラス Object によって定義されている。
多くのクラスがそれを上書きしており,特にBoolean,Character,Integer,Long,Float,Double 及び String がある。
String オブジェクトの作成及び廃棄を避けるために,変換及び連結を一段階で実行してもよく,Javaコンパイラは,繰り返される文字列連結の性能向上を目的として,式の評価によって作成される中間的な String オブジェクトの数を減らすために,StringBuffer クラス又は同様の技術を使用してもよい。プリミティブ型に対しては,処理系はプリミティブ型から文字列に直接変換することによって,ラッパーオブジェクトの作成を最適化してもよい。
これは次の結果を生成する。"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:"
+ 及び - )+ 演算子は,二つのオぺランドが数値型のときに加算を実行し,そのオぺランドの和を生成する。
二項 - 演算子は,減算を実行し,二つの数値オぺランドの差を生成する。
そのオペランドに対して二項数値昇格が実行される (5.6.2)。
数値オぺランドに対する加減式の型は,そのオぺランドの昇格された型とする。
この昇格された型が int 又は long ならば,整数算術が実行される。
この昇格された型が float 又は double ならば,浮動小数点算術が実行される。
二値数値昇格は数値集合変換(5.1.8)を実行することに注意すること。
もしそのオぺランドの式が副作用をもたないならば,加算は可換的な演算とする。 オペランドがすべて同じ型のとき,整数加算は結合的であるが,浮動小数点加算は結合的ではない。
もし整数の加算がオーバフローしたならば,その結果は数学的な和を十分大きな2の補数形式で表現したときの低位ビットとする。 オーバフローが発生すれば,その結果の符号は二つのオぺランド値の数学的和の符号と同じではない。
浮動小数点加算の結果は,次のIEEE 754算術の規則を用いて決定される。
- 演算子は,数値型の二つのオぺランドに適用したときに減算を実行し,そのオぺランドの差を生成する。
その左辺オぺランドは被減数であり,及びその右辺オぺランドは減数とする。
整数及び浮動小数点減算の両方に対して,常に a-b は a+(-b) と同じ結果を生成する。
整数値についてはゼロからの減算は符号反転と同じであるが,浮動小数点オぺランドについては,ゼロからの減算は符号反転と同じではないことに注意すること。
なぜなら,x が +0.0 ならば 0.0-x は +0.0 に等しいが,-x は -0.0 に等しいからである。
オーバフロー,アンダフロー又は情報の損失が発生するかもしれない事実にもかかわらず,数値加減演算子の評価は決して実行時例外を投げない。
<<,符号付き右シフト >>,及び符号なし右シフト >>>を含む。
それらは構文的に左結合とする(左から右にグループ化する。)。
シフト演算子の左辺オぺランドはシフトされる値であり,右辺オぺランドはシフト幅を規定する。
ShiftExpression: AdditiveExpression ShiftExpression << AdditiveExpression ShiftExpression >> AdditiveExpression ShiftExpression >>> AdditiveExpressionシフト演算子のオぺランドのそれぞれの型は,プリミティブな整数的な型でなければならない。 そうでなければ,コンパイル時エラーが発生する。 そのオペランドに対して二項数値昇格(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 の値は,n を s ビット位置だけ左にシフトした値とする。
これは(オーバフローが発生したとしても)2の s 乗の乗算に等しい。
n>>s の値は,n を符号拡張を伴って s ビット位置だけ右にシフトした値とする。
この結果の値は
とする。
n の非負数の値に対しては,これは整数除算演算子/によって計算される,2の s 乗の切り捨て整数除算に等しい。
n>>>s の値は,ゼロ拡張を伴ってn を s ビット位置分右にシフトした値とする。
もし 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 に等しいことに注意すること。)。
a<b<c を (a<b)<c と構文解析するが,これは常にコンパイル時エラーとする。
なぜなら,a<b の型は常に boolean であり,及び< は boolean 値に対する演算子ではないからである,
RelationalExpression: ShiftExpression RelationalExpression < ShiftExpression RelationalExpression > ShiftExpression RelationalExpression <= ShiftExpression RelationalExpression >= ShiftExpression RelationalExpression instanceof ReferenceType関係式の型は,常に
boolean とする。<, <=, >, 及び >=int 又は long であるならば,符号付き整数比較が実行される。
もしこの昇格された型が float 又は double であるならば,浮動小数点比較が実行される。二値数値昇格は,数値集合変換(5.1.8)を実行することに注意すること。 それらの値を表現するためにどの数値集合を用いても,比較は浮動小数点値に対して正確に実行される。
浮動小数点比較の結果は,IEEE 754標準の規定によって決定されるように,次のとおりとする。
false とする。
-0.0<0.0 は false であるが,-0.0<=0.0 は true とする
(しかし,メソッド Math.min 及び Math.max は,厳密に負のゼロを正のゼロよりも小さいと扱うことに注意すること。)。
< 演算子によって生成される値は,もしその左辺オぺランドの値が右その辺オぺランドの値より小さければ,true であり,そうでなければ false とする。
<= 演算子によって生成される値は,もしその左辺オぺランドの値がその右辺オぺランドの値より小さい又は等しければ,true であり,そうでなければ false とする。
> 演算子によって生成される値は,その左辺オぺランドの値がその右辺オぺランドの値より大きければ,true であり,そうでなければfalseとする。
>= 演算子によって生成される値は,その左辺オぺランドの値がその右辺オぺランドの値より大きい又は等しければ,trueであり,そうでなければfalseとする。
instanceofinstanceof 演算子の RelationalExpression オぺランドの型は,参照型又は空型でなければならない。
そうでなければ,コンパイル時エラーが発生する。
instanceof 演算子の後に記述する ReferenceType は,参照型又は空型でなければならない。
そうでなければ,コンパイル時エラーが発生する。
実行時に,instanceof演算子の結果は,もし RelationalExpression の値が null でなく,その参照が例外 ClassCastException を投げずに ReferenceType にキャスト(15.16)できるならば,trueとする。
そうでなければ,その結果は false とする。
もし RelationalExpression の ReferenceType へのキャストがコンパイル時エラーとして拒否されれば,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 に正しくキャストできないならば,そのキャストは実行されないからとする。a==b==c は (a==b)==c と構文解析する。
a==b の結果の型は,常に boolean であり,したがって c は型 boolean でなければならない。
そうでなければ,コンパイル時エラーが発生する。
つまり,a==b==c は ,a, b 及び c がすべて等しいかどうかを検査しない。
EqualityExpression: RelationalExpression EqualityExpression == RelationalExpression EqualityExpression != RelationalExpression
== (等価)及び != (不等価)演算子は,優先順位が低いという点を除いては関係演算子と類似している。
したがって,a<b==c<d は,a<b 及び c<d が同じ真値をもつときには,常に trueとする。
等価演算子は,数値型の二つのオぺランド,型 boolean の二つのオぺランド又はそれぞれが参照型若しくは空型の二つのオぺランドを比較するために使用してよい。
そうでなければ,コンパイル時エラーが発生する。
等価式の型は,常に boolean とする。
すべての場合において,a!=b は !(a==b) と同じ結果を生成する。
もしオぺランド式が副作用をもたないならば,その等価演算子は可換的とする。
== 及び !=int 又は long であるならば,整数等価試験が実行される。
もしその昇格された型が float 又は double であるならば,浮動小数点等価試験が実行される。二値数値昇格は,数値集合変換(5.1.8)を実行することに注意すること。 比較は,それらの値を表現するためにどの数値集合数値集合を用いても,浮動小数点値を正確に処理する。
浮動小数点等価試験は,IEEE 754標準の規則にしたがって,次のように実行される。
== の結果は false であるが,!= の結果は trueとする。
実際に,試験 x!=x の結果は,xの値が NaNの場合に限り真とする
(値が NaNかどうかを試験するために,メソッド Float.isNaN 及び Double.isNaN も使用してよい。)。
-0.0==0.0 は true とする。
== 演算子によって生成される値は,もしその左辺オぺランドの値が右辺オぺランドの値と等しいならば,true とする。
そうでなければ,その結果は false とする。
!= 演算子によって生成される値は,もしその左辺オぺランドの値が右辺オぺランドの値と等しくないならば,true とする。
そうでなければ,その結果は false とする。
== 及び !=boolean であるならば,その演算は論理型等価とする。
boolean 等価演算子は,結合的とする。
== の結果は,もしそのオぺランドが両方とも true 又は 両方とも true であるならば,true とする。
そうでなければ,その結果は false とする。
!= の結果は,もしそのオぺランドが両方とも true 又は両方とも true であるならば,false とする。
そうでなければ,その結果は true とする。
したがって,!= は,論理型オぺランドに適用するときに ^(15.22.2)と同様に振る舞う。
== 及び !=もしどちらか一方のオぺランドの型を他方のオぺランドの型にキャスト変換(5.5)によって変換することが不可能であるならば,コンパイル時エラーが発生する。 その二つのオぺランドの実行時の値は,必然的に不等価とする。
実行時には,== の結果は,そのオぺランドの値が両方とも null,両方とも同じオブジェクト若しくは配列を参照しているならば,true とする。
そうでなければ,その結果はfalseとする。
!= の結果は,もしそのオぺランドの値が両方とも null 又は両方とも同じオブジェクト若しくは配列を参照していれば,false とする。
そうでなければ,その結果はtrueとする。
== は,型 String への参照の比較に使用してもよいが,そのような等価試験は,その二つのオぺランドが同じオブジェクト String を参照しているかどうかを決定する。
もしそのオぺランドが違うオブジェクト String であるならば,それらが同じ文字の並びを含んでいたとしても,その結果はfalseとする。
二つの文字列 s 及び t の内容は,メソッド呼出し s.equals(t) によって試験することができる。
3.10.5も参照すること。
&,XOR演算子 ^ 及びOR演算子| を含む。
これらの演算子は,異なる優先度をもち,& は最大の優先度をもち,| は最小の優先度をもつ。
これらの演算子は各々構文的に左結合とする(各々左から右へとグループ化する。)。
各々の演算子は,もしそのオペランド式がどんな副作用ももたなければ,可換的とする。
各々の演算子は結合的とする。
AndExpression: EqualityExpression AndExpression & EqualityExpression ExclusiveOrExpression: AndExpression ExclusiveOrExpression ^ AndExpression InclusiveOrExpression: ExclusiveOrExpression InclusiveOrExpression | ExclusiveOrExpressionビット単位及び論理演算子は,数値型の二つのオペランド又は型
boolean の二つのオペランドを比較するのに使用してよい。
他のすべての場合はコンパイル時エラーになる。&,^及び|&,^ 又は | の両オペランドがプリミティブ整数型であるとき,最初にオペランドに二項数値昇格(5.6.2)を実行する。
ビット単位の演算子式の型は,そのオペランドの昇格された型とする。
&に関しては,その結果値はオペランド値のビット単位のANDとする。
^に関しては,その結果値はオペランド値のビット単位のXORとする。
|に関しては,その結果値はオペランド値のビット単位のORとする。
例えば,式 0xff00&0xf0f0 の結果は 0xf000 とする。
0xff00^0xf0f0 の結果は 0x0ff0 とする。
0xff00|0xf0f0 の結果は 0xfff0 とする。
&,^及び|&,^ 及び | の両方のオペランドが型booleanであるとき,そのビット単位の演算子式の型は型 boolean とする。
& に関しては,もし両方のオペランド値が true であるならば,その結果値は true とする。
そうでなければ,その結果値は false とする。
^ に関しては,もしそのオペランド値が異なっているならば,その結果値はtrueとする。
そうでなければ,その結果値はfalseとする。
| に関しては,もし両方のオペランド値が false であるならば,その結果値は false とする。
そうでなければ,結果値は true とする。
&&&& 演算子は,& (15.22.2)と類似しているが,しかしその左辺オペランド値が true である場合に限り,その右辺オペランドを評価する。
それは構文的に左結合とする(左から右へとグループ化する。)。
副作用及び結果値の両方に関して,完全に結合的とする。
つまり,任意の式 a,b 及び c に対して,式 ((a)&&(b))&&(c) の評価は,同じ副作用が同じ順序で発生し,式 (a)&&((b)&&(c)) の評価と同じ結果を生じる。
ConditionalAndExpression: InclusiveOrExpression ConditionalAndExpression && InclusiveOrExpression
&& のそれぞれのオペランドは,型 boolean でなければならない。
そうでなければ,コンパイル時エラーが発生する。
条件AND式の型は,常に boolean とする。
実行時に,左辺オペランド式を最初に評価する。
もしその値が false であるならば,条件AND式の値は false であり,その右辺オペランド式は評価されない。
もしその左辺オペランドの値が true であるならば,その右辺オペランド式を評価し,その値は条件AND式の値とする。
したがって,&& は,boolean オペランドに対して& と同じ結果を計算する。
その右辺オペランド式を,常にではなく条件的に評価するという点だけが異なる。
|||| は | (15.22.2) と類似しているが,その左辺オペランドの値が false の場合に限り,その右辺オペランドを評価する。
それは構文的には左結合とする(左から右へとグループ化する。)。
副作用及び結果値の両方に関して,完全に結合的とする。
つまり,任意の式 a,b 及び c に対して,式 ((a)||(b))||(c) の評価は,同じ副作用が同じ順序で発生し,式 (a)||((b)||(c)) の評価と同じ結果を生じる。
ConditionalOrExpression: ConditionalAndExpression ConditionalOrExpression || ConditionalAndExpression
|| のそれぞれのオペランドは,型 boolean でなければならない。
そうでなければ,コンパイル時エラーが発生する。
条件OR式の型は,常に boolean とする。
実行時には,その左辺オペランド式が最初に評価される。
もしその値が true であるならば,その条件OR式の値は true であり,その右辺オペランド式は評価しない。
もしその左辺オペランドの値が false であるならば,その右辺オペランド式を評価し,その値は条件OR式の値になる。
したがって,|| は boolean オペランドに対して | と同じ結果を計算する。
その右辺オペランド式を常にではなく条件的に評価する点だけが異なる。
? :? : は,二つの式のどちらを評価するのがよいかを決定するために,一つの式の論理値を使用する。
条件演算子は,構文的には右結合とする(右から左へとグループ化する。)。
そこで,a?b:c?d:e?f:g は,a?b:(c?d:(e?f:g)) と同じことを意味する。
ConditionalExpression: ConditionalOrExpression ConditionalOrExpression ? Expression : ConditionalExpression条件演算子は,三つのオペランド式をもつ。 1番目と2番目の式との間に
? が現れ,2番目と3番目の式の間に : が現れる。
最初の式は,型 boolean でなければならない。
そうでなければ,コンパイル時エラーが発生する。
条件演算子は,数値型の2番目と3番目のオペランドの選択,型 boolean の2番目と3番目のオペランドの選択,又はそれぞれが参照型か空型のいずれかである2番目と3番目のオペランドの選択に使用してよい。
他のすべての場合には,コンパイル時エラーになる。
void メソッドの呼出しは,2番目のオペランド式も3番目のオペランド式も許されないことに注意すること。
実際に,条件式は,void メソッドの起動が出現するかもしれないいかなる文脈にも出現することは許されない(14.8)。
byteであり及び他方が型 short であるならば,その条件式の型は short とする。
byte,short 又は charである,及び他方のオペランドがその値が型Tで表現可能な型 int の定数式であるならば,その条件式の型は T とする。
boolean 値は,2番目又は3番目のオペランド式のどちらを選択するために使用される。その選択されたオペランド式を評価し,その結果の値は上述した規則によって決定される条件式の型に変換される。 選択されないオペランド式は,条件式のこの特定の評価のためには評価しない。
a=b=c は a=(b=c) を意味する。
これは c の値を b に割り当て,次に b の値を a に割り当てる。
AssignmentExpression: ConditionalExpression Assignment Assignment: LeftHandSide AssignmentOperator AssignmentExpression LeftHandSide: ExpressionName FieldAccess ArrayAccess AssignmentOperator: one of = *= /= %= += -= <<= >>= >>>= &= ^= |=代入演算子の最初のオペランドの結果は変数でなければならない。 そうでなければ,コンパイル時エラーが発生する。 このオペランドは,現在のオブジェクト又はクラスの局所変数又はフィールドの名前付けされた変数であってもよく,フィールドアクセス(15.11)又は配列アクセス(15.13)から生じ得るような計算された変数であってもよい。 代入式の型はその変数の型とする。
実行時には,その代入式の結果は代入が起こった後の変数の値とする。 代入式の結果自身は,変数ではない。
final と宣言された変数は,(それが未初期化最終変数(4.5.4)でない限り)代入できない。
なぜならば,final 変数のアクセスが式として使用されるときに,その結果は値であり変数ではないので,代入演算子の最初のオペランドとして使用することはできない。
=実行時には,式は次に述べる二つの方法の中の一つで評価する。 もしその左辺オペランド式が配列アクセス式でないならば,次の三段階が要求される。
null であるならば,代入は起こらず,NullPointerException が投げられる。
IndexOutOfBoundsException が投げられる。
final であるかもしれない)。
しかし,もしコンパイラがコンパイル時に配列の構成要素が厳密に型 TC であることを証明できないならば,実行時にクラスRCが配列の構成要素の実際の型 SC と代入互換(5.2)であることを保証するために,検査がおこなわなければならない。
この検査は,検査が失敗した場合に ClassCastException ではなく ArrayStoreException が投げられることを除けば,縮小キャスト(5.5, 15.16)に似ている。
したがって,次のようになる。
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());
}
}
このプログラムは,次のように表示する。
この多くの中で最も興味深い場合は,最後から13番目とする。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
これはThreads[1]=StringBuffer => ArrayStoreException
StringBuffer への参照を,構成要素が型 Thread である配列に記憶しようとして,ArrayStoreExceptionを投げたことを示す。
そのコードは,コンパイル時には正しい型とする。
その代入は,型 Object[] の左辺及び型 Object の右辺をもつ。
実行時には,メソッド testFour への最初の実引数は,"Threadの配列"のインスタンスへの参照であり,及び3番目の実引数は,クラス StringBuffer のインスタンスへの参照とする。
+= を除くすべての複合代入演算子は,両方のオペランドがプリミティブ型であることを要求される。
+=に対しては,もしその左辺オペランドが型 String であれば,その右辺オペランドはどの型でもよい。
形式 E1op=E2 の複合代入式は,もとの式は E1 を一度だけ評価する点を除き,E1=(T)((E1)op(E2)) に等価とする。
ここで,T は E1の型とする。
型 T への暗黙のキャストは,等値変換(5.1.1)又はプリミティブ型の縮小変換(5.1.3)のどちらでもよいことに注意すること。
例えば,次のコードは正しい。
short x = 3; x += 4.6;及び,これは次の例とは同等であるので,
x は値 7 をもつ。
short x = 3; x = (short)(x + 4.6);実行時に,その式は二つの方法の中の一つで評価される。 もしその左辺オペランド式が配列アクセス式でないならば,次の四つの段階が要求される。
null であるならば,代入は起こらず,NullPointerException が投げられる。
ArrayIndexOutOfBoundsException が投げられる。
Stringでなければならない。
クラス String は final 宣言されたクラスなので,S もまた String でなければならない。
したがって,単純代入演算子では時々必要になる実行時検査は,複合代入演算子に対しては決して要求されない。
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);
}
}
このプログラムは,次のように表示する。
この多くの中で最も興味深い場合は,最後から11番目及び12番目とする。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
これらは,例外を投げることができる右辺が実際に例外を投げた場合で,さらに多くの中で唯一の場合とする。 これは,本当に,空配列参照値及び領域外インデクス値の検査後に,その右辺オペランドの評価が起こることを示している。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 が複合代入演算子 += により保存される。
この右辺オペランドの評価は,4 を k に割り当て,k+2 を値 6 と計算し,4 に 6 をかけて 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);
Expression: AssignmentExpressionC及びC++と異なり,Javaプログラム言語にはコンマ演算子は存在しない。
ConstantExpression: Expressionコンパイル時の定数式 (constant expression) は,次のものだけを使用して構成されるプリミティブ型の値又は
String を表す式とする。
String のリテラル
String へのキャスト
+, -,~ 及び ! (しかし,++ 又は -- は含まない)
*, / 及び %
+ 及び -
<<,>> 及び >>>
<,<=,> 及び >= (しかし,instanceofは含まない)
== 及び !=
&, ^ 及び |
&& 及び条件OR演算子||
? :
final 変数を参照する単純名
final 変数を参照する形式 TypeName . Identifier の限定名
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版 |