8章 継承
1.継承とは
継承とは、あるクラス(A_Class)があるクラス(B_Class)の一種である場合、A_ClassはB_Classを継承しているという。そして、A_ClassはB_Classの子クラス・サブクラス・派生クラスといい、逆にB_ClassはA_Classの親クラス・スーパークラス・基底クラスという。 これをJavaで定義すると以下のようになる。
class B_Class{ : : } class A_Class extends B Class : : }
「一種である」というのは、下記の図形の例のように「楕円」や「線」や「多角形」は「図形」の一種であり、「円」は「楕円」の一種。「直線」は「線」の一種であるし、「三角形」や「四角形」は「多角形」の一種であるということである。
つまり、「三角形」や「四角形」は「多角形」を継承しているといい、「三角形」や「四角形」は「多角形」の子クラス(サブクラス)であるといい、「多角形」は「三角形」や「四角形」の親クラス(スーパークラス、基底クラス)である。
また、「正方形」は「菱形」と「長方形」の両方の特別な場合であり、このような継承の仕方を多重継承という。Javaのようにオブジェクト指向言語でも多重継承をサポートしていない。
継承すると、親クラスのメンバー変数、メンバー関数は、その子クラスでも使用できるようになる。子クラスでは親クラスのメンバー関数をオーバーライトすることができるため子クラスで実相をより高機能化することができる。
2.スーパークラスのコンストラクタ
クラスを継承して子クラスを作る場合、そのほとんどが親クラスのコンストラクタを明示的に呼び出す必要がある。つまり親クラスのデフォルトコンストラクタ以外を呼び出したい場合などである。 StringVectorクラスと、ソース昨日付きVectorクラス(SortableStringVectorクラス)の場合を考えてみる。SortableStringVectorクラスのコンストラクタに、配列の大きさを指定すると、その引数を親クラスのコンストラクタに通知しなければならない。その場合は「super( )」を使う。このsuperは、コンストラクタの最初にしか用いることはできない。
Test.java class Test{ public static void main( String args[ ]){ SortableStringVector vect = new SortableStringVector( ); String str; int i = 0; while ( true ){ str = readLine( ); if ( str.length( ) == 0 ) break ;
vect.at( i,str ); i++; }
vect.sort( );
int n = vect.getLength( ); for ( i = 0 ; i < n ; i++ ){ str = vect.at( i ); if ( str.== null ) break ; System .out.print(str); } }
// 1行読み込んで、String型として返す public static String readLine( ){ byte b[ ] = new byte [1024]; try { System .in.read(b); } catch ( Exception e){ return null ; } int i; for ( i = 0 ; b[ i ] != 0 && i < b.length ; i++ ); return new String (b, 0, i); } }
StringVector.java
public class StringVector{ /////////////////////////////////////////////////// // 管理情報
protected int m_size; // 配列サイズ protected String m_vector[ ]; // 配列
// コンストラクタ:引数あり // StringVector オブジェクトが定義された時に自動的に呼び出される public StringVector( int sz ); // 配列サイズを保存する m_size = sz ;
// 配列の実体(size個のint配列)を自由記憶上に割り当てる m_vector = new String [m_size] ; } // コンストラクタ:引数なし // StringVector オブジェクトが定義された時に自動的に呼び出される public StringVector( ) { // もう1つのコンストラクタを呼び出す this ( 100 ); }
// 代入 public void at( int n, String value ){ if ( n >= m_size || n < 0 ){ System .err.println("invalid index"); System .exit( 1 ); // 異常終了する }
m_vector[n] = value ; }
// 取得 public String at( int n){ if ( n >= m_size || n < 0 ){ System .err.println("invalid index"); System .exit( 1 ); // 異常終了する }
return m_vector[n]; } // 現在の 配列サイズを獲得する public int getLength( ){ return m_size; } }
SortableStringVector.java
public class SortableStringVector extends StringVector{ /////////////////////////////////////////////////// // 管理情報
// コンストラクタ:引数あり // SortableStringVector オブジェクトが定義された時に自動的に呼び出される public SortableStringVector( int sz ){ // 親クラスのコンストラクタ呼び出し super (sz); } // コンストラクタ:引数なし // StringVector オブジェクトが定義された時に自動的に呼び出される public SortableStringVector( ) { // もう1つのコンストラクタを呼び出す this ( 100 ); }
// バブルソート void sort( ){ int flag = m_size -1; int flag = m_size; for ( ; j > 0 && flag ! = 0 ; j– ){ int i = 0; flag = 0; for ( ; j > i ; i++ ){ if ( m_vector[ i ] ! = null && m_vector[i+1] != null && m_vector[i+1].compareTo(m_vector[i]) > 0 ){ String a = m_vector[i+1]; m_vector[ i+1 ] = m_vector[i]; m_vector[ i ] = a; flag = 1; } } } } }
C:\java>javac Test.java
C:\java>java Test 123 234 345 222
345 234 222 123
C:\java>
このように作成されたSortableStringVectorは、その親クラスであるStringVectorクラスのメソッドを使用することもできるし、新たに加えたsortメソッドも利用できる。親クラスを進化させることができる。
3.継承とキャスト
下記のようにAクラスがBクラスの親クラスだった場合、Aクラス用の変数にBクラスのインスタンスを代入することができる。しかし、逆はエラーになる。犬は哺乳類だが、哺乳類は犬ではない。
class A{ : : } class B extends A{ : : } class Test{ public static void main( String args[ ] ){ // これはOK A a = new B( );
// これはエラー B b = new A( ); } }
これまで、StringVectorクラスを作ってきたが、何もStringクラスに特化させる必要はなかったのである。次のようにすればStringクラス以外のクラスもベクターとして管理することができる。 JavaではすべてのクラスがObjectクラスの子クラスである。最初から用意されているStringクラスはもちろん、これまで作ってきたStringVectorクラスやSortableVectorクラスも実はObjectクラスを継承している。
したがって、下記のようにStringVectorクラスをObjectVectorクラスに書き換えたベクタークラスを用いて今まで通りStringVectorのように使うことができる。
Test.java
class Test{ public static void main( String args[ ] ){ ObjectVector vect = new ObjectVector( ); vect.at(0, "HogeHoge"); vect.at(1, "HonyoHonyo");
System .out.println(vect.at( 0 )); System .out.println(vect.at( 1 )); } }
ObjectVector.java
// StringVector の StringをObjectに変更したベクタークラス public class ObjectVector{ ////////////////////////////////////////////// // 管理情報
protected int m_size; // 配列サイズ protected Object m_vector[ ]; // 配列
// コンストラクタ:引数あり // ObjectVector オブジェクトが定義された時に自動的に呼び出される public ObjectVector( int sz ){ // 配列サイズを保存する m_size = sz ; // 配列の実体(size個のint配列)を自由記憶上に割り当てる m_vector = new Object [m_size] ; }
// コンストラクタ:引数なし // ObjectVector オブジェクトが定義された時に自動的に呼び出される public ObjectVector( ){ // もう1つのコンストラクタを呼び出す this (100) ; }
// 代入 public void at( int n, Object value ){ if ( n >= m_size || n < 0 ){ System .err.println("invalid index"); System .exit(1); // 異常終了する }
m_vector[n] = value; }
// 取得 public Object at( int n){ if ( n >= m_size || n < 0 ){ System .err.println("invalid index"); System .exit(1); // 異常終了する }
return m_vector[n] ; }
// 現在の配列サイズを獲得する public int getLength( ){ return m_size; } }
Test.java
class Test{ public static void main( String args[ ] ){ ObjectVector vect = new ObjectVector( ){ vect.at(0,"HogeHoge"); vect.at(1,"HonyoHonyo"); String str1 = vect.at(0); String str2 = vect.at(1); } }
C:\java>javac Test.java Test.java:7 Incompatible type for declaration.Explicit cast needed to convert java.lang.Object to java.lang.String. String str1 = vect.at(0);
Test.java:8 Incompatible type for declaration.Explicit cast needed to convert String str2 = vect.at(1); 2 errors
C:\java>
これは、ObjectVectorクラスのatメソッドの戻り値がObjectクラスだからである。前述したようにStringクラスはObjectクラスの一種だが、ObjectクラスはStringクラスの一種ではないため、Objectクラスとして返された戻り値はStringクラスに代入できない。
しかし、プログラムをみればお分かりのとおり、実際に戻ってくるのはStringクラスのインスタンスである。この場合は、下記のようにキャストする。
Test.java
class Test{ public static void main( String args[ ] ){ ObjectVector vect = new ObjectVector( ){ vect.at(0,"HogeHoge"); vect.at(1,"HonyoHonyo"); String str1 = ( String ) vect.at(0); String str2 = ( String ) vect.at(1); } }
C:\java>javac Test.java
C:\java>
では、Stringクラスのインスタンスが入っていないのに、Stringクラスにキャストしようとするとどうなるのろうか。次の例では、StringクラスではなくTestクラスのインスタンスをObjectVectorに入れて、Stringクラスにキャストして取り出そうとしている。
Test.java
class Test{ public static void main( String args[ ] ){ ObjectVector vect = new ObjectVector( ){ vect.at(0,"HogeHoge"); vect.at(1," new Test( )); String str1 = ( String ) vect.at(0); String str2 = ( String ) vect.at(1); } }
C:\java>javac Test.java
C:\java>java Test.java Exception in thread "main" java.lang.ClassCastException: Test at Test.main(Test.java:8)
C:\java>
文法的には間違ってないのでコンパイルは正常にできるが、実際に実行しようとした場合にキャストできないとエラーとなってしまう。
このようなことにならないようにプログラマーは気をつけなければならないが、下記のようにinstanceof演算子を用いて、ミスしても大丈夫なようにするべきである。
Test.java
class Test{ public static void main( String args[ ] ){ ObjectVector vect = new ObjectVector( ){ vect.at(0,"HogeHoge"); vect.at(1," new Test( )); String str1 = null ; String str2 = null ; Object obj; = null ;
obj = vect.at(0); if ( obj instanceof String ) str1 = ( String )obj;
obj = vect.at(1); if ( obj instanceof String ) str2 = ( String )obj; } }
C:\java>javac Test.java
C:\java>java Test
C:\java>
4.スコープ
同じ変数や関数名を、親クラス、子クラスで定義するとどのようになるのだろうか。コンパイラは次のような順番で、目的とする変数や関数を検索する。
派生クラスをつくり、その派生クラスのメンバー関数での例をしめす。 ・派生クラスのメンバーメソッド内で定義した変数 ・派生クラスのメンバー変数 ・親クラスのメンバー変数
// 親クラス class Base{ public int a; private int b; } // 子クラス class Deriv extends Base{ public int b; private int c;
public void member_func_deriv( ){ a = 1; // 派生クラスにはないので、親クラスのaを参照 b = 2; // 派生クラスのbを参照 c = 3; // 派生クラスのcを参照 super .a = 10; // Baseクラスのaを参照 // super.b = 20; // Baseクラスのbを参照しようとするが、privateなのでエラー
public static void main( String args[ ]){ Deriv d = new Deriv( ) ;
d.a = 10; // 派生クラスにはないので、親クラスのaを参照 d.b = 10; // 派生クラスのbを参照 d.c = 10; // 派生クラスのcを参照しようとするが、privateなのでエラー
// d. super.a = 10; // このような使い方はできない } }
5.クラスの作成
クラスの構築 ・領域が確保される ・派生クラスの引数リストの一致するコンストラクタを呼び出す ・superがあれば、親クラスの引数リストの一致するコンストラクタを呼び出す ・superがなければ、親クラスの引数リストのないコンストラクタ(デフォルトコンストラクタ) を呼び出す ・派生クラスのコンストラクタの続きが実行される ※親クラスが他のクラスの派生クラスならば、再帰的に繰り返される
//親クラス class Base{ public Base( ){ System .out.println( "Base:Base( )"); } public Base( int i ){ System .out.println( "Base:Base( int )"); } } //子クラス class Deriv extends Base { public Deriv( ){ System .out.println( "Deriv::Deriv( )"); } public Deriv( int i ){ super ( i ); System .out.println( "Deriv::Deriv( )"); }
public static void main( String args[ ] ){ System .out.println( "main"); Deriv d1 = new Deriv( ); Deriv d2 = new Deriv( 1 ); } }
C:\java>javac Deriv.java
C:\java>java Deriv main Base::Base( ) Deriv::Deriv( ) Base::Base( int ) Deriv::Deriv( int ) C:\java>
6.abstarct
abstractとはメソッドの定義だけしておき、そのクラスでは実装せずに子クラスで実装するためのもである。abstractを含むクラスは完成していないクラスとなるために、インスタンスは作られない。子クラスでそのメソッドを実装し、子クラスのインスタンスを作る。 abstractなクラスは、「class」の前に「abstract」をつけないと、コンパイルエラーになってしまう。
//親クラス abstract class Base{ abstract public void func( );
//子クラス class Deriv extends Base { public void func( ){ System. out.println("Deriv::func"); }
public void main( String args[ ]){ Base base1 = new Deriv( ); // OK Base base2 = new Base( ); // エラー } }
基底クラスに純粋仮想関数が複数ある場合、すべての純粋仮想関数を派生クラスで実装しなければ派生クラスもまた抽象クラスになる。
//親クラス abstract class Base{ abstract public void func1( ); abstract public void func2( );
//子クラス abstract class Deriv extends Base { public void func1( ){ System. out.println("Deriv::func1"); } }
//さらに子クラス class DDeriv extends Deriv { public void func2( ){ System. out.println("DDeriv::func2"); } public void main( String args[ ] ){ Base base1 = new DDeriv( ); // OK Base base2 = new Deriv( ); // エラー Base base3 = new Base( ); // エラー } }
抽象クラスは概念として導入する場合が多い。つまりお絵かきソフトを作ろうと考えた場合、図形データはObjectVectorクラスに入れる。図形データは以下のように作る。
//図形クラス(抽象クラス) abstract class Zukei{ protected int x1, x2, y1, y2;
//描画する関数 public abstract void draw( );
//セーブする関数 public abstract void save( String filename); }
//四角形クラス class Rectangle extends Zukei { //描画する関数(このクラスで実装する) public void draw( ) { : : }
//セーブする関数(このクラスで実装する) public void save( String filename){ : : } }
//直線クラス class Line extends Zukei { //描画する関数(このクラスで実装する) public void draw( ) { : : }
//セーブする関数(このクラスで実装する) public void save( String filename ){ : : } }
//円クラス class Circle extends Zukei { //描画する関数(このクラスで実装する) public void draw( ) { : : }
//セーブする関数(このクラスで実装する) public void save( String filename ){ : : } }
class Draw{ public static void main( String args[ ]){ ObjectVector vect = new ObjectVector( ); vect.at(0, new Line( )); vect.at(1, new Circl( )); } }
7.継承と包含
新しいクラスを作成しようとした際には、次の3つの選択肢がある。 1.まったくゼロから作る。 2.既存のクラスを継承して、派生クラスとして作る。 3.既存のクラスを包含して新しく作る。