クラス階層の中でそれ自身に属するオブジェクトが存在せず,他のクラスの基本クラスとしてのみ定義されているクラスのことを抽象クラス(abstract class)といいます.
よいオブジェクト指向プログラムはクラスの階層を含み,階層関係は下の樹形図のような形で表されます.
この樹形図の葉の部分のクラス(例えば,Fish, Owl, Dog)は,それらのクラスの行動を実装するための特定なメソッドを含んでいます(例えば,Fish.swim( ), Owl.fly( ), Dog.run( )).ところが,メソッドには全ての派生クラスでも共通なものもあります(例えば,Vertebrate.eat( ), Mammal.suckle( ), Primate.peel( )).これらのメソッドは基本クラスで抽象メソッドとして宣言します.抽象メソッドにするには,abstractキーワードを用います.
クラス階層のそれぞれのクラスは,1つでも抽象メソッドを含んでいれば,抽象クラスに,それ以外の場合は,具象クラスに分けられます.抽象クラス(abstract class)は1つ以上の抽象メソッドを含んでいるクラスで,具象派生クラス(concrete derived class)は1つも抽象メソッドを含まないクラスです.上の例では,Vertebrateクラスは抽象クラスでFishクラスは具象派生クラスです. 抽象クラスのオブジェクトは宣言できません.
抽象クラスは他のクラスの基本クラスとなって,その派生クラスのインターフェイスや基本的なデータ構造を定義するためにだけ存在し,抽象クラス自身の型のオブジェクトを宣言することは誤りです.
ある派生クラスの基本クラスが抽象クラスである場合,その派生クラスの定義の中で基本クラスの抽象メソッドを全て再定義する必要があります.上の例で,Vertebrate.eat( ), Mammal.suckle( ),とPrimate.peel( )の3つのメソッドだけが抽象メソッドだとすると,抽象クラス(ABC)はVertebrate, Mammal,とPrimateで,残りの15クラスは具象派生クラス(CDC)となります.そして,これらの15の派生クラスでは,それ自身のeat( )関数の実装をし,Mammalの11個の具象派生クラスはそれ自身のsuckle( )関数の実装をし,2つのPrimateクラスの派生クラスではpeel( )関数の実装をする必要があります.
抽象基本クラスは,概してクラス階層の構築の最初の段階で定義されます.そして,抽象基本クラスをもとに,派生クラスの骨組みができあがります.そのとき,抽象メソッドは階層におけるある種の均一性を規定します.
abstract class Shape
{
abstract void display();
}
class Point3D
{
double x,y,z;
public Point3D(double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
}
}
class Circle extends Shape
{
Point3D center, p;
public Circle(Point3D center, Point3D p)
{
this.center = center;
this.p = p;
}
public void display()
{
System.out.println("Circle");
}
}
class AbstractClass
{
public static void main(String[] args)
{
Circle c = new Circle(new Point3D(0,0,0), new Point3D(1,0,0));
c.display();
}
}
実行結果
インターフェイス(Interface)
Javaは言語構造の簡潔さを指向しているため,同時に二つ以上の基本クラスを持つ多重継承を記述することができません.このため,抽象メソッドは特定の基本クラスの内部に含ませる形でしか利用できません.また,継承を重ねた間接継承で対応することもできますが,それはプログラムを分かり難くさせます.これを解決するためいにJavaは,インターフェイスという機能を用意しました.インターフェイスを用いると,多重継承に近い処理が可能となります.
インターフェイスは,クラスメンバを操作する手順としてのメソッドとstatic変数がたくさん集まったものです.インターフェイスの記述は,abstractとclassの代わりにinterfaceキーワードを用いて行います.
interface 名前
{
型 フィールド名 = 初期値;
型 メソッド名(引数);
}
この形式にはabstractキーワードがありませんが,インターフェイス内では,フィールドはpublic static final,メソッドはabstract publicで解釈されることになっています. インターフェイスを用いるときに注意するとことは,以下の通りです.
interface Shape2D
{
double getArea();
}
interface Shape3D
{
double getVolume();
}
インターフェイスの実装
インターフェイスは単独でクラスに実装することもできますし,複数のインターフェイスを実装することもできます.また,クラスの継承とインターフェイスの実装を同時に指定できます.この場合も,直属の基本クラスはただ一つしか持つことができません.
\noindent interface Shape2D
{
double getArea();
}
abstract class Shape
{
abstract void display();
}
class Point3D
{
double x,y,z;
public Point3D(double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
}
}
class Circle extends Shape implements Shape2D
{
Point3D center, p;
public Circle(Point3D center, Point3D p)
{
this.center = center;
this.p = p;
}
public void display()
{
System.out.println("Circle");
}
public double getArea()
{
double dx = center.x - p.x;
double dy = center.y - p.y;
double d = dx*dx + dy*dy;
double radius = Math.sqrt(d);
return Math.PI*radius*radius;
}
}
class Shapes
{
public static void main(String[] args)
{
Circle c = new Circle(new Point3D(0,0,0), new Point3D(1,0,0));
c.display();
System.out.println("面積は" + c.getArea());
}
}
実行結果
インターフェイスの参照
インターフェイスの名前を変数の型として指定することができます.この変数は,そのインターフェイスを実装したオブジェクトを参照するために用いることが可能です.そのインターフェイスを実装していないオブジェクトを変数に代入すると,コンパイルエラーになります.
\noindent interface Shape2D
{ double getArea(); } abstract class Shape { abstract void display(); } class Point3D { double x,y,z; public Point3D(double x, double y, double z) { this.x = x; this.y = y; this.z = z; } } class Circle extends Shape implements Shape2D { Point3D center, p; public Circle(Point3D center, Point3D p) { this.center = center; this.p = p; } public void display() { System.out.println("Circle"); } public double getArea() { double dx = center.x - p.x; double dy = center.y - p.y; double d = dx*dx + dy*dy; double radius = Math.sqrt(d); return Math.PI*radius*radius; } } class InterfaceDec { public static void main(String[] args) { Circle c = new Circle(new Point3D(0,0,0), new Point3D(1,0,0)); c.display(); Shape sh; sh = c; System.out.println("面積は" + sh.getArea()); } } |
実行結果
instanceof演算子
プログラムの都合上,オブジェクトのクラスや,オブジェクトに実装されているインターフェイスを確認しなければならないことがあります.例えば,
class SubClass implements Intf...
という定義があるとき,
SubClass dt = new SubClass();
if(dt instanceof Intf)
と書くと,if文の条件式は真になります.
パッケージ
Javaではコンパイル時にソースファイル上のクラスファイルが作成されます.したがって,異なるプログラムでも同じクラス名を用いていると,クラスファイル名が衝突して,期待した動作が得られなくなります.この問題に対処するためにJavaには,パッケージ(package)というファイル管理機能があります.パッケージを用いると関連のあるファイルを隔離して管理するので,名前の重複に対応できるようになります.つまり,自分で新たなディレクトリ(フォルダ)を作成し,プログラムごとに格納する代わりに,Javaでは言語機能として,新たなディレクトリを作成し,そこにクラスファイルを格納してくれるのです.
パッケージの指定方法は,ソースファイルの先頭に
package パッケージ名;
と書きます.コンパイルの方法は,
javac パッケージ名
ファイル名.java
となります.実行方法は,
java パッケージ名.ファイル名
となります.
複数のソースファイルを同じパッケージに入れる
複数のソースファイルを同じパッケージに入れるには,
複数のソースファイルを別々のパッケージに入れる 複数のソースファイルを別々のパッケージに入れるには,
importステートメント
同じパッケージ内のクラスやインターフェイスにアクセスするのは簡単です.その名前を直接指定するだけでアクセルできます.しかし,別のパッケージ内の型にアクセスする場合は,名前を直接指定できません.この問題を解決する1つの方法は,完全修飾名を使って型を指定することです.例えば,java.awt.eventはJavaの組み込みパッケージの1つです.このパッケージには,MouseEventクラスが定義されています.したがって,java.awt.event内のMouseEventクラスにアクセスするには,java.awt.event.MouseEventと記述します.
しかし,完全修飾名を頻繁に使うのは面倒です.そこで,Javaには「インポート」と呼ばれる機能が用意されています.インポート機能を用いるとパッケージ名を個別付与する必要がなくなります.インポート宣言は次のように行います.
import パッケージ名.クラス名;
これにより,コンパイラにクラスが格納されている場所を知らせます.