■オブジェクト指向プログラミング■

継承

あるクラスを土台にして,何か拡張を施し,新しいクラスを作成することができる.これを継承と呼ぶ.例えば次のような例である.

class superClass
{
	public int abc;
	public int i=100;

	public void test()
	{
		System.out.println("superClass:"+i);
	}

	public void superFunc()
	{
		
	}
}

class subClass extends superClass
{
	public  String i="text";

	public void subFunc()
	{
		
	}

	public void test()
	{
		System.out.println("subClass:"+i);
	}
}

ここではsuperClassが土台となるクラスで,subClassが拡張されたクラスである.subClassの宣言のところで,extends指定により,土台となるsuperClassを指定している.サブクラスは,スーパークラスのすべての要素を受け継いでいる(継承している).例えばサブクラスでは,自分で定義しているsubFunc()の他に,スーパークラスで定義されているsuperFunc()も所有している状態となる.つまり,次のようにmainなどから使用できる.

public class Hello {
  public static void main(String[] args) {
    subClass s1 = new subClass(); //サブクラスとしてs1を生成
    s1.subFunc(); //サブクラスがもともと持っているメソッドはもちろん
    s1.superFunc(); //スーパークラスが持っているメソッドも使用可能
  }
}

オブジェクト指向プログラムでは,この機能を利用して,最小限の労力でプログラムを行うことが期待されている.例えば,画像クラスでは,画像の基本部分を土台のスーパークラスとして用意し,色空間別の画像オブジェクトを用意できれば,色空間に依存した部分だけを書けばよくなる.

class ImageBase //スーパークラス
{
  画像としての基本部分
}

class RGBImage extends ImageBase //RGBクラス
{
  RGBに関わる部分だけ
}

class HSBImage extends ImageBase //HSBクラス
{
  HSBに関わる部分だけ
}

必要に応じて,サブクラスのサブクラスも作成することができる.様々なプログラム目的により,それに適したクラスの階層を設計することが,オブジェクト指向プログラムの主問題になる.実際,どのようにスーパークラスとサブクラスを設計すべきかは,ソフトウェア工学の分野でいろいろ議論されており,種々な技法が編みだされている.しかしここでは,Javaの入門編として,そこまでは立ち入らないこととする.

サブクラスのオブジェクトとスーパークラスのオブジェクトの代入

では,このサブクラスとスーパークラスをmainの中などの第三者が宣言した場合,次のように使用することができる.

public class Hello {
  public static void main(String[] args) {
    subClass s1 = new subClass();
    superClass s2 = new superClass();

    superClass s3 = s1; //サブクラスのオブジェクトを代入可能
    s3.superFunc(); //呼出OK
    s3.subFunc();   //こっちはエラー
 }
}

サブクラスs1とスーパークラスs2は,そのまま宣言したものである.ポイントはスーパークラスの変数には,サブクラスのオブジェクトをそのまま代入可能である点である.これは,いってみれば少なくともサブクラスのオブジェクトは必ずスーパークラスの全要素を継承しているため,スーパークラスだと思って使用されても問題がないのである.サブクラスの要素が無駄に含まれているスーパークラスのオブジェクトと捉えてもよい.

ただし,Javaのコンパイラからすればs3はあくまでsuperClassのオブジェクトに見えるため,s1のオブジェクトが本来もっていたはずのsubFunc()メソッドは呼び出すことができない.(もちろんsuperClassのsuperFunc()は呼び出せる.)

subClass s4 = s2; //逆はエラー

逆に,スーパークラスのオブジェクトは,サブクラスの要素を含んでいないので,逆の代入はエラーとなる.

メンバ変数とメソッドのオーバーライド

では,サブクラスとスーパークラスで,もしも同じ名前があったらどうなるのであろうか.最初の例ではメンバ変数iと,メソッドtest()が両方に存在している.これは,次のように試してみよう.

public class Hello {
 public static void main(String[] args) {
  subClass s1 = new subClass();
  s1.test();
  superClass s2 = new superClass();
  s2.test();
  superClass s3 = s1;  //スーパークラスに代入してから
  s3.test();           //呼び出してみる
  s3.subFunc();        //でもこれはエラー
 }
}

この実行結果は次のとおり.

subClass:text
superClass:100
subClass:text

これを見ればわかる通り,サブクラスが継承したときにスーパークラスにある同名の要素は,すべてサブクラスのものに置き換えられてしまう(オーバーライドされる).逆から見れば,スーパークラスの要素が隠されてしまうことになるため,このことを隠蔽と呼ぶ.最後のs3の例のように,スーパークラスの変数に代入されてからtest()を呼び出しても,スーパークラスではなく,オブジェクトの中に保持しているメソッドの方が呼び出されるため,サブクラスのtest()メソッドが実行されている.

これは,先ほど説明したsubFunc()を呼び出せないことと混乱するかもしれない.test()はスーパークラスにも存在するため,Javaコンパイラはtest()の呼出を認めてくれるが,subFunc()はスーパークラスにないために,コンパイル時点でエラーにされてしまっている.しかし,(コンパイラがスーパークラスのメソッドだと誤解?してくれた)test()の呼出しは,実はサブクラスのtest()にオーバーライドされてしまっているため,実際にはサブクラスの方のtest()が実行されるわけである.

オーバーロード

オーバーライドと似ているため,よく間違えられる言葉としてオーバーロードがある.これは,クラスの継承とは別の問題で,次のように,メソッドの選択を行うための機能を指す.

class Image
{
  public void setData(int x, int y)
  {
    setData(x,y,0); //これは下の方のsetDataを呼び出す
  }
  public void setData(int x, int y, int data)
  {
    (x,y)にdataをセット
  }
}

このように,同じ名前のメソッドがある場合(ただし引数の種類や数などが違う),次のようにmainなどから呼び出す際に,引数の数や種類によってどちらかを自動的に選択して呼び出すことをメソッドのオーバーロード機能と呼ぶ.

public class Hello {
 public static void main(String[] args) {
  Image im = new Image();

  im.setData(100,100,10); //下の方を呼出
  im.setData(64,64); //上の方を呼出
 }
}

もちろん,名前だけでなく,引数の種類と数までいっしょのメソッドは自動的に区別が付けられないため,宣言することができない.

publicとprotectedとprivate

最後に,このメンバ変数やメソッドのアクセスを制御するキーワードを説明する.スーパークラスのメンバ変数やメソッドなどは,すべてサブクラスに継承されると説明した.しかしながら,プログラミングの都合上,あえてサブクラスのプログラムからこれらの要素をアクセスできないように制限したい要望もある.

前に例示した画像クラスでは次のようなメンバ変数があった.

public class Image
{
  int width; //幅
  int height; //高さ
  int [][]data; //幅×高さの大きさを持たないといけない二次元配列データ
} 

dataは,もしも幅と高さが変更されたら,ちゃんとそれに合わせてnewしなおす必要があった.仮りに,このImageクラスを継承して,RGBクラスを作成するとしよう.その時,もしもこのdataとwidth,heightの関係に気づかなければ,単純にwidthやheightを直接書き換えてしまい,dataを変更し忘れてしまうかもしれない.

これを避けるために,継承したサブクラス側から,スーパークラス側のメンバ変数やメソッドに直接アクセスできないように制限することができる.

public class Image
{
  private int width; //幅
  private int height; //高さ
  private int [][]data; //幅×高さの大きさを持たないといけない二次元配列データ
  public void setWidth()
  {
     widthの変更だけでなく
     dataもちゃんと変更
  }
 public void setHeight()
  {
     heightの変更だけでなく
     dataもちゃんと変更
  }
}

private宣言されたメンバ変数,メソッドは,サブクラスから参照できない.その代わり,public宣言してどこからでも参照できるようなアクセスメソッドを提供することになる.mainなどからは次のように使用することになる.

public class Hello {
 public static void main(String[] args) {
   Image im = new Image();

   im.width=640;  //これはエラー,privateなので
   im.setWidth(640); //こちらが正解
	}
}

実は,privateはあまり使用しない.というものjavaのクラスでは,デフォルトで(publicなどと明示しない限り)privateとして扱われるためである.

publicとprivateの他に,その中間のprotectedがある.これは,mainなどの完全な第三者からはアクセスできないものの,サブクラスからのアクセスを認めるためのものである.

public class Hello {
 public static void main(String[] args) {
  Image im = new Image();
  im.width=640;  //これはエラー,protectedなので
	}
}

class Image
{
  protected int width;
}

class RGBImage extends Image //サブクラス
{
   この中ではwidth=640;など直接アクセス可能
}