継承(クラスとオブジェクト)
オブジェクト指向プログラミング(OOP)とは、データとその操作方法を一緒にしたオブジェクト(Javaの場合はクラス)を利用したプログラム技法のこと。Smalltalkのアラン・ケイ(名付け親)とC++のビャーネ・ストロヴストルップ(特徴を再定義)が影響を与えたと言われる。
特徴
- カプセル化
- 継承
- ポリモーフィズム(多態性)
カプセル化については、アクセス修飾子とかセッタゲッタ(どうでもいいけど、雪駄下駄って出ると日本だなぁって思う)あたりで半分くらいは理解できてる気がするので、継承から進むようだ。
継承は、クラスの機能を他のクラスが引続ぐ仕組み。親子のような関係。継承元をスーパークラス(親クラス)、継承するクラスはサブクラス(子クラス)。継承を用いて、同一の機能を省略していく記述方法を差分プログラミングと呼ぶ。
スーパークラスから継承したメソッドを上書きすることを、メソッドのオーバーライド(再定義)と呼ぶ。オーバーロード(多重定義)は同一クラス内の引数の異なるメソッドを複数定義すること。ライドでスーパークラスに乗るイメージ、とのこと。
フィールドの再定義はオーバーライドとは呼ばない。ポリモーフィズムは単に上書きのことを言うわけではない。staticメソッドの再定義もできるが、あまり推奨はされない。
上記機能を試してみた。
/*
* 動物クラス
*/
public class Animal {
String favorite = "worm";
void sleep(){
System.out.println(getClass().getName() + " is sleeping normally now..." );
}
}
/*
* 猿クラス(動物クラスから継承)
*/
public class Ape extends Animal{
String favorite = "banana";
void sleep(){
System.out.println("Ape is sleeping on the tree now.....");
super.sleep();
}
}
/*
* 人間クラス(猿クラスから継承)
*/
public class Human extends Ape {
String favorite = "beef";
String item = "PC";
void sleep(){
System.out.println("Human is sleeping on the bed now.....");
}
void eatAndSleep(){
System.out.println(getClass().getName() + " is eating " + favorite +"." );
sleep();
super.sleep();
}
}
public class OOPSample {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println("### " + animal.getClass().getName() + " ###");
//System.out.println(animal.favorite);
animal.sleep();
Ape ape = new Ape();
System.out.println("### " + ape.getClass().getName() + " ###");
//System.out.println(ape.favorite);
ape.sleep();
Human human = new Human();
System.out.println("### " + human.getClass().getName() + " ###");
//System.out.println(human.favorite);
human.eatAndSleep();
}
}
実行結果
### Animal ###
Animal is sleeping normally now...
### Ape ###
Ape is sleeping on the tree now.....
Ape is sleeping normally now...
### Human ###
Human is eating beef.
Human is sleeping on the bed now.....
Ape is sleeping on the tree now.....
Human is sleeping normally now...
オーバーライドの制限
final修飾子がついたメソッドはオーバーライドできない。また、オーバーライドした際、元のメソッドで定義されているアクセス修飾子と同じか、またはゆるい宣言でなければならない。スーパークラスでprivateのメソッドは継承できない。同じシグネチャのメソッドを定義できるが、別メソッドの定義として扱われる。
スーパークラスのstaticなメソッドと同名の非staticなメソッドをサブクラスで定義することができない。非staticメソッドをstaticメソッドでオーバーライドすることもできない。throwsによる例外宣言も継承できるが、限定的な形になる(例外をなんでもスローできるわけではない)。
final修飾子
final修飾子をつけることで、クラス、また個別のメソッドの継承を禁止することができる。フィールドにfinal修飾子をつけた場合は、オーバーライドと継承を禁止するということではなく、代入(または初期化)時の値を変更できないということを表す。
継承とコンストラクタ、super()
コンストラクタ(他にはprivateなメンバ)は継承されない。コンストラクタ内でsuper()
を宣言すると、スーパークラスのコンストラクタを明示的に呼び出すことができる。superはコンストラクタ内で最初に呼び出される必要がある(あとにするとエラーが出る、納得)。
is or has
isで考えられる場合は継承関係、hasの場合は、メンバとして考えると、実装のときに迷わないらしい。
問題
メンバにwidth, height getArea()を持ったRectクラスを継承した、Cubeクラスで奥行きdepthと体積を求めるgetVolumeを定義して使用するサンプルを作成する。
public class Rect {
int width, height = 0;
public int getArea(){
return width * height;
}
}
public class Cube extends Rect {
int depth = 0;
public int getVolume() {
return getArea() * depth;
}
}
public class CubeSample {
public static void main(String[] args) {
Cube cube = new Cube();
cube.width = 10;
cube.height= 20;
cube.depth = 30;
System.out.println(cube.getVolume());
}
}
より整理して書くと以下。
class Rect {
int width, height = 0;
Rect(int x, int y) {
width = x;
height = y;
}
int getArea(){
return width * height;
}
}
class Cube extends Rect {
int depth = 0;
Cube(int x, int y, int z) {
super(x, y);
depth = z;
}
public int getVolume() {
return getArea() * depth;
}
}
public class CubeSample {
public static void main(String[] args) {
Cube cube = new Cube(10,20,30);
System.out.println(cube.getVolume());
}
}