- トップページ
- 特集PC技術
- Java言語入門 ~C言語を学んだ君へ~
- [第9回]継承
Java言語入門 ~C言語を学んだ君へ~
[第9回]継承
継承はオブジェクト指向における重要な概念の1つです。
ここでしっかり基礎を覚えてください。
目次
- [1] 継承とは
- [2] コンストラクタの呼び出し
- [3] オーバーライド
- [4] final
- [5] 継承とアクセス修飾子
- [6] オブジェクトのキャスト
- [7] 継承とポリモーフィズム
- [8] 抽象クラスと抽象メソッド
- [9] Objectクラス
- [10] UML
- [11] 練習問題 第1問
- [12] 第1問解答
- [13] 練習問題 第2問
- [14] 第2問解答
- [15] 練習問題 第3問
- [16] 第3問解答
[1] 継承とは
継承とは、「あるクラス」の機能を引き継いで、「新しいクラス」を作成するための仕組みです。
「あるクラス」をスーパークラス、「新しいクラス」をサブクラス
といいます。これについては、第7回「オブジェクト指向プログラミング」で説明しました。
ここではもう少し踏み込んで、継承関係についての説明をします。
「AはBの一種」である。
この関係が成り立つ時、AはBを継承できます。
例えば、「人間は哺乳類の一種」です。他に「犬は哺乳類の一種」です。
よって、人間と犬は哺乳類を継承できます。
ここで継承を行う上での注意点があります。
「AはBの一種」の関係からわかるように、AはBより特化(具体化)しています。
そのため、「哺乳類が人間を継承する」というような劣化(抽象化)の継承関係は行わないでください。
継承を学ぶ前にこのような注意をしたのは、むやみに継承してほしくないためです。
継承を使えば、「作業効率の向上」「可読性の向上」などが期待できますが、誤って使えば、逆の効果に陥る危険性もあります。
そのため、継承を行う場合は「AはBの一種」を思い浮かべて、正しい継承が行うことを心掛けてください。
継承の基本形
継承を行うために必要なキーワードがextendsです。
継承元のクラスの定義時に次のようにして使います。
継承の書式
class A extends B {
:
}
これで「AはBを継承する」ことになります。
このときのAがサブクラス、Bがスーパークラスとなります。
では、具体例として、哺乳類クラスと人間クラスを作成してみましょう。
人間は哺乳類の一種なので、哺乳類クラスを継承すればいいことになります。
哺乳類がスーパークラスで、人間がサブクラスです。
では、図で関係を見てみましょう。
少し簡単な例ですが、このようなクラス関係をソースに置き換えてみます。
また、一般的にこのような図をクラス図と呼びます。
サンプルプログラム
class Mammal { private String name; private int age; public Mammal(String name, int age) { System.out.println("Mammalコンストラクタの呼び出し"); this.name = name; this.age = age; } public void eat() { System.out.println(age + "歳の" + name + "が食事をします"); } } class Human extends Mammal { public Human(String name, int age) { super(name, age); System.out.println("Humanコンストラクタの呼び出し"); } } public class Java09_01 { public static void main(String args[]) { Human human = new Human("太郎", 10); human.eat(); } }
実行結果
少しわかりにくかったと思いますが、重要な点は以下の3つです。
・Mammalクラスに「名前」、「年齢」、「食べる」を定義していること
・Humanクラスに「メンバ変数」や「メソッド」を定義していない
・Humanクラスを作成したのに、Mammalクラスの機能(eat())が使えること
以上の点が確認できたら完璧です。これが「継承」というものです。
HumanクラスはMammalクラスを継承した時点で、Mammalクラスの機能が使えます。
1つ継承を使う時に注意点があります。
多重継承はすることができません。
つまり、"class A extends B, C"とすることができません。
別に複数できてもいいのでは?と思いますが、きちんと理由があります。それは第10回インタフェースで説明があります。
他に「super」などわかりにくい所もあったと思いますが、それについては次の項目で説明します。
[2] コンストラクタの呼び出し
オブジェクトを生成するとき、コンストラクタは必ず呼び出されます。
仮に、コンストラクタを定義していなければ、
JVMによって引数のないコンストラクタが自動的に作成されます。
このコンストラクタをデフォルトコンストラクタといいます。
ここで、話を継承に戻しますが、継承したクラスをインスタンス化する場合、
そのクラスのコンストラクタより先に、スーパークラスのコンストラクタを呼び出さなくてはいけない
という決まりがあります。そのため、上のHumanクラスのコンストラクタの初めに「super」と書きました。
これは、スーパークラスのことを指しています。
前回説明した「this」はクラス自身でしたが、それに似たものです。
つまり、「super(name, age)」によって、
スーパークラスのコンストラクタを呼び出しています。その結果、
サブクラスのインスタンス化(メインの処理)
↓
スーパークラスのコンストラクタの呼び出し
↓
サブクラスのコンストラクタの呼び出し
という順番で処理が行われます。実行結果がその証拠です。
なお、スーパークラスとサブクラスのコンストラクタがお互いに引数なしの場合、
「super」と書かなくても自動的に引数なしのコンストラクタが呼び出されます。
ですが、どのコンストラクタを呼び出したかわかりやすくするために、
「super」を書いた方が良いでしょう。
[3] オーバーライド
オーバーライドについて説明します。
これによって、より継承としての質が上がります。
オーバーライドとは
オーバーライドとはスーパークラスのメソッド内容の書き換えです。
これまでの継承はただスーパークラスの機能を引き継ぐだけでした。
しかし、サブクラスではさらに特化した機能に変更したい場合があります。
例えば、前のMammalクラスのeatメソッドは「食事をします」という抽象的なものでした。
それは、哺乳類によって「食べ物」や「食べ方」が違うため、具体的な処理ができないためです。
しかし、人間なら「箸を使ってご飯を食べる」という具体的な処理ができます。
では、実際にオーバーライドによって、Humanクラスを変更します。
サンプルプログラム
class Mammal { private String name; private int age; public Mammal(String name, int age) { System.out.println("Mammalコンストラクタの呼び出し"); this.name = name; this.age = age; } public void eat() { System.out.println(age + "歳の" + name + "が食事をします"); } } class Human extends Mammal { public Human(String name, int age) { super(name, age); System.out.println("Humanコンストラクタの呼び出し"); } // オーバーライド public void eat() { System.out.println("箸を使ってご飯を食べる"); } } public class Java09_01 { public static void main(String args[]) { Human human = new Human("太郎", 10); human.eat(); } }
実行結果
オーバーライドは、書き変えたいメソッドと同等なものを、もう一度サブクラスで定義し直すことで実現します。
今回は「eat」メソッドを書き換えました。より人間らしい食事になったと思います。
人によって箸を使わないとか、そういう苦情はやめてください。
super
オーバーライドについては説明しました。
しかし、前の方法には不十分な点があります。
それは機能の追加です。前回は機能を書き直していました。
そこで、「super」を使って機能の追加を行います。「super」とはスーパークラスを指します。
つまり、Humanクラスで「super.eat()」と書けば、Mammalクラスのeatメソッドを呼び出すことができます。
これを使って、機能の追加が実現できます。以下のようにHumanクラスを変更します。
サンプルプログラム
class Mammal { private String name; private int age; public Mammal(String name, int age) { System.out.println("Mammalコンストラクタの呼び出し"); this.name = name; this.age = age; } public void eat() { System.out.println(age + "歳の" + name + "が食事をします"); } } class Human extends Mammal { public Human(String name, int age) { super(name, age); System.out.println("Humanコンストラクタの呼び出し"); } // オーバーライド public void eat() { super.eat(); System.out.println("箸を使ってご飯を食べる"); } } public class Java09_01 { public static void main(String args[]) { Human human = new Human("太郎", 10); human.eat(); } }
実行結果
実行結果より、機能が追加できたことがわかります。
なお、スーパークラスのコンストラクタの呼び出しと異なり、
スーパークラスのメソッドの呼び出しは場所を選ばないので、
好きな場所で呼び出すことができます。
[4] final
finalキーワードについての説明をします。
これを使うことで、バグの少ないソースを書くことができます。
また、「定数」の作り方についても説明します。
「final」とは文字通り「最後」という意味で、これ以上変更したくない場合に使用します。
finalの書式
final class A { // classの前にfinalを付ける : }
final void method() { // 戻り値の前にfinalを付ける : }
final int VALUE = 10; //型宣言の前にfinalを付ける
先頭にfinalをつけます。finalにつけたものに関して変更が禁止されます。
finalは、クラス、メソッド、メンバ変数それぞれで使うことができます。
クラスにfinalを付けた場合には継承ができません。
メソッドにfinalを付けた場合には、オーバーライドによる書き換えができません
変数にfinalをつけた場合には、C言語の[定数]のような役割を果たします。
#define VALUE 10
これとほぼ同じです。
しかし、Javaの定数は「型」を持ち、「修飾子」を付けられます。
つまり、C言語の定数より機能が上です。
定数名は「大文字で書くことが一般的」であり、これについてはC言語と共通しています。
補足として、上記の書き方でも十分定数の役割を果たしますが、基本的にそのような書き方をしません。
定数は次のように書きます。
static final int VALUE = 10; // 基本的な定数の宣言方法
サンプルプログラム
public class Java09_02 { public static void main(String args[]) { FinalClass2 fc2 = new FinalClass2(); //fc2.a = 20; final変数は値の変更ができない fc2.func(); } } class FinalClass{ //final定数を宣言 final public int a = 10; //finalメソッドを定義 final public void func(){ System.out.println("funcメソッド"); } } class FinalClass2 extends FinalClass{ //final public void func(){} finalメソッドはオーバーライドできない }
実行結果
finalを使ったプログラムを書いてみました。
コメント部分の//を消してみればわかると思いますが、変更ができません。
このようにこれ以上変更する予定がない場合などに用いられます。
[5] 継承とアクセス修飾子
継承関係におけるアクセス修飾子の効果について説明します。
「パッケージ」を学習していない段階では、アクセス修飾子をすべて理解できません。
ここでは、アクセス修飾子について少しだけ説明します。
「パッケージ」については、第11回パッケージを見てください。
アクセス修飾子の復習
第8回で説明したように、アクセス制御には以下の方法がありました。
アクセス修飾子 | 制御の強さ | 機能 |
---|---|---|
public | 弱い | 全てのクラスからアクセスを許可 |
protected | 少し弱い | 同じパッケージ、継承先からのアクセスを許可 |
private | 強い | 同じのクラスからのアクセスを許可 |
なし | 少し強い | 同じパッケージからのアクセスを許可 |
次の項目で、継承とこれら4種類のアクセス制御について説明します。
アクセス修飾子の効果
継承の場合のアクセス修飾子の効果を、メンバ変数を例に説明します。
次のソースを見てください。
アクセス修飾子のプログラム
class Super { public int a; protected int b; private int c; int d; public super() { a = b = c = d = 10; } } class Sub extends Super { public Sub() { super(); System.out.println("a="+a); System.out.println("b="+b); //System.out.println("c="+c); ここだけコンパイルエラー System.out.println("d="+d); } } public class Java09_03{ public static void main(String args[]){ Sub sub = new Sub(); } }
実行結果
スーパークラスSuperに4種類のアクセス制御を持たせたint型を定義しています。
サブクラスSubで各int型のメンバ変数にアクセスを試みます。
結果、「private」のみアクセスができません。
アクセス制御方法の一覧と比較して、理解ができたと思います。
この例では、「public」はすべてのクラスからアクセスできるので良いとしても、
「protected」と「アクセス修飾子なし」の違いがわかりにくいです。
その違いは、第11回「パッケージ」で説明します。
今は、「protected」は継承した先からアクセスできると覚えておいてください。
[6] オブジェクトのキャスト
オブジェクトのキャストについて説明します。
基本データ型のキャストとの違いを学んでください。
基本データ型の違い
キャストするには基本データ型のキャストは次のように行いました。
double value1 = 1.414;
int value2 = (int)value1; // int型にキャスト
double value3 = value2; // キャストしてdouble型に戻す
大きいデータ型から小さいデータ型にキャストする場合は「(データ型)」を使っています。
次にオブジェクトをキャストする方法を説明します。
B b1 = new B();
A a = b1; // A型にキャスト
B b2 = (B)a; // キャストしてB型に戻す
AとBはオブジェクトです。
B型のオブジェクトをA型にキャストし、それをB型に戻しています。
ここで注意することは、AはBのスーパークラスです。
この継承関係が成り立つ時のみ、キャストが行えます。
例えば、次のクラス関係があったとします。
この場合、
・BはAにキャストできる
・CはBにキャストできる
・CはAにキャストできる
以上の3パターンのみキャストできます。
キャストした場合のオブジェクトと基本データ型の違いを説明します。
まず、基本データのキャストについてです。次のソースを見てください。
キャストのサンプルプログラム
public class Java09_04 { public static void main(String args[]) { double value1 = 1.414; int value2 = (int)value1; System.out.println(value2); // int型にしたデータを出力 System.out.println((double)value2); //double型に戻して出力 } }
実行結果
一度キャストしたデータを元の型に戻しても、失ったデータは戻りません。
当然といえば当然です。次にオブジェクトのキャストです。
次のソースを見てください。
オブジェクトのサンプルプログラム
class A { int a; public A() { a = 10; } } class B extends A { int b; public B() { super(); b = 20; } } public class Java09_05 { public static void main(String args[]) { B b = new B(); A a = b; System.out.println(a.a); //System.out.println(a.b); クラスBの変数は使えない B b1 = (B)a; System.out.println(b1.b); // クラスBの変数が使える } }
実行結果
ソース中、コメントアウトした処理があります。
それは「System.out.println(a.b);」で、当然エラーになる処理です。
理由は、クラスAにキャストしたために、クラスBの機能が使えなくなったからです。
ない機能を使えるはずがありません。しかし、一度クラスAにキャストしたオブジェクトを、
元のクラスBにキャストすると、クラスBの機能が使えるようになります。
オブジェクトのキャストでは、基本データ型のキャストと異なり、機能(データ)がなくなりません。
キャストをしても、メモリ上にはクラスBの情報が残ります。
オーバーライドとキャスト
キャストをしてもデータは消えないと説明しました。
しかし、キャストしたクラスにない機能は使用できませんでした。
では、キャスト先とキャスト元の両方にある機能を見てみましょう。
つまり、「オーバーライドしたメソッド」はどのような結果になるでしょう。
次のソースを見てください。
オーバーライドしたメソッド
class C { public void print() { System.out.println("クラスCです"); } } class D extends C { public void print() { System.out.println("クラスDです"); } } public class Java09_06 { public static void main(String args[]) { D d1 = new D(); C c = d1; c.print(); // キャストしたあとメソッドの呼び出し D d2 = (D)c; d2.print(); // 元の型にキャストしてメソッドの呼び出し } }
実行結果
Dクラスのオブジェクトを、Cクラスにキャストした時とDクラスに戻した時
それぞれで、メソッドを呼び出しています。
クラスCです
クラスDです
と表示されるように思えますが、それは誤りです。
実際はキャストしていても、元のクラスのメソッドが呼び出されます。
少しわかりにくいですが、この点に注意してください。
しかし、これを利用すると、「ポリモーフィズム」を実現する大きな利点になります。
それについては、次のページで説明します。
[7] 継承とポリモーフィズム
これまでに「継承」、「オーバーライド」、「キャスト」について説明しました。
これでポリモーフィズムを実現できます。
オーバーロードとは異なる方法を説明します。
設計
設計と言ったら大げさですが、どのようなものを作るかを考えるということです。
ここでは、哺乳類の継承を例にポリモーフィズムを実現させます。
まず、「哺乳類」には「犬」、「猫」、「人間」がいます。
これらの共通点は「食べる」ことです。
(他にもたくさんありますが、1つだけにします)
「食べる」ことは同じですが、具体的に食べるものはそれぞれ異なります。
そこで、スーパークラスにある「食べる」には何も書きません。
継承をするクラス(人間、犬、猫)に何を食べるのか決めます。
では、食べるものはそれぞれ何にしましょうか。
犬は「ドッグフード」、猫は「キャットフード」、人間は「カレー」にしましょう。
この食べる部分には「オーバーライド」を使います。
これで、各4つのクラスを作成します。
次の項目でこのクラス図を元にプログラムを作成し、ポリモーフィズムを実現させます。
作成
実際に作成します。ソースは次のようになります。
サンプルプログラム
class Mammal { public void eat() { } } class Dog extends Mammal { public void eat() { System.out.println("ドッグフードを食べます"); } } class Cat extends Mammal { public void eat() { System.out.println("キャットフードを食べます"); } } class Human extends Mammal { public void eat() { System.out.println("カレーを食べます"); } } public class Java09_07 { public static void main(String args[]) { Mammal mammal[] = new Mammal[3]; mammal[0] = new Dog(); // 犬生成 mammal[1] = new Cat(); // 猫生成 mammal[2] = new Human(); // 人間生成 for (int i = 0; i < mammal.length; i++) { mammal[i].eat(); // まとめてeat()を呼び出す } } }
実行結果
メインで同じeatメソッドを3回呼び出しています。
しかし、各オブジェクトに定義されているeatメソッドの処理は異なります。
それが実行結果に反映されています。
ポリモーフィズムが実現できたことがわかると思います。
ポリモーフィズムとは、同じ名前で異なる処理を行うことです。
[8] 抽象クラスと抽象メソッド
通常のクラスとメソッドとは少し違ったクラスとメソッドについて説明します。
それは、抽象クラスと抽象メソッドです。
継承によるポリモーフィズムを実現する手法になるため、しっかり覚えてください。
ポリモーフィズムの実現
ポリモーフィズムは、第8回「クラス」で説明しました。
同じ名前で異なる処理を行うことだと説明しました。
つまり、すでに"Java09_07のプログラム"で、継承によるポリモーフィズムを実現しています。
(eat()メソッドをそれぞれdog,cat,humanで、同じメソッド名でも処理が異なっています。)
では、ポリモーフィズムを実現するための手法の抽象クラス、抽象メソッドとは何なのでしょうか。
まず、Java09_07のプログラムでポリモーフィズムは実現できました。
それは小規模のソースでありすべてのクラスを「同一人物」が作成したソースだからです。
Java09_07の例では、「eatメソッドをオーバーライドするぞ!」と決めていたから実現できたのです。
では、もしクラスごとに作成する人が異なったらどうなるでしょうか。
Mammalクラスを継承しても、中にはeatメソッドをオーバーライドせずに、「taberuメソッド」という全く別のメソッドを作る人がいるかもしれません。
他に、先ほどのように「決まり事」があったとしても、「eetメソッド」のように誤って書く人もいるでしょう。
このような場合、ポリモーフィズムは実現できなくなります。
以上の問題を解決するために、必ずオーバーライドしなくてはいけないeatメソッドを作る必要があります。
そうすれば、誤ったメソッドを作る人が減り、ポリモーフィズムが実現しやすくなります。
これを実現する目的としてあるのが、抽象クラスと抽象メソッドなのです。その方法について次の項目で説明します。
abstract
abstractとは「抽象的」な意味を持つキーワードで、何も書いていないメソッドを作るために使います。
このようなメソッドを抽象メソッドといい、次のように書きます。
abstract void method();
戻り値の前に「abstract」を付けて、さらに「{}」の代わりに「;」を付けます。
まさに「何も書いていないメソッド」の出来上がりです。
そして、この「抽象メソッド」を「1つ以上」含むクラスを抽象クラスといい、次のように書きます。
abstract class A {
abstract void method(); // 抽象メソッド
}
クラスの前に「abstract」を付けます。
抽象クラスとメソッドを使う時には以下のことに気をつけて使います。
・抽象メソッドを作る時には、必ずクラスにも"abstract"を書くこと(抽象クラスにすること)
・抽象クラスはインスタンス化しないこと(できません)
・抽象クラスを継承したら必ず抽象クラスにある抽象メソッドをオーバーライドすること
理由は「何も書いていないメソッド(抽象メソッド)」があり、
継承によってオーバーライドすることを前提にしているためです。
abstractの制限
具体的にどのようなことが、制限されるかを説明します。
Java09_07プログラムのソースを少し変更しました。次のソースを見てください。
サンプルプログラム
// 抽象クラス abstract class Mammal { // 抽象メソッド public abstract void eat(); } // 正しくオーバーライドしたクラス class Dog extends Mammal { public void eat() { System.out.println("ドッグフードを食べます"); } } // 新しくtaberuメソッドを作ったクラス(コンパイルエラー) class Cat extends Mammal { public void taberu() { System.out.println("キャットフードを食べます"); } } public class Java09_08 { public static void main(String args[]) { Mammal mammal[] = new Mammal[3]; mammal[0] = new Dog(); mammal[1] = new Cat(); mammal[2] = new Mammal(); // 抽象クラス作成(コンパイルエラー) for (int i = 0; i < mammal.length; i++) { mammal[i].eat(); } } }
実行結果
上のソースでは、2つのコンパイルエラーが起きています。
1つは「Catクラス」で「eatメソッド」をオーバーライドしていないからです。
eatメソッドは抽象メソッドのため、オーバーライドしないと、コンパイルエラーが起きます。
もう1つは、「Java09_06クラス」で抽象クラスをインスタンス化しようとしているからです。
抽象クラスはインスタンス化できません。そのため、コンパイルエラーが起きます。
以上のように、誤った使い方があると、コンパイラが教えてくれるため、
バグの起きにくいソースを作ることができるようになります。
[9] Objectクラス
Objectクラスについて説明します。
このクラスは「継承」に関わっており、Javaプログラミングをする上で、覚えておきたいクラスの1つです。
Objectクラスとは
これまで継承するには、「extends」をすると説明しました。
では、「extends」を付けないクラスは、他のクラスと継承関係のない、独立したクラスになるのでしょうか。
実は「extends」を付けなくても、継承は行われます。
その継承元のクラスがObjectクラスです。
このObjectoクラスは継承関係において、すべてのクラスの頂点に立つクラスです。
今まで使用した「Stringクラス」や「Systemクラス」、その他「自作クラス」も、
元を辿ればObjectクラスを継承しています。
Objectクラスのメソッド
Objectクラスはすべてのクラスの頂点にあるため、特殊なメソッドを持っています。
ここでは、その一部を紹介します。
boolean equals
前回の説明で使用した「equals」です。
これは、2つのオブジェクトが等しいかどうかを調べるメソッドです。
前回は、Stringクラスに使用しましたが、実は「Objectクラス」のメソッドなのです。
そのため、他のクラスすべてに「equalsメソッド」があります。
ただし、自作クラスなどで「オーバーライド」をしていない場合、
正しく動作しない場合があります。
Class getClass
見たことない戻り値だと思いますが、これは「Classクラス」です。
つまり、これはClassクラスを返すメソッドです。
Classクラスとは、そのオブジェクトに関する情報を保持します。
例として、Classクラスのメソッドには「String getName()」があります。
このメソッドを使うとそのクラスの名前を返します。次のソースを見てください。
サンプルプログラム
public class Java09_09 { public static void main(String args[]) { String str = "文字列です"; System.out.println(str.getClass().getName()); } }
実行結果
このように、オブジェクトがStringクラスであると教えてくれます。
Objectクラスとキャスト
キャストについてはすでに説明しました。
そこでは、キャストは「継承関係」がないと行えないと説明しました。
ところが、Objectクラスはすべてのクラスのスーパークラスです。
ということは、どのようなクラスを作っても、必ずどこかで「継承関係」が生まれます。
つまり、どのようなクラスでもObjectクラスにキャストができるということです。
次のソースを見てください。
サンプルプログラム
class X { } class Y { } public class Java09_10 { public static void main(String args[]) { Object obj[] = new Object[2]; obj[0] = new X(); obj[1] = new Y(); for (int i = 0; i < obj.length; i++) { System.out.println(obj[i].getClass().getName()); } } }
実行結果
「extends」を使わずに作成した「Aクラス」と「Bクラス」のオブジェクトが、同じ配列(変数)に格納できます。
これは、Objectクラスの配列のため、どのようなクラスも格納することができるということです。
これを用いると、1つの配列で異なるクラスをすべて管理することも可能です。
ただし、このように何でも1つの配列で管理すると、
どのオブジェクトが入っているかがわかりにくくなるため、
使うときは十分注意してください。
[10] UML
継承の説明で、ある図を見せました。オブジェクト指向によるシステム開発では、
そのオブジェクト間のやりとりを言葉で伝えることになりますが、
プログラムを見て判断するというのは、情報が多すぎて大変です。
そこで、このような図を使って伝えやすくします。
これにより、はるかにわかりやすく伝えることができます。
これをUMLといいます。上で使われている図はクラス図と呼ばれていて、
クラス関係を示すときによく使われる図なのです。
また、他のUMLには動作を中心としたものなどさまざまあります。
このコンテンツではUMLについてこれ以上の説明を行いませんが、
Javaプログラムを一通り学んだらUMLを学んでみると良いでしょう。
[11]練習問題 第1問
以下の指示に従ってプログラムを作成しなさい。
なお、ファイル名はEx09_01.javaとする。
作成にあたっての条件
以下の条件を満たす、犬(Dog)クラスと猫(Cat)クラスを作成しなさい。
ただし、「継承」を使って効率よく作成しなさい。
Dogクラスの情報
項目 | 内容 |
---|---|
メンバ変数 | 年齢 |
コンストラクタ | 年齢を引数から受け取り、メンバ変数を初期化する |
メソッド1 | 歩く |
メソッド2 | 噛みつく |
Catクラスの情報
項目 | 内容 |
---|---|
メンバ変数 | 年齢 |
コンストラクタ | 年齢を引数から受け取り、メンバ変数を初期化する |
メソッド1 | 歩く |
メソッド2 | ひっかく |
その他
*なお、上記のクラスが作成出来れば、生成するクラス数、メソッドの処理内容、カプセル化などについては自由に指定してよい。
[12]第1問解答
第1問解説
簡単な継承が書けるかを確認する問題です。以下より、詳しく解説します。
ただし、基本的にカプセル化はほとんど意識して作成していません。
1~11行目
Mammal(哺乳類)クラスを宣言します。
問題で指定したクラスではありませんが、DogクラスとCatクラスのスーパークラスとして作成します。
記述する内容は、以下のDogクラスとCatクラスの「共通点」です。
共通のMammalクラスの作成
項目 | 内容 |
---|---|
メンバ変数 | 年齢 |
コンストラクタ | 年齢を引数から受け取り、メンバ変数を初期化する |
メソッド1 | 歩く |
なお、メンバ変数の年齢ageは、継承先でも使えるように、「protected」を使っています。
「private」でもかまいませんが、その場合は継承先でも使える工夫をしてください。
13~21行目:
Dogクラスを作成します。
Mammalクラスを継承しているため、「噛みつく」メソッドのみ記述するだけで済みます。
23~31行目
Catクラスを作成します。
Mammalクラスを継承しているため、「ひっかく」メソッドのみ記述するだけで済みます。
[13]練習問題 第2問
以下のプログラムの実行結果を答えなさい。
[14]第2問解答
第2問解説
抽象クラスを使った「ポリモーフィズム」の理解度を確認する問題です。
特別難しいことはしていませんので、簡単に解説します。
解説
まず、クラスAをクラスBとクラスCが継承しています。
ここで、クラスAは抽象クラスのため、
クラスBとクラスCはprintメソッドをオーバーライドします。
次に、クラスEx09_02のメインメソッドでは、
クラスBのオブジェクトを、クラスAの型にキャストしています。
そして、printメソッドを呼び出します。ここで、重要なことは
キャストを使っても「元のオブジェクトの処理が呼び出される」ということです。
したがって、クラスBとCのオブジェクトに対して、
printメソッドを呼び出したため、それが実行結果に反映されます。
[15]練習問題 第3問
クラスやメソッドには「abstract」と「final」を同時に付けることはできません。
その理由を答えなさい。
例えば、次のように記述することはできません。
// クラスの場合
abstract final class A {
:
}
[16]第3問解答
「abstract」と「final」は全く正反対の機能を持つため、
同時に使うと、「矛盾」が生じてしまうから。
第3問解説
「abstract」と「final」についての理解度を確認する問題です。
この2つについて、もう一度説明します。
abstract
抽象クラス・抽象メソッドを作成するために使うキーワードです。
つまり、「継承」・「オーバーライド」を前提に定義したい時に使います。
final
変更不可にするために使うキーワードです。
具体的に「継承」・「オーバーライド」・「書き換え」を禁止したい時に使います。
以上より、abstractとfinalは全く正反対の機能を持ちます。
そのため、同時に使うと、abstractで継承を前提にしているにもかかわらず、
finalで継承を禁止するという、矛盾が生じます。
したがって、この2つは同時には使えません。
コメント
May
お疲れ様です。最近Javaの勉強を始めました。
とてもわかりやすい記事で参考にさせてもらってます。
finalの項で説明文に誤字、脱字、
あとサンプルプログラムが正常に表示されていないようなので報告させて頂きました。
2012年1月24日 17:07
特集PC技術メンバー
ご連絡ありがとうございます。ほぷしぃ特集PC技術メンバーです。
ご指摘の通り、ソースコードが正しく表示されておりませんでしたので
他のソースコードに合わせて統一を行いました。
又、ご指摘のありました誤字、脱字につきましても対応しました。
また、何かありましたら、コメント等でご連絡をお願いします。
2012年1月25日 08:48
匿名
アクセス修飾子のプログラムの
class Super {
public int a;
protected int b;
private int c;
int d;
public super() { // superがSuperでないためコンパイルエラー
a = b = c = d = 10;
}
}
ではないかと。
2016年5月19日 11:30
コメントの投稿
トラックバックURL
http://www.isl.ne.jp/cgi-bin/mt/mt-tb.cgi/1100