[go: up one dir, main page]
More Web Proxy on the site http://driver.im/

目次| |



8. クラス

クラス宣言(class declaration)は,新しい参照型を定義し,その実装方法を記述する(8.1)

クラス名の有効範囲(8.1.1)は,そのクラスが宣言されているパッケージ内のすべての型宣言とする。クラスは,abstract宣言(8.1.2.1)してよい。クラスが完全に実装されていない場合は,abstract宣言しなければならない。これらのクラスは,インスタンス化できないが,サブクラスで拡張できる。クラスをfinal 宣言 (8.1.2.2) してよい。この場合は,サブクラスをもつことができない。クラスをpublic宣言すれば,そのクラスは,他のパッケージから参照できる。

Object以外の各クラスは,既存のある一つのクラスの拡張(つまりサブクラス(8.1.3))とし,インタフェース(8.1.4) を実装してよい。

クラスの本体では,メンバ(フィールド及びメソッド),静的初期化子,及びコンストラクタ(8.1.5)を宣言する。メンバ名の有効範囲は,メンバが属するクラス宣言全体とする。フィールド,メソッド,及びコンストラクタの宣言は,アクセス修飾子(6.6) publicprotected,及び privateを含むことができる。クラスのメンバは,宣言されたメンバ及び継承されたメンバ(8.2)の両方を含む。新しく宣言したフィールドは,スーパクラス又はスーパインタフェースで宣言された(同名の)フィールドを隠ぺいできる。また,新しく宣言したメソッドは,スーパクラス又はスーパインタフェースで宣言されたメソッドを隠ぺい,実装又は上書きすることができる。

フィールド宣言 (8.3)は,一度だけ具体化されるクラス変数,及びクラスのインスタンスごとに新規に具体化されるインスタンス変数を記述する。フィールドは,final宣言(8.3.1.2)してよい。この場合には,そのフィールドは,その宣言の一部として以外は値を代入できない。すべてのフィールド宣言は,初期化子を記述してよい。finalフィールドの宣言は,初期化子を記述しなければならない。

メソッド宣言 (8.4)は,メソッド呼出し式(15.11)によって呼び出されるコードを記述する。クラスメソッドは,クラス型に関して呼び出される。インスタンスメソッドは,クラス型のインスタンスである特定のオブジェクトに関して呼び出される。実装方法が示されていないメソッドは,abstract宣言しなければならない。メソッドを final (8.4.3.3)宣言することもできる。この場合には,そのメソッドを隠ぺい又は上書きできない。メソッドは,プラットフォーム依存のnativeコード(8.4.3.4)で実装してよい。synchronized(8.4.3.5)宣言したメソッドは,synchronized文 (14.17)を使用したかのように,その本体を実行する前にオブジェクトを自動的にロックし,制御を戻す際にそのロックを自動的に解除する。このようにして,そのオブジェクトの動作を他のスレッド(17.)の動作と同期させることができる。

メソッド名は,オーバロード(8.4.7)してよい。

静的初期化子 (8.5)は,クラスが最初にロードされるときに,そのクラスの初期化(12.4)を支援するために使用される実行可能コードのブロックとする。

コンストラクタ(8.6)は,メソッドと似ているが,メソッド呼出しで直接呼び出すことはできない。コンストラクタは,新しいクラスインスタンスを初期化するために使用する。メソッドと同様に,コンストラクタは,オーバロード(8.6.6)してよい。

8.1 クラス宣言

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

完全限定名(6.7)P をもつ名前付きパッケージ(7.4.1)の中にクラスを宣言した場合,そのクラスは,完全限定名P.Identifierをもつ。 クラスが名前付きでないパッケージ(7.4.2)にある場合,そのクラスは,完全限定名Identifierをもつ。 次に例を示す。

class Point { int x, y; }

クラスPointは,package文がないコンパイル単位の中で宣言されている。したがって,Pointを,そのクラスの完全限定名とする。 これに対し,次の例を考える。


package vista;
class Point { int x, y; }

この例では,クラス Point の完全限定名は,vista.Pointとする。 (パッケージ名 vistaは,局所的又は個人的使用に適している。 そのパッケージを,広範囲に配布することを意図するならば,一意的なパッケージ名(7.7)を与えることが望ましい。)

クラスの名前 Identifier が同じパッケージ内で宣言されている他のクラス型又はインタフェース型の名前と同じならば(7.6),コンパイル時エラーが発生する。

クラスの名前 Identifier が,そのクラス宣言を含むコンパイル単位(7.3)内で,単一の型インポート宣言 (7.5.1)によって型としても宣言されているならば,コンパイル時エラーが発生する。

次に例を示す。


package test;
import java.util.Vector;

class Point {
        int x, y;
}

interface Point 
{	// compile-time error #1
        int getR();
        int getTheta();
}
class Vector { Point[] pts; } 	// compile-time error #2

最初のコンパイル時エラーは,同じパッケージ内で名前 Pointを,クラス及びインタフェースの両方に二重宣言していることによって発生する。 コンパイル時に検出される2番目のエラーは,名前 Vectorを,クラス型の宣言及び単一の型インポート宣言の両方で宣言しようとしていることとする。

しかしながら,クラスの名前 Identifier が,そのクラス宣言を含むコンパイル単位(7.3)内で,要求時の型インポート宣言 (7.5.2)によって別途インポートされるかもしれない型の名前と同じになっても,エラーとはならないことに注意すること。 次に例を示す。

package test;
import java.util.*;
class Vector { Point[] pts; } 	// not a compile-time error

クラス Vector の宣言は,クラス java.util.Vectorがあるにもかかわらず許される。 このコンパイル単位では,単純名 Vector は,クラス test.Vector を参照し, java.util.Vector を参照しない。 (この場合でも,完全限定名を使用すれば,そのコンパイル単位内のコードによって,java.util.Vector参照することはできる。)

8.1.1 クラス型名の有効範囲

クラス宣言における Identifier は,クラスの名前を指定する。このクラス名の有効範囲(6.3)は,そのクラスが宣言されているパッケージ全体とする。例として,次のコンパイル単位を考える。

package points;

class Point {
        int x, y;                            // coordinates
        PointColor color;                    // color of this point
        Point next;                          // next point with this color

        static int nPoints;
}

class PointColor {
        Point first;                         // first point with this color
        PointColor(int color) {
                this.color = color;
        }
        private int color;                   // color components
}

クラスメンバの宣言で,互いに使用し合う二つのクラスを定義している。クラス型名Point及び PointColor の有効範囲は,現在のコンパイル単位全体を含め,パッケージpoints全体となるので,この例は,正しくコンパイルされる。つまり,前方参照は,問題とはならない。

8.1.2 クラス修飾子

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

アクセス修飾子publicは,6.6で規定する。クラス宣言で同じ修飾子が2回以上出現すれば,コンパイル時エラーが発生する。クラス宣言でクラス修飾子が複数個出現する場合は,必須ではないが,慣習的に,ClassModifier の前述の生成規則内の記述と矛盾しない順序で出現することとする。

8.1.2.1 abstractクラス

abstract クラスは,不完全なクラス,又は不完全と考えられるクラスとする。abstractクラスだけが,abstractメソッド(8.4.3.19.4),つまり,宣言されているがまだ実装されていないメソッドをもってよい。abstract宣言されていないクラスがabstractメソッドを含んでいれば,コンパイル時エラーが発生する。次の条件のいずれかが真ならば,クラスは,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.8)を使用して,abstractクラスのインスタンスを生成しようとすると,コンパイル時エラーが発生する。クラスClass のメソッド newInstance(20.3.6)を使って,abstractクラスをインスタンス化しようとすると,InstantiationException(11.5.1.2)が投げられる。したがって,前述の例に次の文を続けると,コンパイル時エラーとなる。

        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.6.8),これを表現する適切な方法は,実引数なしのコンストラクタを宣言し,それに private を指定し,その呼出しを決して行わず,さらに他のコンストラクタを宣言しないこととする。 この形式のクラスは,通常,クラスメソッド及び変数を含む。 インスタンス化できないクラスの例としては,クラスjava.lang.Math がある。この宣言は,次のとおりとする。


public final class Math {

        private Math() { }                     // never instantiate this class
        . . . declarations of class variables and methods . . .
}

8.1.2.2 finalクラス

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

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

8.1.3 スーパクラス及びサブクラス

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


        Super:
extends ClassType

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

        ClassType:

                TypeName

ClassTypeは,アクセス可能な(6.6)クラス型の名前でなければならない。そうでなければ,コンパイル時エラーが発生する。 現在のパッケージに存在するすべてのクラスは,アクセス可能とする。 他のパッケージに存在するクラスは,ホストシステムがそのパッケージへのアクセスを許可(7.2)し,クラスが public 宣言されていれば,アクセス可能とする。 ClassTypeが,final(8.1.2.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; }

この例での関係は,次のとおり。

クラスがそれ自体のサブクラスになると宣言されている場合,コンパイル時エラーとなる。 例えば,次の例は,コンパイル時エラーを発生する。

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)インタフェース型の名前でなければならない。 そうでなければ,コンパイル時エラーが発生する。 現在のパッケージ内のすべてのインタフェースは,アクセス可能とする。 他のパッケージ内のインタフェースは,ホストシステムがそのパッケージへのアクセスを許可(7.4.4)し,そのインタフェースが public 宣言されていれば,アクセス可能とする。

一つの 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.2.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.4)を含んでよい。クラス本体はまた,静的初期化子(8.5)及びクラスのコンストラクタの宣言(8.6)を含んでよい。

クラス型で宣言された,又はクラス型によって継承されたメンバ名の有効範囲は,クラス型宣言の本体全体とする。

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 フィールドである xyuseCount ,及び 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 class OnePoint {
        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で規定する。 FieldDeclaration内の Identifier は,そのフィールドを参照する名前の中で使用してよい。 フィールド名の有効範囲(6.3)は,フィールドが宣言されているクラス宣言の本体全体とする。 複数の宣言子を記述することによって,一つのフィールド宣言の中に,複数のフィールドを宣言してよい。 FieldModifier及びTypeは,宣言内のすべての宣言子に適用される。 配列型を含む変数宣言は,10.2で規定する。

一つのクラス宣言の本体に,同じ名前をもつ二つのフィールドの宣言が含まれることは,コンパイル時エラーとする。 メソッド及びフィールドは,同じ名前をもってよい。 その理由は,それらは,異なった文脈で使用されること,及び異なる検索手順によってあいまいさが解消されるためとする(6.5)。

クラスがある特定の名前をもつフィールドを宣言した場合,そのクラスのスーパクラス及びスーパインタフェースの中の,それと同じ名前をもつ任意の及びすべてのアクセス可能なフィールドの宣言を隠ぺい(hide)(6.3.1)すると呼ぶ。

あるフィールド宣言が他のフィールド宣言を隠ぺいする場合,二つのフィールドが同じ型をもつ必要はない。

クラスは,その直接的スーパクラス及び直接的スーパインタフェースから,そのクラス内のコードでアクセス可能であり,そのクラス内の宣言で隠ぺいされていない,スーパクラス及びスーパインタフェースの中のすべてのフィールドを継承する。

クラスは,同じ名前をもつ複数のフィールドを継承できる(8.3.3.3)。 これだけでは,コンパイル時エラーにはならない。 しかしながら,クラス本体内で,それらのフィールドを単純名で参照すると,それらの参照は,あいまいであるために,コンパイル時エラーが発生する。

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

隠ぺいされたフィールドは,(フィールドが static ならば)限定名,又はキーワード super若しくはスーパクラス型へのキャストを含んでいるフィールドアクセス式(15.10)を使ってアクセス可能とする。 この詳細及び例については,15.10.2を参照のこと。

8.3.1 フィールド修飾子

アクセス修飾子 publicprotected,及び private は,6.6で規定する。 フィールド宣言に同じ修飾子が2回以上出現した場合,又はフィールド宣言がアクセス修飾子 publicprotected,及び private を2個以上含んでいる場合,コンパイル時エラーが発生する。 フィールド宣言内で,複数個の(異なる)フィールド修飾子が出現する場合,必須ではないが,習慣上,前述のFieldModifier の生成規則内の記述の順序と矛盾しないように出現するものとする。

8.3.1.1 staticフィールド

フィールドを static 宣言した場合,そのクラスのインスタンスがいくつ生成されても(まったく生成されなくてもよい),そのフィールドの実体は,一つだけ存在する。 static フィールドは,クラス変数(class variable)とも呼ばれ,クラスが初期化(12.4)されるときに,実体が生成される。

static 宣言されていないフィールド(non-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

これは,フィールド,xy,及び puseCount の変更は,q のフィールドには,影響しないことを示している。 その理由は,これらのフィールドは,異なるオブジェクト内のインスタンス変数だからである。 この例では,クラスPoint のクラス変数 origin を,Point.origin のように限定名としてクラス名を使用する方法,並びに p.origin 及び q.origin のようにフィールドアクセス式(15.10)内のクラス型の変数を使用する方法,の両方を用いて参照している。 クラス変数 origin へのアクセスの,これらの二つの方法は,同じオブジェクトにアクセスすることが,次に示す参照等値演算式(15.20.3)の値が true であることにより証明されている。

q.origin==Point.origin

また,次の増分式からも証明される。

p.origin.useCount++;

この結果,q.origin.useCount の値は, 1 となる。 その理由は,p.origin 及び q.origin が同じ変数を参照しているからである。

8.3.1.2 finalフィールド

フィールドは,final 宣言できる。この場合,宣言子に変数初期化子を含まなければならない。 そうでなければ,コンパイル時エラーが発生する。 クラス変数及びインスタンス変数(static フィールド及び non-static フィールド)の両方とも final 宣言してよい。

final フィールドへの値の代入は,コンパイル時エラーを生じる。 したがって,一度 final フィールドを初期化すると,それは,常に同じ値を保持する。 final フィールドがオブジェクトへの参照を保持する場合,そのオブジェクトの状態は,オブジェクトへの操作によって変化してよい。 しかし,フィールドは,常に同じオブジェクトを参照する。 配列は,オブジェクトであるため,この規則は,配列にも適用される。 つまり,final フィールドが配列への参照を保持している場合,その配列の要素は,配列への操作で変化してよい。 しかし,フィールドは,常に同じ配列を参照する。

フィールドを final 宣言することは,その値が変化しないことを示す有用な文書,プログラミングエラーを防ぐことを助ける,及びコンパイラが効率的なコードを生成することを簡単にするものとして役立つ。

次に例を示す。


class Point {
        int x, y;
        int useCount;
        Point(int x, int y) { this.x = x; this.y = y; }
        final static Point origin = new Point(0, 0);
}

クラス Point は,final クラス変数 origin を宣言している。 変数 origin は,座標を (0,0) とするクラス Point のインスタンスであるオブジェクトへの参照を保持している。 変数 Point.origin の値は,決して変化しないので,その初期化子によって生成された,同じオブジェクト Point を常に参照する。 しかし,このオブジェクト Point への操作は,その状態を変えるかもしれない。 例えば,その useCountを修正する,又は誤って そのx 若しくは y 座標を修正する。

8.3.1.3 transientフィールド

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


class Point {
        int x, y;
        transient float rho, theta;
}

クラス Point のインスタンスが,システムサービスによって永続的記憶域に保存される場合,フィールド x 及び y だけが保存される。本言語規定では,このサービスの詳細は,規定しない。これは,将来の版で提供する予定とする。

8.3.1.4 volatileフィールド

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

Javaは,ある目的に対して,より便利に利用できる第2の機構を提供する。つまり,フィールドをvolatile宣言できる。 この場合,スレッドは,変数にアクセスするたびに,フィールドの作業コピーをマスタコピーに一致させなければならない。 さらに,スレッドの動作による,一つ以上の volatile 変数のマスタコピーへの操作は,主メモリによって,スレッドの要求した順序と同じ順序で実行する。

次の例において,あるスレッドがメソッド one を繰り返し呼び出し (ただし,呼出し回数は,全部を合わせても Integer.MAX_VALUE(20.7.2)回を超えないものとする),さらに,もう一つのスレッドが,メソッド 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.5)することとする。


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.25)されることを意味する。 さらに,次が成立する。

例:


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.3)でも使用される。 その場合,ローカル変数宣言文が実行されるたびに,初期化子が評価され,代入が実行される。

クラス(又はインタフェース)のフィールド用の変数初期化子の評価が,検査例外(11.2)によって中途完了する可能性がある場合,コンパイル時エラーが発生する。

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

クラス変数に対する初期化式が,そのクラス変数の単純名又は同じクラス内でそのクラス変数の右側(つまりソーステキストで後)に宣言が出現する他のクラス変数の単純名の使用を含む場合,コンパイル時エラーが発生する。次に例を示す。


class Test {
        static float f = j;                  // compile-time error: forward reference
        static int j = 1;
        static int k = k+1;                  // compile-time error: forward reference
}

この例は,二つのコンパイル時エラーを引き起こす。その理由は,j が宣言される前にf の初期化の中で j が参照されているため,及び k の初期化が k 自身を参照しているためである。

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

キーワードthis(15.7.2)又はsuper(15.10.215.11)がクラス変数に対する初期化式内に出現する場合,コンパイル時エラーが発生する。

(ここで,実行時には,final宣言されていてコンパイル時の定数値で初期化されているstatic変数が,最初に初期化されることに注意すること。 これは,インタフェース内の同様のフィールド(9.3.1)にも適用される。 これらの変数は,たとえ悪意のあるプログラムによっても,デフォルト初期値(4.5.4)を決して見ることができない“定数”とする。 詳細な規定については,12.4.2及び13.4.8を参照のこと。)

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

インスタンス変数に対する初期化式が,そのインスタンス変数の単純名,又は同じクラス内でそのインスタンス変数の右側(つまりソーステキストで後)に宣言が出現する他のインスタンス変数の単純名の使用を含む場合, コンパイル時エラーが発生する。次に例を示す。


class Test {
        float f = j;
        int j = 1;
        int k = k+1;
}

この例は,二つのコンパイル時エラーを引き起こす。 その理由は,j が宣言される前に f の初期化の中で j が参照されているため,及び k の初期化が k 自身を参照しているためである。

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


class Test {
        float f = j;
        static int j = 1;
}

この例は,エラーなしにコンパイルできる。 クラス Testが初期化されるときに,j を 1 に初期化し,クラス Test のインスタンスが生成されるたびに,fj の現在の値に初期化する。

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

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 は,double値,つまりクラス 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.10.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       . . .
}

フィールド REDGREEN,及び 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 に存在する生成規則だが,明確化のためにここに再度記述する。

        VariableDeclaratorId:

                Identifier
                VariableDeclaratorId [ ]

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

二つの形式仮引数が同じ名前をもつように宣言された(つまり,それらの宣言に同じIdentifierを指定した)場合,コンパイル時エラーが発生する。

メソッドが呼び出された (15.11)とき,メソッド本体の実行前に,宣言されたTypeで新しく生成された仮引数の変数を,実引数の式の値で初期化する。 Declaratorld に出現する Identifier は,その形式仮引数を参照するために,メソッド本体内で,単純名として使用してよい。

形式仮引数名の有効範囲は,メソッドの本体全体とする。 これらの仮引数名は,メソッド内でローカル変数又は例外仮引数として再宣言してはならない。 つまり,仮引数の名前の隠ぺいは,許されない。

形式仮引数は,単純名でだけ参照され,限定名 (6.6)で参照されることはない。

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 メソッド修飾子

        MethodModifiers:

                MethodModifier
                MethodModifiers MethodModifier

        MethodModifier: one of

                public protected private

        abstract static final synchronized native

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

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

8.4.3.1 abstractメソッド

abstractメソッドの宣言は,メソッドをメンバとして導入し,そのシグネチャ(名前並びに仮引数の個数及び型),返却値の型,及び(もしあれば)throws 節を提供するが,実装は,提供しない。 abstract メソッド m の宣言は,abstractクラス内(このクラスを A とする)に出現しなければならない。 そうでなければ,コンパイル時エラーが発生する。 abstract宣言されていない A のすべてのサブクラスは,m に対する実装を提供しなければならない。 そうでなければ,コンパイル時エラーが発生する。 より正確には,abstractクラス A のすべてのサブクラス C に対して,Cabstract 宣言されていないならば,次のすべてを真とするクラス B が存在しなければならない。

このようなクラス B が存在しない場合,コンパイル時エラーが発生する。

privateメソッドを abstract宣言することは,コンパイル時エラーとする。 privateメソッドは,サブクラスから参照できないので,サブクラスは,private abstract 宣言されたメソッドを実装できない。 したがって,このようなメソッドは,決して使用できない。

staticメソッドを abstract 宣言することは,コンパイル時エラーとする。

finalメソッドを abstract 宣言することは,コンパイル時エラーとする。

abstractクラスは,他のabstractメソッドの宣言を提供することによって,abstractメソッドを上書きできる。 これによって,文書化用注釈(18.)を記述する場所,又は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(20.1.2)を上書きしている。(クラス 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) と呼ぶ。 これは,非静的メソッドと呼ぶこともある。 インスタンスメソッドは,常にあるオブジェクトに関して呼び出され,そのオブジェクトが,メソッド本体の実行中にキーワードthis及びキーワードsuperで参照される,現在のオブジェクトとなる。

8.4.3.3 finalメソッド

メソッドは,サブクラスがそれを上書きしたり又は隠ぺいしたりできなくするために,final宣言することができる。 finalメソッドを上書き又は隠ぺいしようとすることは,コンパイル時エラーとする。

privateメソッド及びfinalクラス(8.1.2.2)の中で宣言されたすべてのメソッドは,上書きできないため,暗黙的にfinalとする。 それらのメソッド宣言に,冗長なキーワードfinalを含めることは,許されているが,必須ではない。

finalメソッドを,abstract宣言することは,コンパイル時エラーとする。

実行時に,機械語生成器又は最適化器は,容易及び安全にfinalメソッドの本体を“インライン化”できる。 つまり,メソッドの呼び出しを,その本体内のコードで置き換えることができる。次に例を示す。


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];
                        pi.x += i;
                        pi.y += p.length-1-i;
                }

このループは,さらに最適化されるかもしれない。

これらのインライン化は,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 synchronizedメソッド

synchronizedメソッドは,実行前にロック(17.1)を取得する。 クラス (static) メソッドに対しては,そのメソッドのクラスに対するClass(20.3)オブジェクトと関連したロックを使用する。 インスタンスメソッドに対しては,this(そのメソッドが呼び出されたオブジェクト)と関連したロックを使用する。 これらのロックは,synchronized文 (14.17)で使用できるものと同じロックとする。次のコードを考える。


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(20.22)又はそのサブクラスでなければ,コンパイル時エラーが発生する。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.15)も含んではならない。

メソッドが返却値の型をもつように宣言されていれば,その本体内のすべてのreturn文 (14.15) は,式(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)されていないスーパクラス及びスーパインタフェースのすべてのメソッドを(abstractであってもなくても)継承(inherits) する。

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

クラスがインスタンスメソッドを宣言している場合,そのメソッド宣言は,そのクラスのスーパクラス及びスーパインタフェース内で同じシグネチャをもつ任意及びすべてのメソッドを上書き(override)すると呼ぶ。 上書きされなければ,そのスーパクラス及びスーパインタフェースのメソッドは,クラス内のコードからアクセス可能とする。 また,クラスで宣言されたメソッドがabstractでなければ,そのメソッドの宣言は,クラスのスーパクラス及びスーパインタフェースで同じシグネチャをもつ任意及びすべてのabstract宣言のメソッドを 実装(implement) すると呼ぶ。 実装されなければ,そのスーパクラス及びスーパインタフェースのメソッドは,クラス内のコードからアクセス可能とする。

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

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

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

クラスが static メソッドを宣言している場合,このメソッドの宣言は,そのクラスのスーパクラス及びスーパインタフェース内で同じシグネチャをもつ任意及びすべてのメソッドを隠ぺい(hide)すると呼ぶ。 隠ぺいされなければ,そのスーパクラス及びスーパインタフェースのメソッドは,クラス内でアクセス可能とする。 static メソッドがインスタンスメソッドを隠ぺいすると,コンパイル時エラーが発生する。 この点で,メソッドの隠ぺいは,フィールドの隠ぺい(8.3)とは異なる。 その理由は,static 変数が,インスタンス変数を隠ぺいすることは,許されていることによる。

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

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

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

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

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

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

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

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

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

8.4.7 オーバロード

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

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

メソッドが呼び出される (15.11)とき,呼び出されるメソッドのシグネチャを決定(15.11.2)するために,コンパイル時に,実引数の数及び実引数のコンパイル時の型が使用される。 呼び出すべきメソッドがインスタンスメソッドであれば,呼び出される実際のメソッドは,動的メソッド検索を使用して実行時に決定(15.11.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.11で規定されるオーバロード解決手順によってコンパイル時に決定する。

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 は,クラス Bufferからメソッド 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 静的初期化子

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

静的初期化子が検査例外(11.2)で中途完了(14.115.5)する可能性があると,コンパイル時エラーが発生する。

静的初期化子及びクラス変数初期化子は,ソーステキストで出現した順に実行され,ソーステキストの後方に宣言が出現するクラスで宣言されるクラス変数は,それが有効範囲内にあるとしても参照できない。この制限は,コンパイル時に循環的な又はそうでなければ不正な初期化を捕捉するために設計されている。次に例を示す。


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 がその変数初期化子で初期化される前に j の値にアクセスしているからである。変数 jは,この時点までは,デフォルト値 (4.5.4)をもっている。

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

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

8.6 コンストラクタの宣言

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

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

次に簡単な例を示す。


class Point {
        int x, y;
        Point(int x, int y) { this.x = x; this.y = y; }
}

コンストラクタは,クラスインスタンス生成式 (15.8),クラスClass(20.3)のメソッド newInstance,文字列連結演算子 + (15.17.1)による変換及び連結,並びに他のコンストラクタからの明示的なコンストラクタ呼出し(8.6.5)によって呼び出される。コンストラクタは,決してメソッド呼出し式(15.11)で呼び出すことはできない。

コンストラクタへのアクセスは,アクセス修飾子 (6.6) によって支配される。これは,たとえば,アクセス不可能なコンストラクタを宣言することによってインスタンス化を防ぐ場合(8.6.8)に有用とする。

コンストラクタ宣言は,メンバではない。コンストラクタ宣言は,決して継承されず,したがって,隠ぺい又は上書きの対象にはならない。

8.6.1 形式仮引数

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

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

コンストラクタのシグネチャは,構造及び振舞いに関して,メソッドのシグネチャ(8.4.2)と同一とする。

8.6.3 コンストラクタ修飾子

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

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

8.6.4 コンストラクタのthrows

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

8.6.5 コンストラクタ本体

コンストラクタ本体の最初の文は,後ろに括弧付き実引数の並びが続くthisが記述された同じクラスの他のコンストラクタの明示的呼出しであるか,又は後ろに括弧付き実引数の並びが続くsuperが記述された直接的スーパクラスのコンストラクタの明示的呼出しであってよい。

コンストラクタがthis を含む一つ以上の明示的コンストラクタ呼出しの列を介して,直接的又は間接的に自分自体を呼び出すことは,コンパイル時エラーとする。

コンストラクタ本体が明示的コンストラクタ呼出しで開始されず,宣言しているコンストラクタがルートクラスであるクラス Object の一部でなければ,コンストラクタ本体は,コンパイラにより暗黙に,スーパクラスのコンストラクタ呼出し“super();”で開始すると仮定される。ただし,その直接的スーパクラスに存在する実引数を取らないコンストラクタ呼出しとする。

明示的なコンストラクタ呼出しの可能性以外は,コンストラクタ本体は,メソッド本体(8.4.5)と類似する。return(14.15)が式を含まなければ,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 のコンストラクタを座標を渡して呼び出す。

明示的なコンストラクタ呼出し文は,このクラス又は任意のスーパクラスの中で宣言された,いかなるインスタンス変数又はインスタンスメソッドも参照できないし,式の中では, this 又は super を使用することもできない。そうでなければ,コンパイル時エラーが発生する。たとえば,前述の例のColoredPointの最初のコンストラクタを次のように変更したとする。


        ColoredPoint(int x, int y) {
                this(x, y, color);
        }

この場合,コンパイル時エラーが発生する。その理由は,スーパクラスのコンストラクタ呼出しでは,インスタンス変数が使用できないからである。

直接的スーパクラスのコンストラクタの呼出しは,それが実際は,明示的なコンストラクタ呼出し文として出現しても,又は自動的に提供された(8.6.7)としても,コンストラクタから正常に制御が戻った後で,付加的な暗黙の動作を実行する。つまり,初期化子をもつすべてのインスタンス変数が,そのときに,クラス宣言のソーステキストに出現する順で初期化される。キーワードthisを使用した同じクラス内の他のコンストラクタの呼出しは,この付加的な暗黙的動作を実行しない。

新しいクラスインスタンスの生成及び初期化を,12.5に規定する。

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

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

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

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

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

クラスが public 宣言されていれば,デフォルトコンストラクタは,暗黙にアクセス修飾子public(6.6)を与えられる。そうでなければ,デフォルトコンストラクタは,アクセス修飾子が示されないデフォルトアクセスをもつ。次に例を示す。


public class Point {
        int x, y;
}

これは,次の宣言と等価とする。


public class Point {
        int x, y;
        public Point() { super(); }
}

ここで,クラス Point は,publicなので,デフォルトコンストラクタは, public となる。

8.6.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内でだけインスタンス化できる。


目次| |