目次 | 前 | 次 | 索引 | Java言語規定 第2版 |
クラス宣言(class declaration)は,新しい参照型を定義し,その実装方法を記述する(8.1)。
入れ子クラス(nested class)は,他のクラス又はインタフェースの本体内で宣言されたクラスとする。最上位クラス(top level class)は,入れ子クラスでないクラスとする。
ここでは,最上位(7.6) ,入れ子(メンバクラス(8.5, 9.5),局所クラス(14.3) 及び匿名クラス(15.9.5)を含め)すべてのクラスの共通意味論を論じる。特定の種類のクラスに特有の詳細は,それぞれの節で論じる。
名前付きクラスは,abstract
宣言(8.1.1.1)してよい。クラスが完全に実装されていない場合は,abstract
宣言しなければならない。これらのクラスは,インスタンス化できないが,下位クラスで拡張できる。クラスをfinal
宣言 (8.1.1.2)してよい。この場合は,下位クラスをもつことができない。クラスをpublic
宣言すれば,そのクラスは,他のパッケージから参照できる。
Object
以外の各クラスは,既存のある一つのクラスの拡張(つまり下位クラス(8.1.3))とし,インタフェース(8.1.4)を実装してよい。
クラスの本体では,メンバ(フィールド,メソッド,入れ子クラス及びインタフェース),静的初期化子及びコンストラクタ(8.1.5)を宣言する。メンバ(8.2)の有効範囲(6.3)は,メンバが属するクラス宣言全体とする。フィールド,メソッド及びコンストラクタの宣言は,アクセス修飾子(6.6)public
, protected
及びprivate
を含むことができる。クラスのメンバは,宣言されたメンバ及び継承されたメンバ(8.2)の両方を含む。新しく宣言したフィールドは,上位クラス又は上位インタフェースで宣言された(同名の)フィールドを隠ぺいできる。新しく宣言したクラスメンバ及びインタフェースメンバは,上位クラス又は上位インタフェースで宣言されたクラスメンバ又はインタフェースメンバを隠ぺいできる。新しく宣言したメソッドは,上位クラス又は上位インタフェースで宣言されたメソッドを隠ぺい,実装又は上書きすることができる。
フィールド宣言(8.3)は,一度だけ具体化されるクラス変数及びクラスのインスタンスごとに新規に具体化されるインスタンス変数を記述する。フィールドは,final
宣言(8.3.1.2)してよい。この場合には,そのフィールドは,一度しか値を代入できない。すべてのフィールド宣言は,初期化子を記述してよい。
メンバクラス宣言(8.5)は,取り巻きクラスのメンバである入れ子クラスを記述する。メンバクラスは,取り巻きクラスのインスタンス変数にアクセスしない静的クラスか,又は,内部クラス(8.1.2)とする。
メンバインタフェース宣言(8.5)は,取り巻きクラスのメンバである入れ子インタフェースを記述する。
メソッド宣言(8.4)は,メソッド呼出し式(15.12)によって呼び出されるコードを記述する。クラスメソッドは,クラス型に関して呼び出される。インスタンスメソッドは,クラス型のインスタンスである特定のオブジェクトに関して呼び出される。実装方法が示されていないメソッドは,abstract
宣言しなければならない。メソッドを final
(8.4.3.3)宣言することもできる。この場合には,そのメソッドを隠ぺい又は上書きできない。メソッドは,プラットフォーム依存のnative
コード(8.4.3.4)で実装してよい。synchronized
(8.4.3.6)メソッドは,synchronized
文 (14.18)を使用したかのように,その本体を実行する前にオブジェクトを自動的にロックし,制御を戻す際にそのロックを自動的に解除する。このようにして,そのオブジェクトの動作を他のスレッド(17.)の動作と同期させることができる。
メソッド名は,オーバロード(8.4.7)してよい。
インスタンス初期化子(8.6)は,インスタンスが最初に生成される(15.9)ときに,そのインスタンスの初期化を支援するために使用される実行可能コードのブロックとする。
静的初期化子(8.7)は,クラスが最初にロードされるときに,そのクラスの初期化(12.4)を支援するために使用される実行可能コードのブロックとする。
コンストラクタ(8.8)は,メソッドと似ているが,メソッド呼出しで直接呼び出すことはできない。コンストラクタは,新しいクラスインスタンスを初期化するために使用する。メソッドと同様に,コンストラクタは,オーバロード(8.8.6)してよい。
ClassDeclaration: ClassModifiersopt class Identifier Superopt Interfacesopt ClassBodyクラス宣言内のIdentifierは,クラスの名前を規定する。取り囲むクラス又はインタフェースに同じ単純名があれば,コンパイル時エラーが発生する。
ClassModifiers: ClassModifier ClassModifiers ClassModifier ClassModifier: one of public protected private abstract static final strictfpすべての修飾子が,全種類のクラス宣言に適用されるわけではない。アクセス修飾子public は,最上位クラス(7.6)及び メンバクラス(8.5, 9.5)にだけ付随し,6.6, 8.5及び9.5で論じる。アクセス修飾子protected 及び private は,直接取り囲むクラス宣言(8.5)にだけ付随し,8.5.1で論じる。 アクセス修飾子static は,メンバクラス(8.5, 9.5)にだけ付随する。クラス宣言で同じ修飾子が2回以上出現すれば,コンパイル時 エラーが発生する。
クラス宣言でクラス修飾子が複数個出現する場合は,必須ではないが,慣習的に,ClassModifierの前述の生成規則内の記述と矛盾しない順序で出現することとする。
abstract
クラスabstract
クラスは,不完全なクラス,又は不完全と考えられるクラスとする。abstract
クラスだけが,abstract
メソッド(8.4.3.1, 9.4),つまり,宣言されているがまだ実装されていないメソッドをもってよい。abstract
宣言されていないクラスがabstract
メソッドを含んでいれば,コンパイル時エラーが発生する。次の条件のいずれかが真ならば,クラスCは,abstract
メソッドをもつ。
abstract
メソッド(8.4.3)の宣言を含む。
abstract
メソッド(8.4.6.1)を宣言する。
abstract
となる)メソッドを宣言又は継承しており,Cがそれを実装するメソッドを宣言も継承もしていない。
クラスabstract class Point { int x = 1, y = 1; void move(int dx, int dy) { x += dx; y += dy; alert(); } abstract void alert(); } abstract class ColoredPoint extends Point { int color; } class SimplePoint extends Point { void alert() { } }
Point
は,abstract
宣言されていなければならない。その理由は,alert
という名前のabstract
メソッドの宣言を含むからである。ColoredPoint
という名前のPoint
の下位クラスは,abstract
メソッドalert
を継承している,したがって,この下位クラスもabstract
宣言しなければならない。一方,SimplePoint
という名前のPoint
の下位クラスは,alert
の実装を提供している。したがって,abstract
宣言する必要はない。
クラスインスタンス生成式 (15.9)を使用して,abstract
クラスのインスタンスを生成しようとすると,コンパイル時エラーが発生する。
したがって,前述の例に次の文を続けると,コンパイル時エラーとなる。
クラスPoint p = new Point();
Point
は,abstract
なので,インスタンス化できない。しかし,変数Point
は,クラスPoint
の任意の下位クラスの参照で正しく初期化でき,クラスSimplePoint
は,abstract
宣言されていないので,次の例は正しい。
Point p = new SimplePoint();
abstract
クラスの下位クラスは,(それ自体がabstract
宣言されていなければ)インスタンス化でき,その結果,abstract
クラスのコンストラクタが実行され,そのクラスのインスタンス変数のフィールド初期化子が実行される。したがって,上の例では,SimplePoint
のインスタンス化によってPoint
のデフォルトコンストラクタ,並びにx
及びy
のフィールド初期化子が実行される。abstract
であって,abstract
メソッドをすべて実装する下位クラスを生成できないクラス型を宣言することは,コンパイル時エラーとする。この状況は,メンバとして,メソッドのシグネチャ(8.4.2)は同じだが返却値の型が異なる二つのabstractメソッドをクラスがもつときに発生する。
クラスinterface Colorable { void setColor(int color); } abstract class Colored implements Colorable { abstract int setColor(int color); }
Colored
のいかなる下位クラスも,前述の二つのabstract
メソッドの仕様を満たし,型int
の一つの実引数を取る名前setColor
のメソッドの実装を与えることは不可能となる。その理由は,インタフェースColorable
内では同じメソッドに値を返さないように要求しているのに対して,クラスColored
内ではメソッドに型int
の値を返すように要求している(8.4)からである。
クラス型は,実装を完了するためにその下位クラスが生成できるという意図がある場合に限り,abstract
宣言しなければならない。 単にクラスのインスタンス化をできなくすることが目的ならば(8.8.8),これを表現する適切な方法は,実引数なしのコンストラクタを宣言し,それにprivate
を指定し,その呼出しを決して行わず,さらに他のコンストラクタを宣言しないこととする。 この形式のクラスは,通常,クラスメソッド及び変数を含む。 インスタンス化できないクラスの例としては,クラスMath
がある。この宣言は,次のとおりとする。
public final class Math { private Math() { } // never instantiate this class . . . declarations of class variables and methods . . . }
final
クラスfinal
宣言できる。final
クラスの名前が,他のclass
宣言の extends
節(8.1.3)に出現した場合,コンパイル時エラーが発生する。 つまり,finalクラスは,下位クラスをもつことはできない。 クラスを同時にfinal
及びabstract
の両方で宣言した場合,クラスの実装を決して完了できない(8.1.1.1)ので,コンパイル時エラーが発生する。
final
クラスは,いかなる下位クラスももつことはないので,finalクラスのメソッドは,決して上書き(8.4.6.1)されることはない。
strictfp
クラスstrictfp
修飾子の効果は,クラス宣言内のすべてのfloat
及び double
式を明示的に FP厳密 (15.4)とする。これは,クラスで宣言される全メソッド及び全入れ子型が暗黙にstrictfp
となることを意味する。
クラスのすべての変数初期化子,インスタンス初期化子,静的初期化子及びコンストラクタの中でのfloat
及びdouble
式も明示的に FP厳密であることに注意。
内部クラスは,コンパイル時定数でない静的メンバを継承してよい。ただし,宣言してはならない。内部クラスでない入れ子クラスは,Java言語の通常の規則にしたがって自由に静的メンバを宣言してよい。メンバインタフェース(8.5)は,常に暗黙でstaticなので,内部クラスとは決して見なされない。class HasStatic{ static int j = 100; } class Outer{ class Inner extends HasStatic{ static final x = 3; // ok - compile-time constant static int y = 4; // compile-time error, an inner class } static class NestedButNotInner{ static int z = 5; // ok, not an inner class } interface NeverInner{} // interfaces are never inner }
文又は式が静的文脈で出現する(occurs in a static context)必要十分条件は,その文又は式を取り囲む最内のメソッド,コンストラクタ,インスタンス初期化子,静的初期化子,フィールド初期化子,又は明示的コンストラクタ文が,静的メソッド,静的初期化子,静的変数の変数初期化子,又は明示的コンストラクタ呼出し文 (8.8.5)であることとする。
内部クラスC がクラスOの直接内部クラス(direct inner class of a class O)であるのは,O がC を直ちに字句的に取り囲むクラスであり,かつC の宣言が静的文脈に出現しない場合とする。クラスC がクラスO の内部クラス(inner class of class O) であるのは,O の直接内部クラスであるか,それとも,O の内部クラスの内部クラスである場合とする。
クラスO は,それ自身のゼロ番目の字句的取り囲みクラス(zeroth lexically enclosing class of itself)とする。クラスO がクラスCのn番目の字句的取り囲みクラス(nth lexically enclosing class of a class C)であるのは,C の n - 1番目の字句的取り囲みクラスを直ちに取り囲むクラスである場合とする。
クラスO の直接内部クラスC のインスタンスi は,i の直ちに取り囲むインスタンス(immediately enclosing instance)として知られるO のインスタンスと関連する。オブジェクトの直ちに取り囲むインスタンスは,存在するなら,そのオブジェクトが生成された(15.9.2)時に決定される。
オブジェクトo は,それ自身のゼロ番目の字句的取り囲みインスタンス(zeroth lexically enclosing instance of itself)とする。オブジェクトo がインスタンスiのn番目の字句的取り囲みインスタンスであるのは,i のn - 1番目の字句的取り囲みインスタンスを直ちに取り囲むインスタンスである場合とする。
内部クラスが字句的取り囲みクラスのメンバであるインスタンス変数を参照する時には,対応する字句的取り囲みインスタンスの変数が用いられる。字句的取り囲みクラスの未初期化最終 (4.5.4)フィールドは,内部クラスの内部で割り付けられてはならない。
宣言が静的文脈に出現する内部クラスI のインスタンスは,字句的取り囲みインスタンスをもたない。しかし,I が静的メソッド又は静的初期化子内部で直ちに宣言されるならば,I は取り囲みブロックをもつ。これは,I の宣言を字句的に取り囲む最内ブロック文とする。
さらに,それ自体クラスSO の直接内部クラスであるC のすべての上位クラスS については,Sに関してiの直ちに取り囲むインスタンス(the immediately enclosing instance of i with respect to S)として知られるi に関連するSO のインスタンスが存在する。そのクラスの直接的上位クラスに関してオブジェクトの直ちに取り囲むインスタンスは,もし存在するなら,明示的コンストラクタ呼出し文によって上位クラスコンストラクタが呼び出されるときに決定される。
内部クラスで使われるが宣言されていない局所変数,形式メソッド仮引数,又は,例外ハンドラ仮引数は,final
と宣言され,かつ,内部クラスの本体の前に確実に代入(16.)されねばならない。
内部クラスは,局所(14.3),匿名(15.9.5)及び非静的メンバクラス(8.5)を含む。例を示す。
クラスclass Outer { int i = 100; static void classMethod() { final int l = 200; class LocalInStaticContext{ int k = i; // compile-time error int m = l; // ok } } void foo() { class Local { // a local class int j = i; } } }
LocalInStaticContext
の宣言は,静的メソッドclassMethod
内部の静的文脈に出現する。クラスOuter
のインスタンス変数は,静的メソッドの本体内部で使えない。特に,Outer
のインスタンス変数は,LocalInStaticContext
の本体内部で使えない。しかし, まわりのメソッドの局所変数は,(final
としていれば)エラーなしに参照できる。
宣言が静的文脈に出現しない内部クラスは,取り囲むクラスのインスタンス変数を自由に参照してよい。インスタンス変数は,常にインスタンスに関して定義される。取り囲むクラスのインスタンス変数の場合は,そのインスタンス変数はそのクラスの取り囲むインスタンスに関して定義されなければならない。例えば,上のクラスLocal
は,クラスOuter
の取り囲みインスタンスをもつ。さらに例をあげる。
ここで,class WithDeepNesting{ boolean toBe; WithDeepNesting(boolean b) { toBe = b;} class Nested { boolean theQuestion; class DeeplyNested { DeeplyNested(){ theQuestion = toBe || !toBe; } } } }
WithDeepNesting.Nested.DeeplyNested
のすべてのインスタンスは,クラス WithDeepNesting.Nested
の取り囲みインスタンス(直ちに取り囲むインスタンス) 及びクラスWithDeepNesting
の取り囲みインスタンス(第2字句的取り囲みインスタンス)をもつ。extends
節は,現在のクラスの直接的上位クラス(direct superclass)を指定する。 そのクラスは,そのクラスが拡張された元のクラスの直接的下位クラス(direct subclass)と呼ぶ。 直接的上位クラスとは,現在のクラスの実装が他のクラスの実装を導出して得られたときの,導出元クラスとする。extends
節は,クラスObject
の定義に出現してはならない。その理由は,クラスObject
が根源的なクラスであり,直接的上位クラスをもたないからである。 これ以外のクラスのクラス宣言がextends
節をもたなければ,そのクラスは,暗黙の直接的上位クラスとしてクラスObject
をもつ。
Super: extends ClassType次は,4.3に存在する生成規則だが,明確化のためにここに再度規定する。
ClassType: TypeNameClassTypeは,アクセス可能な(6.6)クラス型の名前でなければならない。そうでなければ,コンパイル時エラーが発生する。ClassTypeが,
final
(8.1.1.2)宣言されたクラスの名前ならば,コンパイル時エラーが発生する。final
クラスは,下位クラスをもつことは許されないからである。この例は,次の関係をもつ。class Point { int x, y; } final class ColoredPoint extends Point { int color; } class Colored3DPoint extends ColoredPoint { int z; } // error
Point
は,Object
の直接的下位クラスとする。
Object
は,クラスPoint
の直接的上位クラスとする。
ColoredPoint
は,クラスPoint
の直接的下位クラスとする。
Point
は,クラスColoredPoint
の直接的上位クラスとする。Colored3dPoint
の宣言は,final
クラスColoredPoint
を拡張しようとしているので,コンパイル時エラーが発生する。下位クラス(subclass)の関係は,直接的下位クラス関係の推移的閉包(transitive closure)とする。 次のいずれかが真ならば,クラスA は,クラスC の下位クラスとする。
A がC の下位クラスであるときは必ず,クラスC は,クラスA の上位クラス(superclass)であると呼ぶ。
この例での関係は,次のとおり。class Point { int x, y; } class ColoredPoint extends Point { int color; } final class Colored3dPoint extends ColoredPoint { int z; }
Point
は,クラスColoredPoint
の上位クラスとする。
Point
は,クラスColored3dPoint
の上位クラスとする。
ColoredPoint
は,クラスPoint
の下位クラスとする。
ColoredPoint
は,クラスColored3dPoint
の上位クラスとする。
Colored3dPoint
は,クラスColoredPoint
の下位クラスとする。
Colored3dPoint
は,クラスPoint
の下位クラスとする。extends
又はimplements
節で,上位クラス又は 上位インタフェースとして,あるいは,上位クラス又は上位イン タフェース名内部の限定子として,Tが記述される場合とする。 クラス C が参照型Tに依存する(depends)のは,次の条件のいずれかが成り立つ場合とする。
class Point extends ColoredPoint { int x, y; } class ColoredPoint extends Point { int color; }
クラスをロード(12.2)する際,実行時に循環的に宣言されたクラ スを検出した場合,ClassCircularityError
が投げられる。
implements
節は,宣言しようとして いるクラスの直接的上位インタフェース(direct superinterfaces)のインタフェース名を列挙する。
Interfaces: implements InterfaceTypeList InterfaceTypeList: InterfaceType InterfaceTypeList , InterfaceType次は,4.3に存在する生成規則だが,明確化のためにここに再度規定する。
InterfaceType: TypeName各InterfaceTypeは,アクセス可能な(6.6)インタフェース型の名前でなければならない。 そうでなければ,コンパイル時エラーが発生する。
一つのimplements
節の中で同じインタフェースが2回以上指定されている場合,コンパイル時エラーが発生する。
たとえインタフェースが違った形式で名前付けされていても,これが真となる。次に例を示す。
これは,名前 java.lang.class Redundant implements java.lang.Cloneable, Cloneable { int x; }
Cloneable
及び.Cloneable
が,同じインタフェースを参照しているため,コンパイル時エラーとなる。次の条件のいずれかが真ならば,インタフェース型I は,クラス型C の上位インタフェース(superinterface)とする。
この例の関係は,次のとおりとする。public interface Colorable { void setColor(int color); int getColor(); } public interface Paintable extends Colorable { int MATTE = 0, GLOSSY = 1; void setFinish(int finish); int getFinish(); } class Point { int x, y; } class ColoredPoint extends Point implements Colorable { int color; public void setColor(int color) { this.color = color; } public int getColor() { return color; } } class PaintedPoint extends ColoredPoint implements Paintable { int finish; public void setFinish(int finish) { this.finish = finish; } public int getFinish() { return finish; } }
Paintable
は,クラスPaintedPoint
の上位インタフェースとする。
Colorable
は,クラスColoredPoint
及びクラスPaintedPoint
の上位インタフェースとする。
Paintable
は,インタフェースColorable
の下位インタフェースとし,さらに,Colorable
は,Paintable
の上位インタフェースとする。
PaintedPoint
は,上位インタフェースとしてColorable
をもつ。 その理由は,Colorable
が,ColoredPoint
の上位インタフェースであること及びPaintable
の上位インタフェースであることの両方による。
宣言しようとしているクラスがabstract
でない場合,各々の直接的上位インタフェースで定義されたメソッドの宣言は,このクラス内での宣言によって実装するか,又は直接的上位クラスから継承された既存のメソッドの宣言によって実装しなければならない。 その理由は,abstract
でないクラスは,abstract
なメソッド(8.1.1.1)をもつことができないことによる。
その理由は,interface Colorable { void setColor(int color); int getColor(); } class Point { int x, y; }; class ColoredPoint extends Point implements Colorable { int color; }
ColoredPoint
がabstract
クラスではないのに,インタフェースColorable
のメソッド setColor
及びメソッドgetColor
の実装を与えていないためである。クラス内の一つのメソッド宣言が,複数の上位インタフェースのメソッドを実装することは,許される。次に例を示す。
クラスinterface Fish { int getNumberOfScales(); } interface Piano { int getNumberOfScales(); } class Tuna implements Fish, Piano { // You can tune a piano, but can you tuna fish? int getNumberOfScales() { return 91; } }
Tuna
内でのメソッド getNumberOfScales
は,インタフェースFish
内で宣言されたメソッド及びインタフェースPiano
内で宣言されたメソッドと同じ名前,シグネチャ及び戻り値の型をもつ。 したがって,Tuna
用のメソッドは,両インタフェース内のメソッドを実装していると見なされる。インタフェースinterface Fish { int getNumberOfScales(); } interface StringBass { double getNumberOfScales(); } class Bass implements Fish, StringBass { // This declaration cannot be correct, no matter what type is used. public ??? getNumberOfScales() { return 91; } }
Fish
及びインタフェースStringBass
の中で宣言されている,名前getNumberOfScales
の二つのメソッドと同じシグネチャ及び返却値の型をもつ,名前getNumberOfScales
のメソッドを宣言することはできない。 その理由は,クラスは,与えられたシグネチャに対して一つのメソッドだけしかもつことができない(8.4)ことによる。 したがって,一つのクラスでは,インタフェースFish
及びインタフェースStringBass
の両方を実装することは,不可能(8.4.6)となる。
ClassBody: { ClassBodyDeclarationsopt } ClassBodyDeclarations: ClassBodyDeclaration ClassBodyDeclarations ClassBodyDeclaration ClassBodyDeclaration: ClassMemberDeclaration InstanceInitializer StaticInitializer ConstructorDeclaration ClassMemberDeclaration: FieldDeclaration MethodDeclaration ClassDeclaration InterfaceDeclaration ;クラス型Cで宣言された,又は継承されたメンバ mの宣言の有効範囲は,入れ子型宣言を含めてC の本体全体とする。
C そのものが入れ子クラスなら,取り囲む有効範囲においてmのための同種(変数,メソッド,又は型)の定義がある。 (有効範囲は,ブロック,クラス,又はパッケージでよい。) これらの場合,C で宣言又は継承されたメンバ m は,mのほかの定義をおおい隠す。(6.3.1)
Object
は除く。
private
宣言されているクラスのメンバは,そのクラスの下位クラスによって継承されない。protected
宣言又は public
宣言されているクラスのメンバだけが,そのクラスが宣言されているパッケージ以外のパッケージで宣言されている下位クラスによって継承される。コンストラクタ,静的初期化子及びインスタンス初期化子は,メンバではないので,継承されない。
この例は,四つのコンパイル時エラーを起こす。class Point { int x, y; private Point() { reset(); } Point(int x, int y) { this.x = x; this.y = y; } private void reset() { this.x = 0; this.y = 0; } } class ColoredPoint extends Point { int color; void clear() { reset(); } // error } class Test { public static void main(String[] args) { ColoredPoint c = new ColoredPoint(0, 0); // error c.reset(); // error } }
ColoredPoint
は,main
内で要求されている二つの整数仮引数付きで宣言されたコンストラクタをもたないので,エラーが発生する。これは,ColoredPoint
が,その上位クラスPoint
のコンストラクタを継承していない事実を示す。
ColoredPoint
が,コンストラクタを宣言していないこと,そのためにデフォルトコンストラクタが自動的に生成(8.8.7)されること及びそのデフォルトコンストラクタが
に等価なことのために,もう一つのエラーが発生する。このとき,このコンストラクタは,クラスColoredPoint() { super(); }
ColoredPoint
の直接的上位クラスに対するコンストラクタを実引数なしで呼び出す。 エラーは,実引数を取らないPoint
のコンストラクタが,private
宣言されているために,たとえ上位クラスのコンストラクタ呼出し(8.8.5)であっても,クラスPoint
の外部からアクセスできないことにある。
クラスPoint
のメソッドreset
が private
宣言されているので,クラスColoredPoint
で継承されないために,さらに二つのエラーが発生する。つまり,クラスColoredPoint
のメソッドclear
及びクラスTest
のメソッドmain
内でのreset
のメソッド呼出しは,正しくない。points
が,二つのコンパイル単位として宣言されている例を考える。
及びpackage points; public class Point { int x, y; public void move(int dx, int dy) { x += dx; y += dy; } }
さらに,他のパッケージ内の3番目のコンパイル単位を考える。package points; public class Point3d extends Point { int z; public void move(int dx, int dy, int dz) { x += dx; y += dy; z += dz; } }
パッケージimport points.Point3d; class Point4d extends Point3d { int w; public void move(int dx, int dy, int dz, int dw) { x += dx; y += dy; z += dz; w += dw; // compile-time errors } }
points
の,二つのクラスのコンパイルは成功する。クラスPoint3d
は,クラスPoint
と同じパッケージ内にあるので,クラスPoint
のフィールドx
及びy
を継承する。クラスPoint4d
は,異なるパッケージ内にあるので,クラスPoint
のフィールドx
及びy
,並びにクラスPoint3d
のフィールドz
を継承せず,コンパイルは,失敗する。import points.Point3d; class Point4d extends Point3d { int w; public void move(int dx, int dy, int dz, int dw) { super.move(dx, dy, dz); w += dw; } }
dx
,dy
及び dz
を処理するために,上位クラスPoint3d
のメソッドmove
を使用している。クラスPoint4d
をこのように記述すれば,エラーなしでコンパイルできる。Point
を,次のとおりとする。
package points; public class Point { public int x, y; protected int useCount = 0; static protected int totalUseCount = 0; public void move(int dx, int dy) { x += dx; y += dy; useCount++; totalUseCount++; } }
public
及びprotected
フィールドであるx
, y
,
useCount
及びtotalUseCount
は,Point
のすべての下位クラスで継承される。したがって,他のパッケージ内の次のプログラムは,正常にコンパイルできる。
class Test extends points.Point { public void moveBack(int dx, int dy) { x -= dx; y -= dy; useCount++; totalUseCount++; } }
クラス変数class Point { int x, y; void move(int dx, int dy) { x += dx; y += dy; totalMoves++; } private static int totalMoves; void printMoves() { System.out.println(totalMoves); } } class Point3d extends Point { int z; void move(int dx, int dy, int dz) { super.move(dx, dy); z += dz; totalMoves++; } }
totalMoves
は,クラスPoint
内でだけ使用できる。つまり,この変数は,下位クラスPoint3d
では継承されない。クラスPoint3d
の メソッドmove
が,変数totalMoves
の増分を試みる箇所で,コンパイル時エラーが発生する。public
宣言されていなくても,そのクラスがpublic
宣言された上位クラス又は上位インタフェースをもっていれば,そのクラスが宣言されているパッケージの外のコードに対して,実行時にそのクラスのインスタンスが使用可能となることがある。 そのクラスのインスタンスは,型がpublic
である変数に代入できる。 その変数で参照されるオブジェクトのpublic
なメソッド呼出しが,そのクラスがpublic
な上位クラス又は上位インタフェースのメソッドを実装又は上書きしていれば,public
と宣言されていないクラスのメソッドを呼び出せる。 (この場合,そのメソッドがpublic
ではないクラスの中で宣言されていても,そのメソッド自体は,必ずpublic
宣言されているものとする。)次に,他のパッケージの他のコンパイル単位を考える。package points; public class Point { public int x, y; public void move(int dx, int dy) { x += dx; y += dy; } }
さらに,第3のパッケージ内のpackage morePoints; class Point3d extends points.Point { public int z; public void move(int dx, int dy, int dz) { super.move(dx, dy); z += dz; } public void move(int dx, int dy) { move(dx, dy, 0); } } public class OnePoint { public static points.Point getOne() { return new Point3d(); } }
morePoints.OnePoint.getOne()
の呼出しは,型Point3d
が,パッケージmorePoints
の外では使用できないにもかかわらず,Point
として使用可能なPoint3d
を返す。このとき,メソッドmove
をそのオブジェクトに対して呼び出すことができる。この処理は,Point3d
のメソッドmove
がpublic
なので許される。(この条件は,public
なメソッドを上書きするメソッドは,それ自体が public
でなければならないために必須とする。この条件が満たされていなければ,正しく処理されない。)オブジェクトのフィールドx
及びy
は,第3のパッケージからもアクセス可能とする。
クラスPoint3d
のフィールドz
は, public
なのだが,型Point
の変数p
でクラスPoint3d
のインスタンスへの参照だけが与えられているために,パッケージmorePoints
の外のコードからは,このフィールドにアクセスできない。p
は,型Point
をもつこと及びクラスPoint
が名前z
のフィールドをもたないこと,のために,式p.z
は正しくない。クラス型Point3d
は,パッケージmorePoints
の外からは参照できないので,式((Point3d)p).z
も正しくない。
しかし,フィールドz
のpublic
宣言が無意味なわけではない。パッケージmorePoints
にクラスPoint3d
のpublic
な下位クラスPoint4d
が存在すれば,クラスPoint4d
は,フィールドz
を継承する。
フィールドpackage morePoints; public class Point4d extends Point3d { public int w; public void move(int dx, int dy, int dz, int dw) { super.move(dx, dy, dz); w += dw; } }
z
は,public
なので,public
な型Point4d
の変数又は式を介して,morePoints
以外のパッケージ内のコードからもアクセス可能となる。
FieldDeclaration: FieldModifiersopt Type VariableDeclarators ; VariableDeclarators: VariableDeclarator VariableDeclarators , VariableDeclarator VariableDeclarator: VariableDeclaratorId VariableDeclaratorId = VariableInitializer VariableDeclaratorId: Identifier VariableDeclaratorId [ ] VariableInitializer: Expression ArrayInitializerFieldModifiersは,8.3.1で規定する。 FieldDeclarator内の Identifierは,そのフィールドを参照する名前の中で使用してよい。フィールドは,メンバとする。 フィールド宣言の有効範囲(6.3)は,8.1.5で規定する。 複数の宣言子を記述することによって,一つのフィールド宣言の中に,複数のフィールドを宣言してよい。 FieldModifiers及びTypeは,宣言内のすべての宣言子に適用される。 配列型を含む変数宣言は,10.2で規定する。
一つのクラス宣言の本体に,同じ名前をもつ二つのフィールドの宣言が含まれる場合は,コンパイル時エラーとする。 メソッド,型及びフィールドは,同じ名前をもってよい。 その理由は,それらは,異なった文脈で使用されること及び異なる検索手順によってあいまいさが解消されるからとする(6.5)。
クラスがある特定の名前をもつフィールドを宣言した場合,そのクラスの上位クラス及び上位インタフェースの中の,それと同じ名前をもつありとあらゆるアクセス可能なフィールドの宣言を隠ぺい(hide)すると呼ぶ。フィールド宣言は,また,取り囲むクラス又はインタフェースの任意のアクセス可能フィールド,ならびに,取り囲むブロックの同じ名前をもつ局所変数,形式メソッド仮引数及び例外ハンドラ仮引数の宣言をおおい隠す(6.3.1)。
あるフィールド宣言が他のフィールド宣言を隠ぺいする場合,その二つのフィールドが同じ型をもつ必要はない。
クラスは,その直接的上位クラス及び直接的上位インタフェースから,そのクラス内のコードでアクセス可能であり,そのクラス内の宣言で隠ぺいされていない,上位クラス及び上位インタフェースの中のすべての非privateフィールドを継承する。
上位クラスのprivateフィールドは下位クラスからアクセス可能なことに注意せよ。 (例えば,両方のクラスが同じクラスのメンバである場合。) それにもかかわらず,privateフィールドは,決して下位クラスに継承されない。
クラスは,同じ名前をもつ複数のフィールドを継承できる(8.3.3.3)。 これだけでは,コンパイル時エラーにはならない。 しかしながら,クラス本体内で,それらのフィールドを単純名で参照すると,それらの参照は,あいまいであるために,コンパイル時エラーが発生する。
一つのインタフェースから,同じフィールドの宣言を継承する経路が,複数あってもよい。 この場合は,フィールドは,一度だけ継承されると考えられ,あいまいさを生じることなく単純名によって参照可能とする。
隠ぺいされたフィールドは,限定名(static
ならば),又はキーワードsuper
若しくは上位クラス型へのキャストを含むフィールドアクセス式(15.11)を使ってアクセス可能とする。 この詳細及び例については,15.11.2を参照のこと。
型float
のフィールドに貯えられた値は,常に単精度数値集合(4.2.3)の要素とする。同様に,型double
のフィールドに貯えられた値は,常に倍精度数値集合の要素とする。型 float
のフィールドが,単精度数値集合の要素でない単精度指数部拡張数値集合の要素を含むことは許さないし,型double
のフィールドが,倍精度数値集合の要素でない倍精度指数部拡張数値集合の要素を含むことも許さない。
FieldModifiers: FieldModifier FieldModifiers FieldModifier FieldModifier: one of public protected private static final transient volatileアクセス修飾子
public
,
protected
及びprivate
は,6.6で規定する。 フィールド宣言に同じ修飾子が2回以上出現した場合,又はフィールド宣言がアクセス修飾子public
, protected
及びprivate
を2個以上含んでいる場合,コンパイル時エラーが発生する。フィールド宣言内で,複数個の(異なる)フィールド修飾子が出現する場合,必須ではないが,習慣上,前述のFieldModifierの生成規則内の記述の順序と矛盾しないように出現するものとする。
static
フィールドstatic
宣言した場合,そのクラスのインスタンスがいくつ生成されても(まったく生成されなくてもよい),そのフィールドの実体は,一つだけ存在する。 static
フィールドは,クラス変数(class variable)とも呼ばれ,クラスが初期化(12.4)されるときに,実体が生成される。
static
宣言されていないフィールド(非static
フィールドとも呼ぶ)は,インスタンス変数(instance variable) と呼ばれる。クラスのインスタンスが生成されるときは必ず,そのインスタンスに関連する新しい変数が,そのクラス又はそのクラスの上位クラスで宣言されたすべてのインスタンス変数に対して生成される。次のプログラム例は,
出力を,次のとおり表示する。class Point { int x, y, useCount; Point(int x, int y) { this.x = x; this.y = y; } final static Point origin = new Point(0, 0); } class Test { public static void main(String[] args) { Point p = new Point(1,1); Point q = new Point(2,2); p.x = 3; p.y = 3; p.useCount++; p.origin.useCount++; System.out.println("(" + q.x + "," + q.y + ")"); System.out.println(q.useCount); System.out.println(q.origin == Point.origin); System.out.println(q.origin.useCount); } }
これは,(2,2) 0 true 1
p
のフィールドx
, y
及びuseCount
の変更が,q
のフィールドには,影響しないことを示している。 その理由は,これらのフィールドは,異なるオブジェクト内のインスタンス変数だからである。 この例では,クラスPoint
のクラス変数 origin
を,Point.origin
のように限定名としてクラス名を使用する方法,並びにp.origin
及びq.origin
のようにフィールドアクセス式(15.11)内のクラス型の変数を使用する方法,の両方を用いて参照している。 クラス変数 origin
へのアクセスの,これらの二つの方法は,同じオブジェクトにアクセスすることが,次に示す参照型等価演算式(15.21.3)の値がtrue
であることにより証明されている。
また,次の増分式からも証明される。q.origin==Point.origin
この結果,p.origin.useCount++;
q.origin.useCount
の値は,1
となる。その理由は,p.origin
及び q.origin
が同じ変数を参照しているからである。final
フィールドfinal
宣言(4.5.4)できる。クラス変数及びインスタンス変数(static
フィールド及び 非static
フィールド)の両方ともfinal
宣言してよい。未初期化最終 (4.5.4)クラス変数が,その宣言されたクラスで静的初期化子(8.7)により,確実に代入(16.7)されていなければ,コンパイル時エラーとする。
未初期化最終インスタンス変数は,宣言したクラスでのすべてのコンストラクタ(8.8)の終りで確実に代入(16.8)されなければならない。そうでなければ,コンパイル時エラーとなる。
transient
フィールドtransient
を付してもよい。クラスclass Point { int x, y; transient float rho, theta; }
Point
のインスタンスが,システムサービスによって永続的記憶域に保存される場合,フィールドx
及びy
だけが保存される。本言語規定では,このサービスの詳細は,規定しない。そのようなサービス例については,java.io.Serializableの規定を参照せよ。volatile
フィールドJavaは,ある種の目的に対して,より便利に利用できる第2の機構,volatileフィールドを提供する。
フィールドは,volatile
宣言できる。 この場合,スレッドは,変数にアクセスするたびに,フィールドの作業コピーをマスタコピーに一致させなければならない。 さらに,スレッドの動作による, volatile 変数のマスタコピーへの操作は,主メモリによって,スレッドの要求した順序と同じ順序で実行する。
次の例において,あるスレッドがメソッドone
を繰り返し呼び出し (ただし,呼出し回数は,全部を合わせてもInteger.MAX_VALUE
回を超えないものとする。),さらに,もう一つのスレッドが,メソッドtwo
を繰り返し呼び出したとする。
メソッドclass Test { static int i = 0, j = 0; static void one() { i++; j++; } static void two() { System.out.println("i=" + i + " j=" + j); } }
two
は,i
の値よりも大きなj
の値を出力することがある。 その理由は,この例は,同期を含んでおらず,しかも17.の規則の下では,i
及びj
の共有値が無秩序に更新されることがあるからとする。
この無秩序動作を防ぐ一つの方法は,メソッドone
及びtwo
をsynchronized
宣言(8.4.3.6)することとする。
これにより,メソッドclass Test { static int i = 0, j = 0; static synchronized void one() { i++; j++; } static synchronized void two() { System.out.println("i=" + i + " j=" + j); } }
one
及び two
が並行的に実行されるのを防ぎ,さらに,i
及びj
の共有変数が,メソッドone
が戻る前に, 両方とも更新されることを保証する。 したがって,メソッド two
がi
の値より大きな値のj
を見ることはない。 実際には,メソッド two
は,i
及びj
に対して,常に同じ値を見る。
もう一つの方法は,i
及び j
を volatile
宣言することとする。
この例では,メソッドclass Test { static volatile int i = 0, j = 0; static void one() { i++; j++; } static void two() { System.out.println("i=" + i + " j=" + j); } }
one
及び メソッドtwo
は,並行的に実行できる。 しかも,i
及び j
に対する共有変数へのアクセスは,それぞれのスレッドによるプログラムテキストの実行中に出現するものと,正確に同じ回数及び同じ順序で行われることを保証する。 したがって,メソッドtwo
がi
の値より大きな値のj
を見ることはない。 その理由は,i
に対するそれぞれの更新は,j
への更新が発生する前に,i
の共有変数に反映されなければならないからである。 しかしながら,メソッドtwo
のある呼出しが,i
の値より大きな値のj
を見ることはある。 その理由は,メソッドtwo
が,i
の値を取得する瞬間及びj
の値を取得する瞬間の間に,メソッドone
が,何回か実行されているかも知れないからである。
詳細な規定及び例については,17.を参照のこと。
final
変数を,volatile
宣言した場合,コンパイル時エラーが発生する。
static
フィールド)に対するものならば,クラスが初期化される(12.4)ときに,変数初期化子が評価され,代入が一度だけ実行される。
static
ではないフィールド)に対するものならば,クラスのインスタンスが生成される(12.5)たびに,変数初期化子が評価され,代入が実行される。このプログラムは,次の結果を出力する。class Point { int x = 1, y = 5; } class Test { public static void main(String[] args) { Point p = new Point(); System.out.println(p.x + ", " + p.y); } }
その理由は,新しい1, 5
Point
が生成されるたびに,x
及びy
への代入が発生するからである。変数初期化子は,局所変数宣言文(14.4)でも使用される。 その場合,局所変数宣言文が実行されるたびに,初期化子が評価され,代入が実行される。
名前付きクラス(又はインタフェース)のstatic
フィールド又はインスタンス変数用の変数初期化子の評価が,検査例外(11.2)によって中途完了する可能性がある場合,コンパイル時エラーが発生する。
キーワードthis
(15.8.3)又はsuper
(15.11.2, 15.12)がクラス変数に対する初期化式内に出現する場合,コンパイル時エラーが発生する。
ここで,実行時には,final
宣言されていてコンパイル時の定数値で初期化されているstatic
変数が,最初に初期化されることに注意すること。 これは,インタフェース内の同様のフィールド(9.3.1)にも適用される。 これらの変数は,たとえ悪意のあるプログラムによっても,デフォルト初期値(4.5.5)を決して観察できない"定数"とする。 詳細な規定については,12.4.2及び13.4.8を参照のこと。
宣言がソーステキスト上で使用の後に出現するクラス変数の使用は,たとえ有効範囲にあっても,制限を受ける。クラス変数への前方参照を支配する適切な規則については, 8.3.2.3を参照のこと。
static
変数の単純名を,たとえその宣言がソーステキストで後に出現する場合でも,使用してもよい。この例は,エラーなしにコンパイルできる。 クラスclass Test { float f = j; static int j = 1; }
Test
が初期化されるときに,j
を1
に初期化し,クラスTest
のインスタンスが生成されるたびに,f
をj
の現在の値に初期化する。
インスタンス変数に対する初期化式は,現在のオブジェクトを参照するためのキーワードthis
(15.8.3)及び上位クラスのオブジェクトを参照するためのキーワードsuper
(15.11.2, 15.12)を使用してよい。
宣言がソーステキスト上での使用の後に出現するインスタンス変数の使用は,たとえ有効範囲にあっても,制限を受ける。インスタンス変数への前方参照を支配する適切な規則については,8.3.2.3を参照のこと。
static
)フィールドであり,さらに次の条件すべてが成り立ちさえすれば,メンバの宣言は,使用の前に出現する必要がある。
static
)変数初期化子又はC のインスタンス(又はstatic
)初期化子において,使用が出現する。
しかし,次の例はエラーなくコンパイルできる。class Test { int i = j; // compile-time error: incorrect forward reference int j = 1; }
これは,class Test { Test() { k = 2; } int j = 1; int i = j; int k; }
Test
のコンストラクタ(8.8)が3行後で宣言されているフィールドk
を参照するのに関係しない。この制限は,コンパイル時に,循環的初期化などの不正形式の初期化を検出するように設計されている。したがって,次の二つの例は,コンパイル時エラーとなる。
及びclass Z { static int i = j + 2; static int j = 4; }
この方式は,メソッドによるアクセスをチェックしない。したがって,class Z { static { i = j + 2; } static int i, j; static { j = 4; } }
は,次の出力を出す。class Z { static int peek() { return j; } static int i = peek(); static int j = 1; } class Test { public static void main(String[] args) { System.out.println(Z.i); } }
理由は,0
i
の変数初期化子がクラスメソッドpeek
を使って,変数j
が,未だデフォルト値(4.5.5)を保持している,その変数初期化子により初期化されるより前に,変数j
の値にアクセスするからとする。class UseBeforeDeclaration { static { x = 100; // ok - assignment int y = x + 1; // error - read before declaration int v = x = 3; // ok - x at left hand side of assignment int z = UseBeforeDeclaration.x * 2; // ok - not accessed via simple name Object o = new Object(){ void foo(){x++;} // ok - occurs in a different class {x++;} // ok - occurs in a different class }; } { j = 200; // ok - assignment j = j + 1; // error - right hand side reads before declaration int k = j = j + 1; int n = j = 300; // ok - j at left hand side of assignment int h = j++; // error - read before declaration int l = this.j * 3; // ok - not accessed via simple name Object o = new Object(){ void foo(){j++;} // ok - occurs in a different class { j = j + 1;} // ok - occurs in a different class }; } int w = x= 3; // ok - x at left hand side of assignment int p = x; // ok - instance initializers may access static fields static int u = (new Object(){int bar(){return x;}}).bar(); // ok - occurs in a different class static int x; int m = j = 4; // ok - j at left hand side of assignment int o = (new Object(){int bar(){return j;}}).bar(); // ok - occurs in a different class int j; }
この例は,次の結果を出力する。class Point { static int x = 2; } class Test extends Point { static double x = 4.7; public static void main(String[] args) { new Test().printX(); } void printX() { System.out.println(x + " " + super.x); } }
その理由は,クラス4.7 2
Test
内のx
の宣言は,クラスPoint
内のx
の定義を隠ぺいし,クラスTest
は、その上位クラスPoint
からフィールドx
を継承しないためである。 クラスTest
の宣言内では,単純名x
は,クラスTest
内で宣言されたフィールドを参照する。 クラスTest
のコードは,クラスPoint
のフィールドx
をsuper.x
として(又は,x
は,static
なので,Point.x
として)参照してよい。Test.x
の宣言を削除したときは,次のとおりとする。
クラスclass Point { static int x = 2; } class Test extends Point { public static void main(String[] args) { new Test().printX(); } void printX() { System.out.println(x + " " + super.x); } }
Point
のフィールドx
は,もはやクラスTest
内で隠ぺいされない。 代わりに,単純名x
は,フィールドPoint.x
を参照する。 クラスTest
内のコードは,依然として,super.x
としてその同じフィールドを参照できる。 したがって,変更後のプログラムの出力は,次のとおりとなる。
2 2
この例の出力は,次のとおりとなる。class Point { int x = 2; } class Test extends Point { double x = 4.7; void printBoth() { System.out.println(x + " " + super.x); } public static void main(String[] args) { Test sample = new Test(); sample.printBoth(); System.out.println(sample.x + " " + ((Point)sample).x); } }
その理由は,クラス4.7 2 4.7 2
Test
内のx
の宣言は,クラスPoint
内のx
の定義を隠ぺいし,クラスTest
は,その上位クラスPoint
からフィールドx
を継承しないためである。 しかしながら,クラスPoint
のフィールド x は,クラスTest
によって継承(inherited)されていないが,それにもかかわらず,クラス Test
のインスタンスで実装(implemented)されていることに注意しなければならない。 つまり,クラスTest
のすべてのインスタンスは,二つのフィールド,型int
及び 型float
を含んでいる。 両方のフィールドは,名前x
をもつが,クラスTest
の宣言内では,単純名x
は,常に クラス Test
内で宣言されたフィールドを参照する。クラスTest
のインスタンスメソッド内のコードは,クラスPoint
のインスタンス変数x
を,super.x
として参照してよい。
フィールドx
にアクセスするためにフィールドアクセス式を使用するコードは,その参照式の型によって示されるクラス内の名前x
のフィールドにアクセスする。 したがって,式 sample.x
は, float
値,つまりクラスTest
内で宣言されたインスタンス変数 にアクセスする。その理由は,変数sampleの型は,Test
だからである。 しかし,式((Point)sample).x
は,型Point
にキャストしているために,int
値,つまりクラスPoint
で宣言されたインスタンス変数にアクセスする。
x
の宣言をクラスTest
から削除すれば,プログラムは,次のとおりとなる。
クラスclass Point { static int x = 2; } class Test extends Point { void printBoth() { System.out.println(x + " " + super.x); } public static void main(String[] args) { Test sample = new Test(); sample.printBoth(); System.out.println(sample.x + " " + ((Point)sample).x); } }
Point
のフィールドx
は,もはやクラスTest
内で隠ぺいされない。 クラス Test の宣言内のインスタンスメソッド内では,今は単純名x
がクラスPoint
内で宣言されたフィールドを 参照する。クラスTest
内のコードは,依然として同じフィールドをsuper.x
として参照してよい。式 sample.x
は,型Test
内のフィールドx
を引き続き参照するが,そのフィールドは,継承フィールドとなり,クラスPoint
で宣言されたフィールドx
を参照する。変更後のプログラムの出力は,次のとおりとなる。
2 2 2 2
super
(15.11.2)を含むフィールドアクセス式を使用してよい。次に例を示す。
クラスinterface Frob { float v = 2.0f; } class SuperTest { int v = 3; } class Test extends SuperTest implements Frob { public static void main(String[] args) { new Test().printV(); } void printV() { System.out.println(v); } }
Test
は,名前v
の二つのフィールドを,上位クラスSuperTest
及び上位インタフェースFrob
から継承している。これ自体は,許されるが,メソッド printV
内で単純名v
を使用しているために,コンパイル時エラーが発生する。どのv
が意図しているのかを決定できないためである。
これを書き換えた次のコードは,フィールドアクセス式super.v
を使用して,クラスSuperTest
で宣言されている名前v
のフィールドを参照し,さらに,限定名Frob.v
を使用して,インタフェースFrob
で宣言されている名前v
のフィールドを参照している。
コンパイル後の出力結果は,次のとおりとなる。interface Frob { float v = 2.0f; } class SuperTest { int v = 3; } class Test extends SuperTest implements Frob { public static void main(String[] args) { new Test().printV(); } void printV() { System.out.println((super.v + Frob.v)/2); } }
たとえ二つの異なる継承されたフィールドが,同じ型及び同じ値をもち,両方とも2.5
final
宣言されていたとしても,単純名による どちらか一方への参照は,あいまいと見なされ,コンパイル時エラーが発生する。次に例を示す。
クラスinterface Color { int RED=0, GREEN=1, BLUE=2; } interface TrafficLight { int RED=0, YELLOW=1, GREEN=2; } class Test implements Color, TrafficLight { public static void main(String[] args) { System.out.println(GREEN); // compile-time error System.out.println(RED); // compile-time error } }
Test
は,異なる値をもつGREEN
に対する二つの異なる宣言を継承しているので,GREEN
への参照があいまいと見なされる。これは,驚くことではない。この例で大切なのは,RED
への参照も,二つの異なる宣言が継承されているために,あいまいと見なされることである。名前RED
の二つのフィールドが,偶然に同じ型及び同じ不変値をもっている事実は,この判断には影響しない。
フィールドpublic interface Colorable { int RED = 0xff0000, GREEN = 0x00ff00, BLUE = 0x0000ff; } public interface Paintable extends Colorable { int MATTE = 0, GLOSSY = 1; } class Point { int x, y; } class ColoredPoint extends Point implements Colorable { . . . } class PaintedPoint extends ColoredPoint implements Paintable { . . .RED
. . . }
RED
,
GREEN
及びBLUE
は,直接的上位クラスColoredPoint
及び直接的上位インタフェースPaintable
の両方を介して,クラスPaintedPoint
によって継承されている。それにもかかわらず,インタフェースColorable
の中で宣言されたフィールドを参照するために,単純名RED
,
GREEN
及びBLUE
をクラスPaintedPoint
内であいまい性なく使用してよい。
MethodDeclaration: MethodHeader MethodBody MethodHeader: MethodModifiersopt ResultType MethodDeclarator Throwsopt ResultType: Type void MethodDeclarator: Identifer ( FormalParameterListopt )MethodModifiersは,8.4.3,Throws節は,8.4.4,MethodBodyは,8.4.5で,それぞれ規定する。メソッド宣言は,メソッドが返す値の型を指定するか,又はメソッドが値を返さないことを示すためにキーワード
void
を使用する。MethodDeclarator 内のIdentifier は,そのメソッドを参照する名前として使用する。クラスは,そのクラス又はそのクラスのフ ィールド,メンバクラス,又はメンバインタフェースと同じ名前のメソッドを宣言できる。
Javaプラットフォームの旧版との互換性のために,配列を返すメソッドの宣言形式では,仮引数並びの後ろに,配列型の宣言を構成する空の角括弧対をおいてもよい。これは,次の過去の処理系の構文規則に対応する。
MethodDeclarator: MethodDeclarator [ ]しかし,新しいJavaコードでは,使用してはならない。
クラスの本体が同じシグネチャ(8.4.2)(名前,仮引数の個数及びすべての仮引数の型)をもつ二つのメソッドを,メンバとして 定義することは,コンパイル時エラーとする。メソッド及びフィールドは,異なった文脈で使用されること及び異なる検索手続きによってあいまい性が解消される(6.5)ので,同じ名前をもってよい。
FormalParameterList: FormalParameter FormalParameterList , FormalParameter FormalParameter: finalopt Type VariableDeclaratorId次は,8.3に存在する生成規則だが,明確化のためにここに再度記述する。
VariableDeclaratorId: Identifier VariableDeclaratorId [ ]メソッド又はコンストラクタが仮引数をもたなければ,メソッド又はコンストラクタの宣言には,空の丸括弧の対だけが出現する。
同じメソッド又はコンストラクタの二つの形式仮引数が同じ名前をもつように宣言された(つまり,それらの宣言に同じIdentifierを指定した)場合,コンパイル時エラーが発生する。
finalと宣言されたメソッド又はコンストラクタ仮引数がメソッド又はコンストラクタの本体内部で代入されると,コンパイル時エラーが発生する。
メソッド又はコンストラクタが呼び出された(15.12)とき,メソッド又はコンストラクタ本体の実行前に,宣言されたTypeで新しく生成された仮引数変数を,実引数の式の値で初期化する。DeclaratorIdに出現するIdentifierは,その形式仮引数を参照するために,メソッド又はコンストラクタ本体内で,単純名として使用してよい。
メソッド(8.4.1)又はコンストラクタ(8.8.1)の形式仮引数名の有効範囲は,メソッド又はコンストラクタの本体全体とする。
これらの仮引数名は,メソッドの局所変数,もしくは,メソッド 又はコンストラクタのtry文内のcatch節の例外仮引数として再宣言してはならない。しかし,メソッド又はコンストラクタの仮引数は,そのメソッド又はコンストラクタ内部の入れ子になったクラス宣言内でおおい隠される。このような入れ子クラス宣言では,局所クラス(14.3)又は匿名クラス(15.9)を宣言してもよい。
形式仮引数は,単純名でだけ参照され,限定名(6.6)で参照されることは決してない。
型float
のメソッド又はコンストラクタ仮引数は,常に単精度数値集合(4.2.3)の要素を含む。同様に,型double
のメソッド又はコンストラクタ仮引数は,常に倍精度数値集合の要素を含む。型float
のメソッド又はコンストラクタ仮引数が,単精度数値集合の要素でない単精度指数部拡張数値集合の要素を含むことは許さないし,型double
のメソッド仮引数が,倍精度数値集合の要素でない倍精度指数部拡張数値集合の要素を含むことも許さない。
仮引数変数に対応する実引数式がFP厳密 (15.4)でない時は,その実引数式の評価において適当な拡張指数値集合から引き出された中間値の使用を許す。仮引数変数に貯えられる前に,そのような式の結果は,メソッド呼出し変換(5.3)により,対応する標準値集合における一番近い値に対応付けられる。
同じシグネチャをもつ二つのメソッドclass Point implements Move { int x, y; abstract void move(int dx, int dy); void move(int dx, int dy) { x += dx; y += dy; } }
move
を宣言しているので,コンパイル時エラーが発生する。たとえ,宣言の一つがabstract
であってもエラーとする。MethodModifiers: MethodModifier MethodModifiers MethodModifier MethodModifier: one of public protected private abstract static final synchronized native strictfpアクセス修飾子
public
,
protected
及びprivate
は,6.6で規定する。 メソッド宣言内で,同じ修飾子が複数回出現した場合,又はメソッド宣言がアクセス修飾子public
, protected
及びprivate
の二つ以上をもつ場合,コンパイル時エラーが発生する。キーワードabstract
を含むメソッド宣言が,キーワードprivate
, static
, final
,
native
, strictfp
又はsynchronized
のいずれかを含む場合,コンパイル時エラーが発生する。キーワードnative
を含むメソッド宣言がstrictfp
も含むなら,コンパイル時エラーが発生する。メソッド宣言で複数のメソッド修飾子を指定するときは,習慣上,必須ではないが,MethodModifier に関する,前述の生成規則内の順序と矛盾しない順序で指定する。
abstract
メソッドabstract
メソッドの宣言は,メソッドをメンバとして導入し,そのシグネチャ(名前並びに仮引数の個数及び型),返却値の型及び(もしあれば)throws
節を提供するが,実装は,提供しない。abstract
メソッド m の宣言は,abstract
クラス(このクラスを A とする)の直接内部に出現しなければならない。 そうでなければ,コンパイル時エラーが発生する。abstract
宣言されていない A のすべての下位クラスは,m に対する実装を提供しなければならない。 そうでなければ,8.1.1.1で規定するようにコンパイル時エラーが発生する。
private
メソッドをabstract
宣言することは,コンパイル時エラーとする。
private
メソッドは,下位クラスから参照できないので,下位クラスは,private
abstract
宣言されたメソッドを実装できない。したがって,このようなメソッドは,決して使用できない。
static
メソッドをabstract
宣言することは,コンパイル時エラーとする。
final
メソッドをabstract
宣言することは,コンパイル時エラーとする。
abstract
クラスは,他のabstract
メソッドの宣言を提供することによって,abstract
メソッドを上書きできる。
これによって,文書化用注釈を記述する場所,又は下位クラスで実装されるときに,そのメソッドによって投げられる可能性がある検査例外(11.2)の集合を宣言する場所をより限定することができる。例えば,次のコードを考える。
クラスclass BufferEmpty extends Exception { BufferEmpty() { super(); } BufferEmpty(String s) { super(s); } } class BufferError extends Exception { BufferError() { super(); } BufferError(String s) { super(s); } } public interface Buffer { char get() throws BufferEmpty, BufferError; } public abstract class InfiniteBuffer implements Buffer { abstract char get() throws BufferError; }
InfiniteBuffer
内のメソッドget
の上書き宣言は,InfiniteBuffer
のすべての下位クラス内のメソッドget
が,決して例外BufferEmpty
を投げないことを述べている。理由は,そのメソッドget
が,そのバッファ内にデータを生成しているため ,データがなくならないと仮定する。
abstract
ではないインスタンスメソッドを,abstract
メソッドで上書きすることができる。
例えば,それらの下位クラスが,完全化され,インスタンス化可能なクラスであれば,その下位クラスがtoString
の実装を要求しているabstract
クラスPoint
を宣言できる。
このabstract class Point { int x, y; public abstract String toString(); }
toString
のabstract
宣言は,クラスObject
のabstract
宣言されていないメソッドtoString
上書きしている。(クラスObject
は,クラスPoint
の暗黙の直接的上位クラスとする。)さらに,次のコードを追加する。
このコードは,コンパイル時エラーとなる。 その理由は,class ColoredPoint extends Point { int color; public String toString() { return super.toString() + ": color " + color; // error } }
super.toString()
の呼出しは,クラスPoint
内のメソッドtoString
を参照しているが,このクラスは,abstract宣言されており呼び出せないからである。クラスObject
のメソッドtoString
は,次のコードのように,クラスPoint
が,そのメソッドを,明示的に他のメソッドを介して使用可能にしている場合に限り,クラスColoredPoint
で使用可能にすることができる。
abstract class Point { int x, y; public abstract String toString(); protected String objString() { return super.toString(); } } class ColoredPoint extends Point { int color; public String toString() { return objString() + ": color " + color; // correct } }
static
メソッドstatic
宣言されたメソッドを,クラスメソッド(class method)と呼ぶ。 クラスメソッドは,常に特定のオブジェクトへの参照なしで呼び出される。 クラスメソッドの本体で,キーワードthis
又は キーワードsuper
を使用して現在のオブジェクトを参照しようとすると,コンパイル時エラーが発生する。static
メソッドをabstract
宣言することは,コンパイル時エラーとする。
static
宣言されていないメソッドを,インスタンスメソッド(instance method)と呼ぶ。これは,非static
メソッドと呼ぶこともある。インスタンスメソッドは,常にあるオブジェクトに関して呼び出され,そのオブジェクトが,メソッド本体の実行中にキーワードthis
及びキーワードsuper
で参照される,現在のオブジェクトとなる。
final
メソッドfinal
宣言することができる。final
メソッドを上書き又は隠ぺいしようとすることは,コンパイル時エラーとする。
private
メソッド及びfinal
クラス(8.1.1.2)の中で宣言されたすべ てのメソッドは,上書きできないため,暗黙的にfinal
とする。 それらのメソッド宣言に,冗長なキーワードfinal
を含めることは,許されているが,必須ではない。
final
メソッドを,abstract
宣言することは,コンパイル時エラーとする。
実行時に,機械語生成器又は最適化器は,容易及び安全にfinal
メソッドの本体を"インライン化"できる。つまり,メソッド呼出しを,その本体内のコードで置き換えることができる。インライン化プロセスは,メソッド呼出しの意味論を保存せねばならない。特に,インスタンスメソッド呼出しの目標がnull
であるなら,たとえメソッドがインライン化されても,NullPointerException
を投げねばならない。コンパイラは,メソッドへの実引数はメソッド呼出しより前に正しい順序で評価されるように正しい時点で例外を投げるよう保証せねばならない。
ここで,メソッドfinal class Point { int x, y; void move(int dx, int dy) { x += dx; y += dy; } } class Test { public static void main(String[] args) { Point[] p = new Point[100]; for (int i = 0; i < p.length; i++) { p[i] = new Point(); p[i].move(i, p.length-1-i); } } }
main
内にクラスPoint
のメソッドmove
をインライン化するとは,for
ループを次の形式に変換することとする。
このループは,さらに最適化されるかもしれない。for (int i = 0; i < p.length; i++) { p[i] = new Point(); Point pi = p[i]; int j = p.length-1-i; pi.x += i; pi.y += j; }
これらのインライン化は,Test
及びPoint
が,常に一緒にコンパイルされる保証がないならば,コンパイル時に行うことはできない。 これは,Point
,特にそのメソッドmove
,が変更されるたびに,Test.main
のコードも更新されるようにするためである。
native
メソッドnative
メソッドは,プラットフォーム依存のコードで実装されるものとする。 典型的には,C,C++,FORTRAN,又はアセンブリ言語などの他のプログラミング言語で記述される。native
メソッドの本体は,実装を省略することを示すために,ブロックの代わりにセミコロンだけを与える。
native
メソッドをabstract
宣言すると,コンパイル時エラーが発生する。
例えば,標準パッケージjava.io
のクラスRandomAccessFile
は,次のnative
メソッドを宣言していてよい。
package java.io; public class RandomAccessFile implements DataOutput, DataInput { . . . public native void open(String name, boolean writeable) throws IOException; public native int readBytes(byte[] b, int off, int len) throws IOException; public native void writeBytes(byte[] b, int off, int len) throws IOException; public native long getFilePointer() throws IOException; public native void seek(long pos) throws IOException; public native long length() throws IOException; public native void close() throws IOException; }
strictfp
メソッドstrictfp
修飾子の効果は,メソッド本体内部のfloat
又は double
式のすべてを明示的にFP厳密 (15.4)とすることにある。synchronized
メソッドsynchronized
メソッドは,実行前にロック(17.1)を取得する。クラス(static
)メソッドに対しては,そのメソッドのクラスに対 するClass
オブジェクトと関連したロックを使用する。インスタンスメソッドに対しては,this
(そのメソッドが呼び出されたオブジェクト)と関連したロックを使用する。
これらのロックは,synchronized
文(14.18)で使用できるものと 同じロックとする。したがって,次のコードを考える。
これは,次のコードと同じ効果をもつ。class Test { int count; synchronized void bump() { count++; } static int classCount; static synchronized void classBump() { classCount++; } }
さらに詳細な例を示す。class BumpTest { int count; void bump() { synchronized (this) { count++; } } static int classCount; static void classBump() { try { synchronized (Class.forName("BumpTest")) { classCount++; } } catch (ClassNotFoundException e) { ... } } }
この例は,並行動作の使用のために設計されたクラスを定義している。クラスpublic class Box { private Object boxContents; public synchronized Object get() { Object contents = boxContents; boxContents = null; return contents; } public synchronized boolean put(Object contents) { if (boxContents != null) return false; boxContents = contents; return true; } }
Box
の各インスタンスは,任意のオブジェクトへの 参照を保持できるインスタンス変数contents
をもつ。put
を呼び出すことによって,Box
にオブジェクトを入れる。Box
が既に満杯ならば,put
は,false
を返す。get
を呼び出すことによって,Box
の内容を取り出すことができる。box
が空ならば,get
は,空参照を返す。
put
及びget
が,synchronized
宣言されていないとき,二つのスレッドが同時にBox
の同じインスタンスに対するメソッドを実行すれば,そのコードは,不当な動作を行う可能性がある。たとえば,put
に対する二つの呼出しが同時に発生したために,オブジェクトの軌跡を失うかもしれない。
スレッド及びロックの詳細な規定については,17.を参照のこと。
Throws: throws ClassTypeList ClassTypeList: ClassType ClassTypeList , ClassTypethrows 節で指定したClassTypeが,クラス
Throwable
又はその下位クラスでなければ,コンパイル時エラーが発生する。throws
節内で他の例外(非検査例外)も指定してよいが,必須ではない。
メソッド又はコンストラクタ本体の実行の結果生じる可能性のある検査例外に対して,その例外の型又はその例外の型の上位クラスが,メソッド又はコンストラクタの宣言内のthrows
節に指定されていなければ,コンパイル時エラーが発生する。
検査例外の宣言は,必須なので,そのようなエラー条件を処理するコードが含まれているかどうかをコンパイラで確認できる。 検査例外として投げられる例外条件の処理に失敗するメソッド又はコンストラクタは,throws
節に適当な例外型を指定していないことによって,通常は,コンパイル時エラーを生じる。 Javaでは,このように,あるプログラミングスタイルを奨励している,そこでは,発生頻度は少ないが本当に例外的な条件は,この方法によって文書化されている。
この方法で検査されない定義済みの例外とは,可能な発生をすべて宣言することが不便で現実的でないものとする。
Error
の下位クラスによって表現されている例外,例えばOutOfMemoryError
は,仮想計算機に関係する失敗によって投げられることがある。 これらのエラーの多くは,リンク失敗の結果で,Javaプログラム実行中の予想できない点で発生する可能性がある。 ただし,洗練されたプログラムは,これらの条件の一部を検出し回復を試みるかもしれない。
RuntimeException
の下位クラスで表現されている例外,例えばNullPointerException
は,実行時の保全性検査の結果生じ,直接Javaプログラムから又はライブラリルーチンの中で投げられる。このような例外が発生しないと証明できる箇所を,管理可能な数にまで減らすために,プログラム内に十分な情報を取り込むことは,Java言語及びおそらくは,現在の最新の技術の範囲を超えている。abstract
メソッドを実装するメソッドを含み,他のメソッドを上書き又は隠ぺいするメソッド(8.4.6)は,上書き又は隠ぺいされたメソッドよりも多くの検査例外を投げるように宣言してはならない。
より正確に規定する。Bをクラス又はインタフェース,AをBの上位クラス又はインタフェースとし,Bの中のメソッド宣言nが,Aの中のメソッド宣言mを上書き又は隠ぺいするとする。nが検査例外の型を指定するthrows
節をもつ場合,mは,throws
節をもたなければならず,nのthrows
節に上げられているすべての検査例外の型に対して,同じ例外クラス又はその上位クラスの一つがmのthrows
節内に出現しなければならない。そうでなければ,コンパイル時エラーが発生する。
例外に関するより多くの情報及び豊富な例については,11.を参照のこと。
abstract
(8.4.3.1)又はnative
(8.4.3.4)とする。
MethodBody: Block ;メソッド宣言が,
abstract
又はnative
であり,その本体としてブロックをもつならば,コンパイル時エラーが発生する。 メソッド宣言が,abstract
でもnative
でもなく,その本体としてセミコロンをもつならば,コンパイル時エラーが発生する。
実装は,メソッドに対して提供されるべきだが,その実装の中に実行可能コードを必要としなければ,メソッド本体は,文を含まないブロック,つまり,"{ }
"として記述しなければならない。
メソッドがvoid
宣言されていれば,その本体は,式(Expression)をもつreturn
文(14.16)を含んではならない。
メソッドが返却値型をもつと宣言されていれば,その本体内のすべてのreturn
文(14.16)は,式(Expression)をもたなければならない。 メソッド本体が正常完了(14.1)できるならば,コンパイル時エラーが発生する。
つまり,返却値型をもつメソッドは,返却値を返すreturn文を使ってだけ制御を戻さねばならない。 "末尾返却"(return文を省略して,すべての文の正常完了によってメソッドの終わりとすること)は,許されない。
メソッドが宣言された返却値の型をもつがreturn文を含まないことがあることに注意すること。次に例を示す。
class DizzyDean { int pitch() { throw new RuntimeException("90 mph?!"); } }
abstract
であってもなくても)継承(inherits)する。abstract
メソッドのあらゆる宣言を実装する(implement)と言う。
インスタンスメソッドがstatic
メソッドを上書きすると,コンパイル時エラーが発生する。
この点で,メソッドの上書きは,フィールドの隠ぺい(8.3)とは異なる。その理由は,インスタンス変数が,static
変数を隠ぺいすることは,許されることによる。
上書きされたメソッドは,キーワードsuper
を含むメソッド呼出し式(15.12)を使用して,アクセス可能とする。 限定名又は上位クラスへのキャストで,上書きされたメソッドにアクセスしようとすることは無効とすることに注意せよ。 この点で,メソッドの上書きは,フィールドの隠ぺいとは異なる。この点の規定及び例については,15.12.4.9を参照のこと。
strictfp
修飾子の存在又は欠如は,メソッドの上書き及びabstract メソッドの実装の規則に何らの影響も絶対もたない。例えば, FP厳密でないメソッドがFP厳密メソッドを上書きすること,ならびに,FP厳密でないメソッドを上書きすることは,許される。
static
メソッドを宣言している場合,このメソッド宣言は,そのクラスの上位クラス及び上位インタフェース内で同じシグネチャをもつあらゆるメソッドを隠ぺい(hide)する。 隠ぺいされなければ,その上位クラス及び上位インタフェースのメソッドは,クラス内でアクセス可能とする。static
メソッドがインスタンスメソッドを隠ぺいすると,コンパイル時エラーが発生する。
この点で,メソッドの隠ぺいは,フィールドの隠ぺい(8.3)とは異なる。 すなわち,static
変数が,インスタンス変数を隠ぺいすることは,許されている。隠ぺいは,おおい隠し(6.3.1)ならびに不明瞭化(6.3.2)とも異なる。
隠ぺいされたメソッドは,限定名,又はキーワードsuper
若しくは上位クラス型へのキャストを含むメソッド呼出し式(15.12)を使用して,アクセス可能とする。 この点で,メソッドの隠ぺいは,フィールドの隠ぺいと似ている。
void
ならば,コンパイル時エラーが発生する。 さらに,メソッド宣言は,上書き又は隠ぺいするメソッドのthrows
節と衝突(8.4.4)するthrows
節をもってはならない。そうでなければ,コンパイル時エラーが発生する。この点で,メソッドの上書きは,フィールド(8.3)の隠ぺいとは異なる。 すなわち,フィールドが他の型のフィールドを隠ぺいすることは,許されている。
メソッドを上書き又は隠ぺいするアクセス修飾子(6.6)は,少なくとも上書き又は隠ぺいされるメソッドと同じアクセスを提供しなければならない。そうでなければ,コンパイル時エラーが発生する。詳細は,次のとおりとする。
public
ならば,上書き又は隠ぺいするメソッドは,public
でなければならない。 そうでなければ,コンパイル時エラーが発生する。
protected
ならば,上書き又は隠ぺいするメソッドは,protected
又はpublic
でなければならない。 そうでなければ,コンパイル時エラーが発生する。
private
にしてはならない。 そうでなければ,コンパイル時エラーが発生する。
private
メソッドは,技術的な意味で,隠ぺい又は上書きされないことに注意。 これは,下位クラスでは,その上位クラスのprivate
メソッドと同じシグネチャをもつメソッドを宣言できること及びそのようなメソッドの返却値の型又は throws
節が,その上位クラス内のprivate
メソッドの返却値の型又はthrows
節になんらかの関係をもつ必要がないことを意味している。
abstract
でなければ,さらに二つの場合が存在する。
abstract
ではないメソッドがstatic
であるならば,コンパイル時エラーが発生する。
abstract
ではないメソッドが他のすべてのメソッドを上書きして,それ故,実装すると見なす。 abstract
ではないメソッドを,継承された他の各メソッドと比較し,その対に関して返却値の型が異なる,又は一方が返却値の型をもち他方がvoid
ならば,コンパイル時エラーが発生する。さらに,abstract
ではない継承されたメソッドが,他の継承されたメソッドのthrows
節と衝突(8.4.4)するthrows
節をもつならば,コンパイル時エラーが発生する。abstract
であるならば,そのクラスは,必然的にabstract
クラスとなり,すべてのabstract
メソッドを継承すると見なされる。 二つの継承されたメソッドに対して,異なる返却値の型をもっているか,又は一方は,返却値の型をもっており他方は,void
ならば,コンパイル時エラーが発生する。(この場合,throws
節の衝突は,エラーを引き起こさない。)abstract
ではないことはあり得ない。 その理由は,abstract
ではないメソッドは,直接的上位クラスからだけ継承され,上位インタフェースからは継承されないからである。インタフェースの同じメソッド宣言が複数の経路で継承される場合がある。 これで特に問題が生じることも,これが原因でコンパイル時エラーが発生することもない。
throws
節には,いかなる関係も要求されない。
たとえば,クラスが同じ名前をもつ二つのpublic
メソッドを宣言し,下位クラスがそのうちの一つを上書きする場合,その下位クラスは,他方のメソッドを継承する。この点で,Java は, C++ とは異なる。
メソッドが呼び出される(15.12)とき,呼び出されるメソッドのシグネチャを決定(15.12.2)するために,コンパイル時に,実引数の数及び実引数のコンパイル時の型が使用される。 呼び出すべきメソッドがインスタンスメソッドであれば,呼び出される実際のメソッドは,動的メソッド検索を使用して実行時に決定(15.12.4)される。
クラスclass Point { int x = 0, y = 0; void move(int dx, int dy) { x += dx; y += dy; } } class SlowPoint extends Point { int xLimit, yLimit; void move(int dx, int dy) { super.move(limit(dx, xLimit), limit(dy, yLimit)); } static int limit(int d, int limit) { return d > limit ? limit : d < -limit ? -limit : d; } }
SlowPoint
は,それ自体のメソッドmove
でクラスPoint
のメソッドmove
の宣言を上書きし,メソッドの各呼出しで点が移動できる距離を制限する。クラスSlowPoint
のインスタンスに対して メソッドmove
を呼び出すとき,たとえオブジェクトSlowPoint
への参照が型Point
の変数を通じて得られるとしても,クラスSlowPoint
内の上書きする定義が常に呼び出される。
クラスclass Point { int x = 0, y = 0; void move(int dx, int dy) { x += dx; y += dy; } int color; } class RealPoint extends Point { float x = 0.0f, y = 0.0f; void move(int dx, int dy) { move((float)dx, (float)dy); } void move(float dx, float dy) { x += dx; y += dy; } }
RealPoint
は,クラス Point
の 型int
のインスタンス変数x
及びy
の宣言をそれ自体の型float
のインスタンス変数x
及びy
で隠ぺいし,クラスPoint
のメソッドmove
をそれ自体のメソッドmove
で上書きしている。また,名前move
のメソッドを異なるシグネチャ(8.4.2)をもつ他のメソッドでオーバロードしている。
この例では,クラスRealPoint
のメンバは,クラスPoint
から継承されたインスタンス変数color
,RealPoint
で宣言された型float
のインスタンス変数x
及びy
,並びにRealPoint
で宣言された二つのメソッドmove
を含んでいる。
クラスRealPoint
のオーバロードされたメソッドmove
のうちいずれを特定のメソッド呼出しに対して選択するかは,15.12で規定されるオーバロード解決手順によってコンパイル時に決定する。
ここで,クラスclass Point { int x = 0, y = 0, color; void move(int dx, int dy) { x += dx; y += dy; } int getX() { return x; } int getY() { return y; } } class RealPoint extends Point { float x = 0.0f, y = 0.0f; void move(int dx, int dy) { move((float)dx, (float)dy); } void move(float dx, float dy) { x += dx; y += dy; } float getX() { return x; } float getY() { return y; } }
Point
は,フィールドx
及びy
の値を返す,メソッドgetX
及びメソッドgetY
を提供している。 クラスRealPoint
は,同じシグネチャをもつメソッドを宣言し,これらのメソッドを上書きする。 その結果,返却値の型が一致しなくなるため,コンパイル時に各メソッドに対して一つずつ,二つのエラーを生じる。 つまり,クラスPoint
のメソッドが型int
の値を返すのに対し,クラスRealPoint
内の上書きしようとしているメソッドは,型float
の値を返している。
クラスclass Point { int x = 0, y = 0; void move(int dx, int dy) { x += dx; y += dy; } int getX() { return x; } int getY() { return y; } int color; } class RealPoint extends Point { float x = 0.0f, y = 0.0f; void move(int dx, int dy) { move((float)dx, (float)dy); } void move(float dx, float dy) { x += dx; y += dy; } int getX() { return (int)Math.floor(x); } int getY() { return (int)Math.floor(y); } }
RealPoint
における上書きするメソッドgetX
及びメソッドgetY
は,クラスPoint
の上書きされるメソッドと同じ返却値の型をもち,このコードは,正常にコンパイルできる。このプログラム例の出力を次に示す。class Test { public static void main(String[] args) { RealPoint rp = new RealPoint(); Point p = rp; rp.move(1.71828f, 4.14159f); p.move(1, -1); show(p.x, p.y); show(rp.x, rp.y); show(p.getX(), p.getY()); show(rp.getX(), rp.getY()); } static void show(int x, int y) { System.out.println("(" + x + ", " + y + ")"); } static void show(float x, float y) { System.out.println("(" + x + ", " + y + ")"); } }
出力の最初の行は,(0, 0) (2.7182798, 3.14159) (2, 3) (2, 3)
RealPoint
のインスタンスが,実際にクラスPoint
で宣言された二つの整数フィールドを含んでいることを示す。これは,そのフィールドの名前が,クラスRealPoint
(及びその下位クラス)の宣言内に出現するコードから,隠ぺいされていることを示す。型Point
の変数内のクラスRealPoint
のインスタンスへの参照を,フィールドx
にアクセスするために使用するとき,クラスPoint
で宣言された整数フィールドx
がアクセスされる。 その値がゼロという事実は,メソッド呼出しp.move(1,-1)
がクラスPoint
のメソッドmove
を呼び出していないことを示す。かわりに,クラスRealPoint
の上書きしたメソッドmove
を呼び出したことを示す。
出力の2行目は,フィールドアクセスrp.x
が,クラスRealPoint
で宣言されたフィールドx
を参照していることを示す。このフィールドは,型float
で,この2行目は,浮動小数点値を表示する。ちなみに,これは,名前show
のメソッドがオーバロードされていることも示す。メソッド呼出しにおける実引数の型は,二つの定義のどちらが呼び出されるかを示す。
最後の2行は,p.getX()
及びrp.getX()
のメソッド呼出しが,それぞれクラスRealPoint
で宣言されたメソッドgetX
を呼び出すことを示す。実際,クラスRealPoint
のインスタンスのためにRealPoint
本体外からクラスPoint
のメソッドgetX
を呼び出す方法は,そのオブジェクトへの参照を保持するためにどのような変数の型を使用しようとも,存在しない。したがって,フィールドとメソッドでは,動作が異なることがわかる。つまり,隠ぺいは,上書きと異なる。
static
)メソッドは,実際にそのメソッドの宣言を含むクラスを型とする参照を使用して,呼び出すことができる。この点で,staticメソッドの隠ぺいは,インスタンスメソッドの上書きとは異なる。次に例を示す。
この例の出力は,次のとおり。class Super { static String greeting() { return "Goodnight"; } String name() { return "Richard"; } } class Sub extends Super { static String greeting() { return "Hello"; } String name() { return "Dick"; } } class Test { public static void main(String[] args) { Super s = new Sub(); System.out.println(s.greeting() + ", " + s.name()); } }
Goodnight, Dick
greeting
の呼出しは,コンパイル時にどのクラスメソッドを呼び出すかを決定するために,s
の型,つまりSuper
を使用する。それに対し,name
の呼出しは,実行時にどのインスタンスメソッドを呼び出すかを決定するために,s
のクラス,つまりSub
を使用する。
この例は,次を出力する。import java.io.OutputStream; import java.io.IOException; class BufferOutput { private OutputStream o; BufferOutput(OutputStream o) { this.o = o; } protected byte[] buf = new byte[512]; protected int pos = 0; public void putchar(char c) throws IOException { if (pos == buf.length) flush(); buf[pos++] = (byte)c; } public void putstr(String s) throws IOException { for (int i = 0; i < s.length(); i++) putchar(s.charAt(i)); } public void flush() throws IOException { o.write(buf, 0, pos); pos = 0; } } class LineBufferOutput extends BufferOutput { LineBufferOutput(OutputStream o) { super(o); } public void putchar(char c) throws IOException { super.putchar(c); if (c == '\n') flush(); } } class Test { public static void main(String[] args) throws IOException { LineBufferOutput lbo = new LineBufferOutput(System.out); lbo.putstr("lbo\nlbo"); System.out.print("print\n"); lbo.putstr("\n"); } }
クラスlbo print lbo
BufferOutput
は,OutputStream
のごく単純なバッファ付き版を実装する。これは,バッファが満杯になるとき,又はflush
が呼び出されるときに,出力をフラッシュする。下位クラスLineBufferOutput
は,コンストラクタ及び一つのメソッドputchar
だけを宣言し,BufferOutput
のメソッド putchar
を上書きする。BufferOutput
は,クラスBufferOutput
からメソッドputstr
及びメソッドflush
を継承する。
オブジェクトLineBufferOutput
のメソッドputchar
では,文字実引数が改行ならば,メソッドflush
を呼び出す。この例で,上書きに関して重要なことは,クラスBufferOutput
で宣言されているメソッドputstr
は,現在のオブジェクトthis
で定義されているメソッドputchar
を呼び出すことである。このメソッドputchar
は,必ずしもクラスBufferOutput
で宣言されているメソッドputchar
ではない。
したがって,main
内で型LineBufferOutput
のオブジェクトlbo
を使ってputstr
が呼び出されるとき,メソッドputstr
本体内のputchar
の呼出しは,オブジェクトlbo
の putchar
の呼出し,つまり,改行を検査するputchar
で上書き宣言したものとなる。これにより,BufferOutput
の下位クラスは,再定義することなしに,メソッドputstr
の振舞いを変更できる。
拡張するように設計されているBufferOutput
のようなクラスの文書は,クラス及びその下位クラス間の約束事,又は下位クラスがこのようにメソッドputchar
を上書きできることを明確に示すことが望ましい。したがって,クラス BufferOutput
を実装する開発者は,BufferOutput
の今後の実装で,メソッドputchar
を使用しないようにputstr
の実装を変更することはしたくないであろう。そのような変更は,下位クラスとの間の既存の約束を破ることになるからである。13.特に13.2のバイナリ互換性の詳細な規定を参照すること。
BadPointException
の宣言内で新しい例外型を宣言するために,一般的及び従来的な形式を使用する。
この例は,コンパイル時エラーを生じる。その理由は,クラスclass BadPointException extends Exception { BadPointException() { super(); } BadPointException(String s) { super(s); } } class Point { int x, y; void move(int dx, int dy) { x += dx; y += dy; } } class CheckedPoint extends Point { void move(int dx, int dy) throws BadPointException { if ((x + dx) < 0 || (y + dy) < 0) throw new BadPointException(); x += dx; y += dy; } }
CheckedPoint
内のメソッドmove
の上書きが,クラスPoint
内のmove
で宣言していない検査例外を投げるように宣言しているからである。もしも,これがエラーと見なされないとすれば,この例外が投げられれば,型Point
の参照でのメソッドmove
の呼出し側が,呼出し側及びPoint
間での約束事を破ることになってしまう。class CheckedPoint extends Point { void move(int dx, int dy) { if ((x + dx) < 0 || (y + dy) < 0) throw new BadPointException(); x += dx; y += dy; } }
メソッドmove
の本体は,move
に対するthrows
節に出現しない検査例外,つまりBadPointException
を投げることができないために,別のコンパイル時エラーが発生する。
そのクラスが名前をつけたメンバ型を宣言するならば,その型の宣言は,そのクラスの上位クラス及び上位インタフェースで同じ名前をもつすべてのアクセス可能宣言を隠ぺいする(hide)と言う。
クラスC 内部での名前n のメンバ型の宣言d は,d が出現する点での有効範囲における名前n の他の型の宣言をおおい隠す。
単純名C で宣言されたメンバクラス又はインタフェースが完全限定名 Nのクラスの宣言内部で直接取り囲まれるならば,そのメンバクラス又はインタフェースは完全限定名N.Cをもつ。
クラスは,同じ名前の複数の型宣言を,二つのインタフェース,若しくは,その上位クラス及びインタフェースから継承してよい。あいまいな継承クラス又はインタフェースをその単純名で参照しようとすれば,コンパイル時エラーとなる。
複数経路で一つのインタフェースから同じ型宣言が継承されるならば,そのクラス又はインタフェースは,1度だけ継承されると考えられる。それは,あいまいさなくその単純名で参照される。
staticクラスが,取り囲むクラスの非staticメンバの使用を含むならば,コンパイル時エラーが発生する。
メンバインタフェースは,常に暗黙に staticとする。メンバインタフェースの宣言は,static 修飾子を明示的に並べてよいが,必要ではない。
InstanceInitializer: Block名前のあるクラスのインスタンス初期化子は,その例外又はその上位クラスの一つがそのクラスの各コンストラクタのthrows節で明示的に宣言され,そのクラスが少なくとも一つの明示的に宣言されたコンストラクタをもたない限りは,検査例外を投げてはならない。匿名クラス(15.9.5)でのインスタンス初期化子は,任意の例外を投げてよい。
上の規則は,名前付き及び匿名クラスでのインスタンス初期化子を区別する。この相違は,意図的である。 与えられた匿名クラスは,プログラムの単一点で初期化されるだけとする。したがって,匿名クラスのインスタンス初期化子によって,取り囲む式へどのような例外が起きるかについての情報を直接伝播することが可能となる。一方,名前のあるクラスは,多くの場所で初期化できる。 したがって,名前のあるクラスによって,取り囲む式へどのような例外が起きるかについての情報を伝播する唯一の方法は,そのコンストラクタのthrows節による。したがって,匿名クラスの場合では,より自由な規則を用いてよい。同様のことが,インスタンス変数初期化子にも適用される。
インスタンス初期化子が正常完了(14.20)できないならば,コンパイル時エラーとなる。return 文 (14.16)がインスタンス初期化子の内部に出現するならば,コンパイル時エラーとなる。
宣言が構文的に使用の後に出現するインスタンス変数の使用は,そのインスタンス変数が有効範囲にあったとしても,制限を受けることがある。インスタンス変数に対する前方参照の規則については,8.3.2.3を参照。
インスタンス初期化子は,現オブジェクト this (15.8.3)を参照し,キーワード super (15.11.2, 15.12)を使用することが許される。
StaticInitializer: static Block静的初期化子が検査例外(11.2)で中途完了(14.1, 15.6)する可能性があると,コンパイル時エラーが発生する。静的初期化子が通常完了(14.20)できないなら,コンパイル時エラーが発生する。
静的初期化子及びクラス変数初期化子は,ソーステキストで出現した順に実行される。
ソーステキストの後方に宣言が出現するクラスで宣言されるクラス変数は,それが有効範囲内にあるとしても,その使用に制限を受けることがある。クラス変数に対する前方参照の規則については,8.3.2.3を参照。
静的初期化子内の任意の場所でreturn
文(14.16)が出現すれば,コンパイル時エラーが発生する。
キーワードthis
(§15.8.3)又はキーワードsuper
(15.11, 15.12)が静的初期化子内の任意の場所で出現すれば,コンパイル時エラーが発生する。
ConstructorDeclaration: ConstructorModifiersopt ConstructorDeclarator Throwsopt ConstructorBody ConstructorDeclarator: SimpleTypeName ( FormalParameterListopt )ConstructorDeclarator内のSimpleTypeNameは,コンストラクタ宣言を含むクラスの単純名でなければならない。そうでなければ,コンパイル時エラーが発生する。この点を除けば,コンストラクタ宣言は,結果型をもたないメソッド宣言と同様とする。
コンストラクタは,クラスインスタンス生成式(15.9),文字列連結演算子 + (15.18.1)による変換及び連結,並びに他のコンストラクタからの明示的なコンストラクタ呼出し(8.8.5)によって呼び出される。コンストラクタは,決してメソッド呼出し式(15.12)で呼び出されない。class Point { int x, y; Point(int x, int y) { this.x = x; this.y = y; } }
コンストラクタへのアクセスは,アクセス修飾子(6.6)によって支配される。
これは,たとえば,アクセス不可能なコンストラクタを宣言することによってインスタンス化を防ぐ場合(8.8.8)に有用とする。 コンストラクタ宣言は,決して継承されず,したがって,隠ぺい又は上書きの対象にはならない。
ConstructorModifiers: ConstructorModifier ConstructorModifiers ConstructorModifier ConstructorModifier: one of public protected privateアクセス修飾子
public
, protected
及びprivate
は,6.6で規定する。コンストラクタ宣言内で,同じ修飾子が複数回出現する,又はコンストラクタ宣言がアクセス修飾子public
, protected
及びprivate
を複数回もつならば,コンパイル時エラーが発生する。
メソッドとは異なり,コンストラクタは,abstract
, static
, final
,
native
, strictfp
又はsynchronized
宣言できない。コンストラクタは,継承されないので,final
宣言する必要はなく,abstract
コンストラクタ宣言は,決して実装できない。コンストラクタは,常にオブジェクトに関して呼び出されるので,コンストラクタをstatic
宣言することは,意味がない。コンストラクタをsynchronized
宣言する必要性も実際にはない。そのオブジェクトに対するすべてのコンストラクタが処理を完了するまで,他のスレッドに対して使用可能とはならず,生成中のオブジェクトは,必然的にロック設定されるからとする。native
宣言ができないのは,オブジェクト生成中に,上位クラスのコンストラクタが常に適切に呼び出されることを,Java仮想計算機の実装が簡単に検証できるようにするための言語設計の任意な選択とする。
ConstructorModifierはstrictfp
と宣言できないことに注意。ConstructorModifierとMethodModifier (8.4.3)との定義における相違は,意図的な言語設計上の選択とする。これは,コンストラクタがFP厳密(15.4)である必要十分条件がそのクラスがFP厳密となることを実効的に保証する。
throws
節は,構造及び振舞いに関して,メソッドに対するthrows
節(8.4.4)と同一とする。
ConstructorBody: { ExplicitConstructorInvocationopt BlockStatementsopt }コンストラクタが
this
を含む一つ以上の明示的コンストラクタ呼出しの列を介して,直接的又は間接的に自分自身を呼び出すことは,コンパイル時エラーとする。
コンストラクタ本体が明示的コンストラクタ呼出しで開始されず,宣言しているコンストラクタが根源的クラスであるクラスObject
の一部でなければ,コンストラクタ本体は,コンパイラにより暗黙に,上位クラスのコンストラクタ呼出し"super();
"で開始すると仮定される。すなわち,その直接的上位クラスに存在する実引数を取らないコンストラクタ呼出しとする。
明示的なコンストラクタ呼出しの可能性以外は,コンストラクタ本体は,メソッド本体(8.4.5)と類似する。return
文(14.16)が式を含まなければ,return
文は,コンストラクタ本体で使用できる。
class Point { int x, y; Point(int x, int y) { this.x = x; this.y = y; } } class ColoredPoint extends Point { static final int WHITE = 0, BLACK = 1; int color; ColoredPoint(int x, int y) { this(x, y, WHITE); } ColoredPoint(int x, int y, int color) { super(x, y); this.color = color; } }
ColoredPoint
の最初のコンストラクタは,追加の実引数を指定して2番目のコンストラクタを呼び出す。ColoredPoint
の2番目のコンストラクタは,上位クラスPoint
のコンストラクタを座標を渡して呼び出す。12.5及び15.9は,新しいクラスインスタンスの生成及び初期化を記述する。
ExplicitConstructorInvocation: this ( ArgumentListopt ) ; super ( ArgumentListopt ) ; Primary.super ( ArgumentListopt ) ;
class Outer { class Inner{} } class ChildOfInner extends Outer.Inner { ChildOfInner(){(new Outer()).super();} }
this
又はsuper
を使用することもできない。そうでなければ,コンパイル時エラーが発生する。
たとえば,前述の例のColoredPoint
の最初のコンストラクタを次のように変更したとする。
この場合,コンパイル時エラーが発生する。その理由は,上位クラスのコンストラクタ呼出しでは,インスタンス変数が使用できないからである。ColoredPoint(int x, int y) { this(x, y, color); }
匿名クラスインスタンス生成式が,明示的コンストラクタ呼出し文の内部で出現するならば,その匿名クラスは,そのコンストラクタが呼び出されているクラスの取り囲みインスタンスを参照してはならない。
C をインスタンス化されているクラスとする。S をC の直接的上位クラスとする。i を生成されているインスタンスとする。明示的コンストラクタ呼出しの評価は次とする。class Top { int x; class Dummy { Dummy(Object o) {} } class Inside extends Dummy { Inside() { super(new Object() { int r = x; }); // error } Inside(final int y) { super(new Object() { int r = y; }); // correct } } }
super
"にすぐ先行する基本(Primary)式p が評価される。その基本式の評価がnull
ならば,NullPointerException
が起され,上位クラスコンストラクタ呼出しが中途完了する。 そうでなければ,この評価の結果は,S に関するi の直ちに取り囲むインスタンスとなる。O をS の直ちに字句的に取り囲むインスタンスとする。p の型がO 又はO の下位クラスでなければ,コンパイル時エラーとなる。
Object
ならば,デフォルトコンストラクタは,空の本体をもつ。
デフォルトコンストラクタは,throws
節をもたない。
したがって,上位クラスの空次数コンストラクタがthrows
節をもつなら,コンパイル時エラーが発生する。
クラスが public 宣言されていれば,デフォルトコンストラクタは,暗黙にアクセス修飾子public(6.6)を与えられる。クラスが protected宣言されていれば,デフォルトコンストラクタは,暗黙にアクセス修飾子protected (6.6)を与えられる。クラスが private宣言されていれば,デフォルトコンストラクタは,暗黙にアクセス修飾子private (6.6)を与えられる。そうでなければ,デフォルトコンストラクタは,アクセス修飾子が示されないデフォルトアクセスをもつ。
これは,次の宣言と等価とする。public class Point { int x, y; }
ここで,クラスpublic class Point { int x, y; public Point() { super(); } }
Point
は,public
なので,デフォルトコンストラクタは,public
となる。
クラスのデフォルトコンストラクタがクラスそのものと同じデフォルトコンストラクタをもつという規則は,単純かつ直観的とする。しかしながら,これは,クラスがアクセス可能なら常にそのコンストラクタがアクセス可能であると意味しないことに注意。次の例を見よ。
package p1; public class Outer { protected class Inner{} } package p2; class SonOfOuter extends p1.Outer { void foo() { new Inner(); // compile-time access error } }
Inner
のコンストラクタは, protectedとする。 しかしながら,そのコンストラクタは,Inner
に関してprotectedとし,Inner
はOuter
に関してprotectedとする。したがって,Inner
は,Outer
の下位クラスなので, SonOfOuter
においてアクセス可能とする。Inner
のコンストラクタは,クラスSonOfOuter
がInner
の下位クラスではないので,SonOfOuter
においてアクセス不能とする。したがって,Inner
はアクセス可能にもかかわらず,そのデフォルトコンストラクタは,アクセス不能とする。private
宣言することで,クラス宣言の外にあるコードがクラスのインスタンスを生成することを抑制するように,クラスを設計できる。public
クラスも同様に,少なくとも一つのコンストラクタを宣言してpublic
アクセスをもつデフォルトコンストラクタの生成を抑制すること及びパッケージの外でのインスタンスの生成を抑制することができる。クラスclass ClassOnly { private ClassOnly() { } static String just = "only the lonely"; }
ClassOnly
は,インスタンス化できない。これに対して,次の例を考える。
クラスpackage just; public class PackageOnly { PackageOnly() { } String[] justDesserts = { "cheesecake", "ice cream" }; }
PackageOnly
は,宣言されているパッケージjust
内でだけインスタンス化できる。
目次 | 前 | 次 | 索引 | Java言語規定 第2版 |