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

8. クラス

クラス宣言(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)してよい。

8.1 クラス宣言

クラス宣言(class declaration)は,新しい名前の参照型を指定する。

クラス宣言内のIdentifierは,クラスの名前を規定する。取り囲むクラス又はインタフェースに同じ単純名があれば,コンパイル時エラーが発生する。

8.1.1 クラス修飾子

クラス宣言は,クラス修飾子(class modifiers)を含んでよい。

すべての修飾子が,全種類のクラス宣言に適用されるわけではない。アクセス修飾子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の前述の生成規則内の記述と矛盾しない順序で出現することとする。

8.1.1.1 abstractクラス

abstractクラスは,不完全なクラス,又は不完全と考えられるクラスとする。abstractクラスだけが,abstractメソッド(8.4.3.1, 9.4),つまり,宣言されているがまだ実装されていないメソッドをもってよい。abstract宣言されていないクラスがabstractメソッドを含んでいれば,コンパイル時エラーが発生する。次の条件のいずれかが真ならば,クラスCは,abstractメソッドをもつ。

次に例を示す。
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 . . .

}

8.1.1.2 finalクラス

クラスの定義が完了しており,これ以上は下位クラスを要求又は必要としない場合,クラスをfinal宣言できる。finalクラスの名前が,他のclass宣言の extends(8.1.3)に出現した場合,コンパイル時エラーが発生する。 つまり,finalクラスは,下位クラスをもつことはできない。 クラスを同時にfinal及びabstractの両方で宣言した場合,クラスの実装を決して完了できない(8.1.1.1)ので,コンパイル時エラーが発生する。

finalクラスは,いかなる下位クラスももつことはないので,finalクラスのメソッドは,決して上書き(8.4.6.1)されることはない。

8.1.1.3 strictfpクラス

strictfp修飾子の効果は,クラス宣言内のすべてのfloat及び double式を明示的に FP厳密 (15.4)とする。これは,クラスで宣言される全メソッド及び全入れ子型が暗黙にstrictfpとなることを意味する。

クラスのすべての変数初期化子,インスタンス初期化子,静的初期化子及びコンストラクタの中でのfloat及びdouble 式も明示的に FP厳密であることに注意。

8.1.2 内部クラス及び取り囲みインスタンス

内部クラス(inner class)は,明示的又は暗黙にstaticと宣言されない入れ子クラスとする。内部クラスは,静的初期化子(8.7)又はメンバインタフェースを宣言してはならない。内部クラスは,コンパイル時定数フィールド(15.28)でない限りは,静的メンバを宣言してはならない。

この規則の説明に次の例を用いる。

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
}
内部クラスは,コンパイル時定数でない静的メンバを継承してよい。ただし,宣言してはならない。内部クラスでない入れ子クラスは,Java言語の通常の規則にしたがって自由に静的メンバを宣言してよい。メンバインタフェース(8.5)は,常に暗黙でstaticなので,内部クラスとは決して見なされない。

文又は式が静的文脈で出現する(occurs in a static context)必要十分条件は,その文又は式を取り囲む最内のメソッド,コンストラクタ,インスタンス初期化子,静的初期化子,フィールド初期化子,又は明示的コンストラクタ文が,静的メソッド,静的初期化子,静的変数の変数初期化子,又は明示的コンストラクタ呼出し文 (8.8.5)であることとする。

内部クラスCクラスOの直接内部クラス(direct inner class of a class O)であるのは,OC を直ちに字句的に取り囲むクラスであり,かつ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)であるのは,Cn - 1番目の字句的取り囲みクラスを直ちに取り囲むクラスである場合とする。

クラスO の直接内部クラスC のインスタンスi は,i直ちに取り囲むインスタンス(immediately enclosing instance)として知られるO のインスタンスと関連する。オブジェクトの直ちに取り囲むインスタンスは,存在するなら,そのオブジェクトが生成された(15.9.2)時に決定される。

オブジェクトo は,それ自身のゼロ番目の字句的取り囲みインスタンス(zeroth lexically enclosing instance of itself)とする。オブジェクトoインスタンスiのn番目の字句的取り囲みインスタンスであるのは,in - 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字句的取り囲みインスタンス)をもつ。

8.1.3 上位クラス及び下位クラス

クラス宣言内の必須ではないextends節は,現在のクラスの直接的上位クラス(direct superclass)を指定する。 そのクラスは,そのクラスが拡張された元のクラスの直接的下位クラス(direct subclass)と呼ぶ。 直接的上位クラスとは,現在のクラスの実装が他のクラスの実装を導出して得られたときの,導出元クラスとする。extends節は,クラスObjectの定義に出現してはならない。その理由は,クラスObjectが根源的なクラスであり,直接的上位クラスをもたないからである。 これ以外のクラスのクラス宣言がextends節をもたなければ,そのクラスは,暗黙の直接的上位クラスとしてクラスObjectをもつ。

次は,4.3に存在する生成規則だが,明確化のためにここに再度規定する。

ClassTypeは,アクセス可能な(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
この例は,次の関係をもつ。

クラスColored3dPointの宣言は,finalクラスColoredPointを拡張しようとしているので,コンパイル時エラーが発生する。

下位クラス(subclass)の関係は,直接的下位クラス関係の推移的閉包(transitive closure)とする。 次のいずれかが真ならば,クラスA は,クラスC の下位クラスとする。

AC の下位クラスであるときは必ず,クラスC は,クラスA上位クラス(superclass)であると呼ぶ。

次に例を示す。

class Point { int x, y; }
class ColoredPoint extends Point { int color; }
final class Colored3dPoint extends ColoredPoint { int z; }
この例での関係は,次のとおり。

クラスCが型Tに直接依存する(directly depends)のは,Cのextends又はimplements節で,上位クラス又は 上位インタフェースとして,あるいは,上位クラス又は上位イン タフェース名内部の限定子として,Tが記述される場合とする。 クラス C が参照型Tに依存する(depends)のは,次の条件のいずれかが成り立つ場合とする。

クラスが自身に依存するならば,コンパイル時エラーとなる。

次の例は,コンパイル時エラーを発生する。

class Point extends ColoredPoint { int x, y; }
class ColoredPoint extends Point { int color; }

クラスをロード(12.2)する際,実行時に循環的に宣言されたクラ スを検出した場合,ClassCircularityErrorが投げられる。

8.1.4 上位インタフェース

クラス宣言内の,必須でないimplements節は,宣言しようとして いるクラスの直接的上位インタフェース(direct superinterfaces)のインタフェース名を列挙する。

次は,4.3に存在する生成規則だが,明確化のためにここに再度規定する。

InterfaceTypeは,アクセス可能な(6.6)インタフェース型の名前でなければならない。 そうでなければ,コンパイル時エラーが発生する。

一つのimplements節の中で同じインタフェースが2回以上指定されている場合,コンパイル時エラーが発生する。

たとえインタフェースが違った形式で名前付けされていても,これが真となる。次に例を示す。

class Redundant implements java.lang.Cloneable, Cloneable {
	int x;
}
これは,名前 java.lang.Cloneable 及び.Cloneableが,同じインタフェースを参照しているため,コンパイル時エラーとなる。

次の条件のいずれかが真ならば,インタフェース型I は,クラス型C上位インタフェース(superinterface)とする。

あるクラスは,そのすべての上位インタフェースを実装(implement)しているといわれる。

例を示す。

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; }
}
この例の関係は,次のとおりとする。

クラスは,複数の方法で上位インタフェースをもつことができる。 この例では,クラス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;
}
その理由は,ColoredPointabstractクラスではないのに,インタフェース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)となる。

8.1.5 クラス本体及びメンバ宣言

クラス本体(class body)は,そのクラスのメンバの宣言,つまり,フィールド(8.3), クラス(8.5), インタフェース(8.5)及びメソッド(8.4)を含んでよい。クラス本体はまた,クラスのインスタンス初期化子(8.6),静的初期化子(8.7)及びコンストラクタの宣言(8.8)を含んでよい。

クラス型Cで宣言された,又は継承されたメンバ mの宣言の有効範囲は,入れ子型宣言を含めてC の本体全体とする。

C そのものが入れ子クラスなら,取り囲む有効範囲においてmのための同種(変数,メソッド,又は型)の定義がある。 (有効範囲は,ブロック,クラス,又はパッケージでよい。) これらの場合,C で宣言又は継承されたメンバ m は,mのほかの定義をおおい隠す。(6.3.1)

8.2 クラスのメンバ

クラス型のメンバは,次のとおりとする。

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
	}
}
この例は,四つのコンパイル時エラーを起こす。

8.2.1 継承の例

ここでは,幾つか例を挙げてクラスメンバの継承について規定する。

8.2.1.1 例: デフォルトアクセスを伴った継承

パッケージpointsが,二つのコンパイル単位として宣言されている例を考える。

package points;
public class Point {
	int x, y;
	public void move(int dx, int dy) { x += dx; y += dy; }
}
及び

package points;
public class Point3d extends Point {
	int z;
	public void move(int dx, int dy, int dz) {
		x += dx; y += dy; z += dz;
	}
}
さらに,他のパッケージ内の3番目のコンパイル単位を考える。

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を継承せず,コンパイルは,失敗する。

3番目のコンパイル単位の正しい記述は,次のとおりとする。

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;
	}
}
dxdy及び dzを処理するために,上位クラスPoint3dのメソッドmoveを使用している。クラスPoint4dをこのように記述すれば,エラーなしでコンパイルできる。

8.2.1.2 public及びprotectedを伴った継承

クラス 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++;
	}
}

8.2.1.3 privateを伴った継承

次に例を示す。

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の増分を試みる箇所で,コンパイル時エラーが発生する。

8.2.1.4 アクセス不可能クラスのメンバへのアクセス

たとえクラスが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;
	}
}
次に,他のパッケージの他のコンパイル単位を考える。

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(); 
	}
}
さらに,第3のパッケージ内の morePoints.OnePoint.getOne()の呼出しは,型Point3dが,パッケージmorePointsの外では使用できないにもかかわらず,Pointとして使用可能なPoint3dを返す。このとき,メソッドmoveをそのオブジェクトに対して呼び出すことができる。この処理は,Point3dのメソッドmovepublicなので許される。(この条件は,publicなメソッドを上書きするメソッドは,それ自体が publicでなければならないために必須とする。この条件が満たされていなければ,正しく処理されない。)オブジェクトのフィールドx及びyは,第3のパッケージからもアクセス可能とする。

クラスPoint3dのフィールドzは, publicなのだが,型Pointの変数pでクラスPoint3dのインスタンスへの参照だけが与えられているために,パッケージmorePointsの外のコードからは,このフィールドにアクセスできない。pは,型Pointをもつこと及びクラスPointが名前zのフィールドをもたないこと,のために,式p.zは正しくない。クラス型Point3dは,パッケージmorePointsの外からは参照できないので,式((Point3d)p).zも正しくない。

しかし,フィールドzpublic宣言が無意味なわけではない。パッケージmorePointsにクラスPoint3dpublicな下位クラス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以外のパッケージ内のコードからもアクセス可能となる。

8.3 フィールド宣言

クラス型の変数は,フィールド宣言(field declarations)で導入する。

FieldModifiersは,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のフィールドが,倍精度数値集合の要素でない倍精度指数部拡張数値集合の要素を含むことも許さない。

8.3.1 フィールド修飾子

アクセス修飾子public, protected及びprivateは,6.6で規定する。 フィールド宣言に同じ修飾子が2回以上出現した場合,又はフィールド宣言がアクセス修飾子public, protected及びprivateを2個以上含んでいる場合,コンパイル時エラーが発生する。

フィールド宣言内で,複数個の(異なる)フィールド修飾子が出現する場合,必須ではないが,習慣上,前述のFieldModifierの生成規則内の記述の順序と矛盾しないように出現するものとする。

8.3.1.1 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が同じ変数を参照しているからである。

8.3.1.2 finalフィールド

フィールドは,final宣言(4.5.4)できる。クラス変数及びインスタンス変数(staticフィールド及び 非staticフィールド)の両方ともfinal宣言してよい。

未初期化最終 (4.5.4)クラス変数が,その宣言されたクラスで静的初期化子(8.7)により,確実に代入(16.7)されていなければ,コンパイル時エラーとする。

未初期化最終インスタンス変数は,宣言したクラスでのすべてのコンストラクタ(8.8)の終りで確実に代入(16.8)されなければならない。そうでなければ,コンパイル時エラーとなる。

8.3.1.3 transientフィールド

変数は,それがオブジェクトの永続状態の一部ではないことを示すために,transientを付してもよい。

次に例を示す。

class Point {
	int x, y;
	transient float rho, theta;
}
クラスPointのインスタンスが,システムサービスによって永続的記憶域に保存される場合,フィールドx及びyだけが保存される。本言語規定では,このサービスの詳細は,規定しない。そのようなサービス例については,java.io.Serializableの規定を参照せよ。

8.3.1.4 volatileフィールド

Java言語では,17.で規定してあるように,共有変数にアクセスするスレッドが,その変数の私的作業コピーをもつことができる。 これは,マルチスレッドのより効率的な実装を可能にしている。 このような作業コピーは,予め決められた同期点でだけ,つまりオブジェクトがロック設定又はロック解除されるときにだけ,共有主メモリ内のマスタコピーと一致させる必要がある。 共有変数が一貫性及び信頼性をもって更新されることを確実にするために,スレッドは,原則として,それらの共有変数に対する相互排他を強制するロックを得ることによって,それらの変数を排他的に使用しなければならない。

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及びtwosynchronized宣言(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が戻る前に, 両方とも更新されることを保証する。 したがって,メソッド twoiの値より大きな値のjを見ることはない。 実際には,メソッド twoは,i及びjに対して,常に同じ値を見る。

もう一つの方法は,i及び jvolatile宣言することとする。

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に対する共有変数へのアクセスは,それぞれのスレッドによるプログラムテキストの実行中に出現するものと,正確に同じ回数及び同じ順序で行われることを保証する。 したがって,メソッドtwoiの値より大きな値のjを見ることはない。 その理由は,iに対するそれぞれの更新は,jへの更新が発生する前に,iの共有変数に反映されなければならないからである。 しかしながら,メソッドtwoのある呼出しが,iの値より大きな値のjを見ることはある。 その理由は,メソッドtwoが,iの値を取得する瞬間及びjの値を取得する瞬間の間に,メソッドoneが,何回か実行されているかも知れないからである。 詳細な規定及び例については,17.を参照のこと。

final変数を,volatile宣言した場合,コンパイル時エラーが発生する。

8.3.2 フィールドの初期化

フィールド宣言子が変数初期化子(variable initializer)を含む場合,宣言された変数へ値が代入(15.26)されることを意味する。さらに,次が成立する。

例:
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)によって中途完了する可能性がある場合,コンパイル時エラーが発生する。

8.3.2.1 クラス変数用の初期化子

クラス変数に対する初期化式において,インスタンス変数への単純名による参照が出現する場合,コンパイル時エラーが発生する。

キーワード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を参照のこと。

8.3.2.2 インスタンス変数用の初期化子

インスタンス変数に対する初期化式は,そのクラスで宣言された又は継承された任意のstatic変数の単純名を,たとえその宣言がソーステキストで後に出現する場合でも,使用してもよい。

例を示す。

class Test {
	float f = j;
	static int j = 1;
}
この例は,エラーなしにコンパイルできる。 クラスTestが初期化されるときに,j1に初期化し,クラスTestのインスタンスが生成されるたびに,fjの現在の値に初期化する。

インスタンス変数に対する初期化式は,現在のオブジェクトを参照するためのキーワードthis (15.8.3)及び上位クラスのオブジェクトを参照するためのキーワードsuper (15.11.2, 15.12)を使用してよい。

宣言がソーステキスト上での使用の後に出現するインスタンス変数の使用は,たとえ有効範囲にあっても,制限を受ける。インスタンス変数への前方参照を支配する適切な規則については,8.3.2.3を参照のこと。

8.3.2.3 初期化中のフィールド使用の制限

クラス又はインタフェースC のインスタンス(又はstatic)フィールドであり,さらに次の条件すべてが成り立ちさえすれば,メンバの宣言は,使用の前に出現する必要がある。

上の3要件のどれかが満たされない場合,コンパイル時エラーとなる。

したがって,次のプログラムはコンパイル時エラーとなる。

	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;
}

8.3.3 フィールド宣言の例

次の例では,フィールド宣言に関する幾つかの(微妙な)点を規定する。

8.3.3.1 例: クラス変数の隠ぺい

例:

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のフィールドxsuper.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

8.3.3.2 例: インスタンス変数の隠ぺい

次の例は,前述の例と似ているが,static 変数ではなく,インスタンス変数を使用している。

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

8.3.3.3 例: 多重に継承されたフィールド

クラスは,同じ名前をもつ複数のフィールドを,二つのインタフェースから,又はその上位クラス及びインタフェースから継承してよい。あいまいに継承されたフィールドを単純名で参照しようとすると,コンパイル時エラーが発生する。それらのフィールドを,あいまい性なしにアクセスするために,限定名又はキーワード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の二つのフィールドが,偶然に同じ型及び同じ不変値をもっている事実は,この判断には影響しない。

8.3.3.4 例: フィールドの再継承

同じフィールド宣言が複数の経路によって一つのインタフェースから継承されている場合,そのフィールドは,一度だけ継承されたものと見なす。そのフィールドは,単純名によってあいまい性なしに参照してよい。例えば,次のコードを考える。

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内であいまい性なく使用してよい。

8.4 メソッド宣言

メソッド(method)は,決まった数の値を実引数として渡しながら呼び出すことができる実行可能コードを宣言する。

MethodModifiersは,8.4.3Throws節は,8.4.4MethodBodyは,8.4.5で,それぞれ規定する。メソッド宣言は,メソッドが返す値の型を指定するか,又はメソッドが値を返さないことを示すためにキーワードvoidを使用する。

MethodDeclarator 内のIdentifier は,そのメソッドを参照する名前として使用する。クラスは,そのクラス又はそのクラスのフ ィールド,メンバクラス,又はメンバインタフェースと同じ名前のメソッドを宣言できる。

Javaプラットフォームの旧版との互換性のために,配列を返すメソッドの宣言形式では,仮引数並びの後ろに,配列型の宣言を構成する空の角括弧対をおいてもよい。これは,次の過去の処理系の構文規則に対応する。

しかし,新しいJavaコードでは,使用してはならない。

クラスの本体が同じシグネチャ(8.4.2)(名前,仮引数の個数及びすべての仮引数の型)をもつ二つのメソッドを,メンバとして 定義することは,コンパイル時エラーとする。メソッド及びフィールドは,異なった文脈で使用されること及び異なる検索手続きによってあいまい性が解消される(6.5)ので,同じ名前をもってよい。

8.4.1 形式仮引数

メソッドの形式仮引数(formal parameters)は,もしあれば,コンマで区切られた仮引数指定子の並びによって指定する。各仮引数指定子は,型及び仮引数の名前を指定する識別子(角括弧が続くこともある)から構成される。

次は,8.3に存在する生成規則だが,明確化のためにここに再度記述する。

メソッド又はコンストラクタが仮引数をもたなければ,メソッド又はコンストラクタの宣言には,空の丸括弧の対だけが出現する。

同じメソッド又はコンストラクタの二つの形式仮引数が同じ名前をもつように宣言された(つまり,それらの宣言に同じ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)により,対応する標準値集合における一番近い値に対応付けられる。

8.4.2 メソッドのシグネチャ

メソッドのシグネチャ(signature)は,メソッドの名前並びにメソッドに対する形式仮引数の個数及び型で構成する。 クラスは,同じシグネチャをもつ二つのメソッドを宣言してはならない。 そうでなければ,コンパイル時エラーが発生する。

次に例を示す。

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であってもエラーとする。

8.4.3 メソッド修飾子

アクセス修飾子public, protected及びprivateは,6.6で規定する。 メソッド宣言内で,同じ修飾子が複数回出現した場合,又はメソッド宣言がアクセス修飾子public, protected及びprivateの二つ以上をもつ場合,コンパイル時エラーが発生する。キーワードabstractを含むメソッド宣言が,キーワードprivate, static, final, native, strictfp又はsynchronizedのいずれかを含む場合,コンパイル時エラーが発生する。キーワードnativeを含むメソッド宣言がstrictfpも含むなら,コンパイル時エラーが発生する。

メソッド宣言で複数のメソッド修飾子を指定するときは,習慣上,必須ではないが,MethodModifier に関する,前述の生成規則内の順序と矛盾しない順序で指定する。

8.4.3.1 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();
}
このtoStringabstract宣言は,クラスObjectabstract宣言されていないメソッド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
	}
}

8.4.3.2 staticメソッド

static宣言されたメソッドを,クラスメソッド(class method)と呼ぶ。 クラスメソッドは,常に特定のオブジェクトへの参照なしで呼び出される。 クラスメソッドの本体で,キーワードthis又は キーワードsuperを使用して現在のオブジェクトを参照しようとすると,コンパイル時エラーが発生する。staticメソッドをabstract宣言することは,コンパイル時エラーとする。

static宣言されていないメソッドを,インスタンスメソッド(instance method)と呼ぶ。これは,非staticメソッドと呼ぶこともある。インスタンスメソッドは,常にあるオブジェクトに関して呼び出され,そのオブジェクトが,メソッド本体の実行中にキーワードthis及びキーワードsuperで参照される,現在のオブジェクトとなる。

8.4.3.3 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のコードも更新されるようにするためである。

8.4.3.4 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;
}

8.4.3.5 strictfp メソッド

strictfp修飾子の効果は,メソッド本体内部のfloat又は double式のすべてを明示的にFP厳密 (15.4)とすることにある。

8.4.3.6 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.を参照のこと。

8.4.4 メソッドthrows

throws節(throws clause)は,メソッド又はコンストラクタの実行 から生じる可能性のある検査例外(11.2)を宣言するために使用する。

throws 節で指定したClassTypeが,クラスThrowable又はその下位クラスでなければ,コンパイル時エラーが発生する。throws節内で他の例外(非検査例外)も指定してよいが,必須ではない。

メソッド又はコンストラクタ本体の実行の結果生じる可能性のある検査例外に対して,その例外の型又はその例外の型の上位クラスが,メソッド又はコンストラクタの宣言内のthrows節に指定されていなければ,コンパイル時エラーが発生する。

検査例外の宣言は,必須なので,そのようなエラー条件を処理するコードが含まれているかどうかをコンパイラで確認できる。 検査例外として投げられる例外条件の処理に失敗するメソッド又はコンストラクタは,throws節に適当な例外型を指定していないことによって,通常は,コンパイル時エラーを生じる。 Javaでは,このように,あるプログラミングスタイルを奨励している,そこでは,発生頻度は少ないが本当に例外的な条件は,この方法によって文書化されている。

この方法で検査されない定義済みの例外とは,可能な発生をすべて宣言することが不便で現実的でないものとする。

インタフェースで定義されたabstractメソッドを実装するメソッドを含み,他のメソッドを上書き又は隠ぺいするメソッド(8.4.6)は,上書き又は隠ぺいされたメソッドよりも多くの検査例外を投げるように宣言してはならない。

より正確に規定する。Bをクラス又はインタフェース,ABの上位クラス又はインタフェースとし,Bの中のメソッド宣言nが,Aの中のメソッド宣言mを上書き又は隠ぺいするとする。nが検査例外の型を指定するthrows節をもつ場合,mは,throws節をもたなければならず,nthrows節に上げられているすべての検査例外の型に対して,同じ例外クラス又はその上位クラスの一つがmthrows節内に出現しなければならない。そうでなければ,コンパイル時エラーが発生する。

例外に関するより多くの情報及び豊富な例については,11.を参照のこと。

8.4.5 メソッド本体

メソッド本体(method body)は,メソッドを実装するコードのブロック又は実装がないことを示す単なるセミコロンとする。メソッド本体がセミコロンである必要十分条件は,メソッドがabstract(8.4.3.1)又はnative (8.4.3.4)とする。

メソッド宣言が,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?!"); }
}

8.4.6 継承,上書き及び隠ぺい

クラスは,その直接的上位クラス及び直接的上位インタフェースから,クラス内でコードにアクセス可能であり,クラス内の宣言によって上書き(8.4.6.1)又は隠ぺい(8.4.6.2)されていない上位クラス及び上位インタフェースのすべての非privateメソッドを(abstractであってもなくても)継承(inherits)する。

8.4.6.1 (インスタンスメソッドによる)上書き

クラスC で宣言されたインスタンスメソッドm1 が,クラスA で宣言された同じシグネチャのほかのメソッドm2上書きする(overrides)のは,次の2条件が成り立つ場合とする。
  1. CA の下位クラスとする。
  2. 次のどちらかが成り立つ。
さらに,m1 が abstractでなければ,m1 は,上書きするabstractメソッドのあらゆる宣言を実装する(implement)と言う。

インスタンスメソッドがstaticメソッドを上書きすると,コンパイル時エラーが発生する。

この点で,メソッドの上書きは,フィールドの隠ぺい(8.3)とは異なる。その理由は,インスタンス変数が,static変数を隠ぺいすることは,許されることによる。

上書きされたメソッドは,キーワードsuperを含むメソッド呼出し式(15.12)を使用して,アクセス可能とする。 限定名又は上位クラスへのキャストで,上書きされたメソッドにアクセスしようとすることは無効とすることに注意せよ。 この点で,メソッドの上書きは,フィールドの隠ぺいとは異なる。この点の規定及び例については,15.12.4.9を参照のこと。

strictfp修飾子の存在又は欠如は,メソッドの上書き及びabstract メソッドの実装の規則に何らの影響も絶対もたない。例えば, FP厳密でないメソッドがFP厳密メソッドを上書きすること,ならびに,FP厳密でないメソッドを上書きすることは,許される。

8.4.6.2 (クラスメソッドによる)隠ぺい

クラスが staticメソッドを宣言している場合,このメソッド宣言は,そのクラスの上位クラス及び上位インタフェース内で同じシグネチャをもつあらゆるメソッドを隠ぺい(hide)する。 隠ぺいされなければ,その上位クラス及び上位インタフェースのメソッドは,クラス内でアクセス可能とする。staticメソッドがインスタンスメソッドを隠ぺいすると,コンパイル時エラーが発生する。

この点で,メソッドの隠ぺいは,フィールドの隠ぺい(8.3)とは異なる。 すなわち,static変数が,インスタンス変数を隠ぺいすることは,許されている。隠ぺいは,おおい隠し(6.3.1)ならびに不明瞭化(6.3.2)とも異なる。

隠ぺいされたメソッドは,限定名,又はキーワードsuper若しくは上位クラス型へのキャストを含むメソッド呼出し式(15.12)を使用して,アクセス可能とする。 この点で,メソッドの隠ぺいは,フィールドの隠ぺいと似ている。

8.4.6.3 上書き及び隠ぺいの要件

メソッド宣言が他のメソッド宣言を上書き又は隠ぺいする場合,これらメソッドが異なる返却値の型をもつ,又は一方のメソッドが返却値の型をもち他方はvoidならば,コンパイル時エラーが発生する。 さらに,メソッド宣言は,上書き又は隠ぺいするメソッドのthrows節と衝突(8.4.4)するthrows節をもってはならない。そうでなければ,コンパイル時エラーが発生する。

この点で,メソッドの上書きは,フィールド(8.3)の隠ぺいとは異なる。 すなわち,フィールドが他の型のフィールドを隠ぺいすることは,許されている。

メソッドを上書き又は隠ぺいするアクセス修飾子(6.6)は,少なくとも上書き又は隠ぺいされるメソッドと同じアクセスを提供しなければならない。そうでなければ,コンパイル時エラーが発生する。詳細は,次のとおりとする。

private メソッドは,技術的な意味で,隠ぺい又は上書きされないことに注意。 これは,下位クラスでは,その上位クラスのprivateメソッドと同じシグネチャをもつメソッドを宣言できること及びそのようなメソッドの返却値の型又は throws節が,その上位クラス内のprivateメソッドの返却値の型又はthrows節になんらかの関係をもつ必要がないことを意味している。

8.4.6.4 同じシグネチャをもつメソッドの継承

クラスは,同じシグネチャをもつ複数のメソッドを継承できる。 この状況それ自体では,コンパイル時エラーを引き起こさない。 次の二つの可能な場合が存在する。

同じシグネチャをもつ二つ以上の継承されたメソッドがabstractではないことはあり得ない。 その理由は,abstractではないメソッドは,直接的上位クラスからだけ継承され,上位インタフェースからは継承されないからである。

インタフェースの同じメソッド宣言が複数の経路で継承される場合がある。 これで特に問題が生じることも,これが原因でコンパイル時エラーが発生することもない。

8.4.7 オーバロード

クラスの二つのメソッド(両方とも同じクラスで宣言されているか,両方とも一つのクラスから継承されているか,又は一方は,宣言され他方は継承されているか,にかかわらず) が同じ名前をもつが異なるシグネチャをもてば,メソッド名は,オーバロード(overloaded)されたと呼ぶ。これ自体は,いかなる困難も引き起こさず,コンパイル時エラーを生じることはない。同じ名前だが異なるシグネチャをもつ二つのメソッドの返却値の型又はthrows節には,いかなる関係も要求されない。

メソッドは,シグネチャ単位で上書きされる。

たとえば,クラスが同じ名前をもつ二つのpublicメソッドを宣言し,下位クラスがそのうちの一つを上書きする場合,その下位クラスは,他方のメソッドを継承する。この点で,Java は, C++ とは異なる。

メソッドが呼び出される(15.12)とき,呼び出されるメソッドのシグネチャを決定(15.12.2)するために,コンパイル時に,実引数の数及び実引数のコンパイル時の型が使用される。 呼び出すべきメソッドがインスタンスメソッドであれば,呼び出される実際のメソッドは,動的メソッド検索を使用して実行時に決定(15.12.4)される。

8.4.8 メソッド宣言の例

次の例は,メソッド宣言に関して幾つかの(微妙な)点を規定する。

8.4.8.1 例: 上書き

例を示す。

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内の上書きする定義が常に呼び出される。

8.4.8.2 例: オーバロード,上書き及び隠ぺい

例を示す。

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から継承されたインスタンス変数colorRealPointで宣言された型floatのインスタンス変数x及びy,並びにRealPointで宣言された二つのメソッドmoveを含んでいる。

クラスRealPointのオーバロードされたメソッドmoveのうちいずれを特定のメソッド呼出しに対して選択するかは,15.12で規定されるオーバロード解決手順によってコンパイル時に決定する。

8.4.8.3 例: 不当な上書き

次の例は,前節の例を拡張したものとする。

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の値を返している。

8.4.8.4 例: 上書き及び隠ぺい

次の例は,前節の例のエラーを訂正する。

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を呼び出す方法は,そのオブジェクトへの参照を保持するためにどのような変数の型を使用しようとも,存在しない。したがって,フィールドとメソッドでは,動作が異なることがわかる。つまり,隠ぺいは,上書きと異なる。


8.4.8.5 例: 隠ぺいされたクラスメソッドの呼出し

隠ぺいされたクラス (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を使用する。

8.4.8.6 上書きの大きな例

上書きは,次の例が示すように,下位クラスが既存クラスの振舞いを拡張するのを容易にする。

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の呼出しは,オブジェクトlboputcharの呼出し,つまり,改行を検査するputcharで上書き宣言したものとなる。これにより,BufferOutputの下位クラスは,再定義することなしに,メソッドputstrの振舞いを変更できる。

拡張するように設計されているBufferOutputのようなクラスの文書は,クラス及びその下位クラス間の約束事,又は下位クラスがこのようにメソッドputcharを上書きできることを明確に示すことが望ましい。したがって,クラス BufferOutputを実装する開発者は,BufferOutputの今後の実装で,メソッドputcharを使用しないようにputstrの実装を変更することはしたくないであろう。そのような変更は,下位クラスとの間の既存の約束を破ることになるからである。13.特に13.2のバイナリ互換性の詳細な規定を参照すること。

8.4.8.7 例: 例外投げ(throw)がひき起こす不正な上書き

次の例では,クラス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間での約束事を破ることになってしまう。

throws節を削除しても効果はない。

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を投げることができないために,別のコンパイル時エラーが発生する。

8.5 メンバ型宣言

メンバクラス(member class)は,その宣言が他のクラス又はインタフェース宣言に直接取り囲まれるクラスとする。同様に,メンバインタフェース(member interface)は,その宣言が他のクラス又はインタフェース宣言に直接取り囲まれるインタフェースとする。メンバクラス又はインタフェースの有効範囲(6.3)は,8.1.5で規定する。

そのクラスが名前をつけたメンバ型を宣言するならば,その型の宣言は,そのクラスの上位クラス及び上位インタフェースで同じ名前をもつすべてのアクセス可能宣言を隠ぺいする(hide)と言う。

クラスC 内部での名前n のメンバ型の宣言d は,d が出現する点での有効範囲における名前n の他の型の宣言をおおい隠す。

単純名C で宣言されたメンバクラス又はインタフェースが完全限定名 Nのクラスの宣言内部で直接取り囲まれるならば,そのメンバクラス又はインタフェースは完全限定名N.Cをもつ。

クラスは,同じ名前の複数の型宣言を,二つのインタフェース,若しくは,その上位クラス及びインタフェースから継承してよい。あいまいな継承クラス又はインタフェースをその単純名で参照しようとすれば,コンパイル時エラーとなる。

複数経路で一つのインタフェースから同じ型宣言が継承されるならば,そのクラス又はインタフェースは,1度だけ継承されると考えられる。それは,あいまいさなくその単純名で参照される。

8.5.1 アクセス修飾子

public, protected, 及び privateというアクセス修飾子は,6.6で論じる。メンバ型宣言がpublic, protected, 及び privateというアクセス修飾子を複数もつならば,コンパイル時エラーが発生する。

8.5.2 staticメンバ型宣言

キーワード static は,非内部クラス T の本体においてメンバ型 C の宣言を修飾する。その効果は,C が内部クラスでない宣言とする。T のstatic メソッドがその本体内にT の現在のインスタンスをもたないのと同様に,C もTの現在インスタンスをもたないだけでなく,字句的に取り囲むインスタンスももたない。

staticクラスが,取り囲むクラスの非staticメンバの使用を含むならば,コンパイル時エラーが発生する。

メンバインタフェースは,常に暗黙に staticとする。メンバインタフェースの宣言は,static 修飾子を明示的に並べてよいが,必要ではない。

8.6 インスタンス初期化子

クラスで宣言されたインスタンス初期化子(instance initializer)は,そのクラスが生成された(15.9)時に8.8.5.1で規定されたように実行される。

名前のあるクラスのインスタンス初期化子は,その例外又はその上位クラスの一つがそのクラスの各コンストラクタのthrows節で明示的に宣言され,そのクラスが少なくとも一つの明示的に宣言されたコンストラクタをもたない限りは,検査例外を投げてはならない。匿名クラス(15.9.5)でのインスタンス初期化子は,任意の例外を投げてよい。

上の規則は,名前付き及び匿名クラスでのインスタンス初期化子を区別する。この相違は,意図的である。 与えられた匿名クラスは,プログラムの単一点で初期化されるだけとする。したがって,匿名クラスのインスタンス初期化子によって,取り囲む式へどのような例外が起きるかについての情報を直接伝播することが可能となる。一方,名前のあるクラスは,多くの場所で初期化できる。 したがって,名前のあるクラスによって,取り囲む式へどのような例外が起きるかについての情報を伝播する唯一の方法は,そのコンストラクタのthrows節による。したがって,匿名クラスの場合では,より自由な規則を用いてよい。同様のことが,インスタンス変数初期化子にも適用される。

インスタンス初期化子が正常完了(14.20)できないならば,コンパイル時エラーとなる。return 文 (14.16)がインスタンス初期化子の内部に出現するならば,コンパイル時エラーとなる。

宣言が構文的に使用の後に出現するインスタンス変数の使用は,そのインスタンス変数が有効範囲にあったとしても,制限を受けることがある。インスタンス変数に対する前方参照の規則については,8.3.2.3を参照。

インスタンス初期化子は,現オブジェクト this (15.8.3)を参照し,キーワード super (15.11.2, 15.12)を使用することが許される。

8.7 静的初期化子

クラスで宣言される静的初期化子(static initializers)は,クラスが初期化されるときに実行され,クラス変数用のフィールド初期化子(8.3.2)とともに,クラスのクラス変数を初期化 (12.4)するために使用できる。

静的初期化子が検査例外(11.2)で中途完了(14.1, 15.6)する可能性があると,コンパイル時エラーが発生する。静的初期化子が通常完了(14.20)できないなら,コンパイル時エラーが発生する。

静的初期化子及びクラス変数初期化子は,ソーステキストで出現した順に実行される。

ソーステキストの後方に宣言が出現するクラスで宣言されるクラス変数は,それが有効範囲内にあるとしても,その使用に制限を受けることがある。クラス変数に対する前方参照の規則については,8.3.2.3を参照。

静的初期化子内の任意の場所でreturn(14.16)が出現すれば,コンパイル時エラーが発生する。

キーワードthis (§15.8.3)又はキーワードsuper (15.11, 15.12)が静的初期化子内の任意の場所で出現すれば,コンパイル時エラーが発生する。

8.8 コンストラクタの宣言

コンストラクタ(constructor)は,クラスのインスタンスとしてのオブジェクトを生成する際に使用される。

ConstructorDeclarator内のSimpleTypeNameは,コンストラクタ宣言を含むクラスの単純名でなければならない。そうでなければ,コンパイル時エラーが発生する。この点を除けば,コンストラクタ宣言は,結果型をもたないメソッド宣言と同様とする。

次に簡単な例を示す。

class Point {
	int x, y;
	Point(int x, int y) { this.x = x; this.y = y; }
}
コンストラクタは,クラスインスタンス生成式(15.9),文字列連結演算子 + (15.18.1)による変換及び連結,並びに他のコンストラクタからの明示的なコンストラクタ呼出し(8.8.5)によって呼び出される。コンストラクタは,決してメソッド呼出し式(15.12)で呼び出されない。

コンストラクタへのアクセスは,アクセス修飾子(6.6)によって支配される。

これは,たとえば,アクセス不可能なコンストラクタを宣言することによってインスタンス化を防ぐ場合(8.8.8)に有用とする。 コンストラクタ宣言は,決して継承されず,したがって,隠ぺい又は上書きの対象にはならない。

8.8.1 形式仮引数

コンストラクタの形式仮引数は,構造及び振舞いに関して,メソッドの形式仮引数(8.4.1)と同一とする。

8.8.2 コンストラクタのシグネチャ

コンストラクタのシグネチャ(signature)は,そのコンストラクタへの形式仮引数の個数と型からなる。同じシグネチャの二つのコンストラクタを宣言してはならない。そうでなければ,コンパイル時エラーとなる。

8.8.3 コンストラクタ修飾子

アクセス修飾子public, protected及びprivateは,6.6で規定する。コンストラクタ宣言内で,同じ修飾子が複数回出現する,又はコンストラクタ宣言がアクセス修飾子public, protected及びprivateを複数回もつならば,コンパイル時エラーが発生する。

メソッドとは異なり,コンストラクタは,abstract, static, final, native, strictfp又はsynchronized宣言できない。コンストラクタは,継承されないので,final宣言する必要はなく,abstractコンストラクタ宣言は,決して実装できない。コンストラクタは,常にオブジェクトに関して呼び出されるので,コンストラクタをstatic宣言することは,意味がない。コンストラクタをsynchronized宣言する必要性も実際にはない。そのオブジェクトに対するすべてのコンストラクタが処理を完了するまで,他のスレッドに対して使用可能とはならず,生成中のオブジェクトは,必然的にロック設定されるからとする。native宣言ができないのは,オブジェクト生成中に,上位クラスのコンストラクタが常に適切に呼び出されることを,Java仮想計算機の実装が簡単に検証できるようにするための言語設計の任意な選択とする。

ConstructorModifierstrictfpと宣言できないことに注意。ConstructorModifierMethodModifier (8.4.3)との定義における相違は,意図的な言語設計上の選択とする。これは,コンストラクタがFP厳密(15.4)である必要十分条件がそのクラスがFP厳密となることを実効的に保証する。

8.8.4 コンストラクタのthrows節

コンストラクタの throws節は,構造及び振舞いに関して,メソッドに対するthrows(8.4.4)と同一とする。

8.8.5 コンストラクタ本体

コンストラクタ本体の最初の文は,同じクラス又は直接的上位クラス(8.8.5.1)の他のコンストラクタの明示的呼出しであってよい。

コンストラクタが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は,新しいクラスインスタンスの生成及び初期化を記述する。

8.8.5.1 明示的コンストラクタ呼出し

明示的コンストラクタ呼出し文は,次の2種類とする。

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);
}
この場合,コンパイル時エラーが発生する。その理由は,上位クラスのコンストラクタ呼出しでは,インスタンス変数が使用できないからである。

匿名クラスインスタンス生成式が,明示的コンストラクタ呼出し文の内部で出現するならば,その匿名クラスは,そのコンストラクタが呼び出されているクラスの取り囲みインスタンスを参照してはならない。

例を次に示す。

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
		}
	}
}
C をインスタンス化されているクラスとする。SC の直接的上位クラスとする。i を生成されているインスタンスとする。明示的コンストラクタ呼出しの評価は次とする。

8.8.6 コンストラクタのオーバロード

コンストラクタのオーバロードは,振舞いに関してメソッドのオーバロードと同一とする。オーバロードは,コンパイル時に各クラスインスタンス生成式(15.9)で解決される。

8.8.7 デフォルトコンストラクタ

クラスがコンストラクタ宣言を含んでいなければ,実引数を取らないデフォルトコンストラクタ(default constructor)が自動的に提供される。

コンパイラがデフォルトコンストラクタを提供しているが,上位クラスが実引数をもたないコンストラクタをもっていなければ,コンパイル時エラーが発生する。

デフォルトコンストラクタは,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とし,InnerOuterに関してprotectedとする。したがって,Innerは,Outerの下位クラスなので, SonOfOuterにおいてアクセス可能とする。Innerのコンストラクタは,クラスSonOfOuterInnerの下位クラスではないので,SonOfOuterにおいてアクセス不能とする。したがって,Innerはアクセス可能にもかかわらず,そのデフォルトコンストラクタは,アクセス不能とする。

8.8.8 クラスのインスタンス化の抑制

少なくとも一つのコンストラクタを宣言して暗黙のコンストラクタの生成を抑制し,さらにすべてのコンストラクタを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版