継承とは
サブクラスは、スーパークラスのフィールドとメソッドを受け継ぎます。このことを継承と呼びます。
フィールドとメソッドは、そのクラスの性質を表現するものですから、子供が親の遺伝子を受け継ぐように、サブクラスはスーパークラスの性質(フィールドとメソッド)を受け継ぐと考えると良いと思います。
ちなみに、スーパークラスとサブクラスの説明については、下記のページを参照してください。
フィールドの継承
Rectangleクラスをスーパークラスとして、NamedRectangleクラスをサブクラスとして宣言します。
class Rectangle{
int width;
int height;
・・・
}
class NamedRectangle extends Rectangle{
・・・
}
さて、Rectangleクラスのインスタンスはwidthとheightを持つことになりますが、サブクラスのNamedRectangleもインスタンスとしては、widthとheightを持つことになります。
つまり、NamedRectangleの中には、widthもheightも宣言されていないのにスーパークラスのフィールドを持つことになります。
このスーパークラスが持っているフィールドをサブクラスも持つということ、これがフィールドの「継承」です。
class Rectangle{
int width;
int height;
・・・
}
class NamedRectangle extends Rectangle{
NamedRectangle nr = new NamedRectangle();
nr.width = 123;
nr.height = 45;
System.out.println(nr.width);
System.out.println(nt.height);
}
NamedRectangleクラスはRectangleクラスのサブクラスなので、フィールドを宣言せずとも、インスタンスnrを介して、フィールドのwidthやheightを参照したり、代入したりできます。
メソッドの継承
メソッドの継承もフィールドの継承と同様になります。
class Rectangle{
・・・
void setSize(int width,int height){
・・・
}
int getArea(){
・・・
}
}
class NamedRectangle extends Rectangle{
・・・
}
この時、NamedRectangleのインスタンスは、setSizeやgetAreaメソッドを呼び出すことができます。NamedRectangleのクラス宣言の中に、それぞれのメソッドの宣言が書いていなくても呼び出すことができます。これをメソッドの「継承」といいます。
class Rectangle{
・・・
void setSize(int width,int height){
・・・
}
int getArea(){
・・・
}
}
class NamedRectangle extends Rectangle{
NmamedRectangle nr = new NamedRectangle();
nr.setSize(123,45);
System.out.println(nr.getArea());
}
コンストラクタは継承されない
コンストラクタについては、上記のページで説明しています。
念のため、ここでも復習しておきましょう。
インスタンスを作成した時に初期値として設定したいものを決めておけるのがコンストラクタでした。
言い換えれば、コンストラクタでインスタンスの初期値を初期化できるということになります。
さて、このコンストラクタですが、継承されることはありません。
ざっくり理由を説明すると、継承されてしまうとインスタンスの初期化が上手くいかなくなるからです。
スーパークラスで宣言されているからといって、サブクラスのコンストラクタとして呼び出すことはできません。
class Rectangle{
int width;
int height;
Rectangle(){ //01
width = 0;
height = 0;
}
Rectangle(int width,int height){ //02
this.width = width;
this.height = height;
}
・・・
}
class NamedRectangle extends Rectangle{
String name;
NamedRectangle(){ //03
name = "NO NAME";
}
NamedRectangle(String name){ //04
this.name = name;
}
}
public static void main(String[] args){
Rectangle r = new Rectangle(); //01の呼び出し
Rectangle s = new Rectangle(123,45); //02の呼び出し
NamedRectangle nr = new NamedRectangle(); //03の呼び出し
NamedRectangle ns = new NamedRectangle("長方形"); //04の呼び出し
}
上記のメインメソッドでのコンストラクタの呼び出しは、正しい場合の呼び出しです。
次の場合は、コンパイル時にエラーになります。
・・・
public static void main(String[] args){
NamedRectangle nr = new NamedRectangle(123,45); //サブクラスから02の呼び出しはできない。
}
上記のコンストラクタの呼び出しでエラーになるのは、int型の引数を持つコンストラクタは、Rectangleクラスにはありますが、NamedRectangleクラスにはないからです。
そのため、NamedRectangle型のインスタンスとして呼び出そうとしても呼び出せないのです。
スーパークラスのコンストラクタの呼び出し super()
例えば、次のようにインスタンスを作成し、コンストラクタを呼び出す時に、長方形の大きさを決めておきたいときがあると思います。
NamedRectangle ns = new NamedRectangle("長方形"); //04の呼び出し
コンストラクタは初期値を初期化するものですので、この呼び出し時にもフィールドの値を初期化した状態で適用されます。
つまり、次のコンストラクタが自動的に適用されます。
・・・
Rectangle(){ //01
width = 0;
height = 0;
}
・・・
本当は名前つきのインスタンスを作成した時は、widthとheightをあらかじめ決めておきたいという時があります。
Javaでは、明示的に宣言しないと自動的に引数なしのコンストラクタが適用されるという仕様になっています。
widthとheightを決めたいのであれば、引数付きのコンストラクタを呼び出す必要があります。
そういうときは、スーパークラスのコンストラクタに対する引数をそのまま書く形で、
super(200,32);
というように書きます。
したがって、NamedRectangleのコンストラクタは次のように書くことになります。
・・・
NamedRectangle(String name){ //04
super(200,32); //スーパークラスの引数付きコンストラクタの呼び出し
this.name = name;
}
・・・
自分のクラスのコンストラクタの呼び出し this()
super()は、スーパークラスのコンストラクタの呼び出しを表していましたが、自分のクラスのコンストラクタを呼び出すときは、this()を使います。
・・・
class NamedRectangle extends Rectangle{
String name;
NamedRectangle(){ //03
//name = "NO NAME";
this("NO NAME"); //thisでコンストラクタ04を呼び出している
}
NamedRectangle(String name){ //04
super(200,32);
this.name = name;
}
}
・・・
上記の例を見ると、thisで04のコンストラクタを呼び出しています。
まず、thisの引数はString型になりますので、String型の引数を持つコンストラクタが呼び出されます。それに対してthisの引数が”NO NAME”なので、この”NO NAME”という初期値を渡しているということになります。
これは実際にインスタンスを作成して呼び出したときの挙動が変化するわけではありません。
・・・
class NamedRectangle extends Rectangle{
String name;
NamedRectangle(){ //03
//name = "NO NAME";
this("NO NAME"); //thisでコンストラクタ04を呼び出している
}
NamedRectangle(String name){ //04
super(200,32);
this.name = name;
}
}
public static void main(String[] args){
NamedRectangle nr = new NamedRectangle(); //03の呼び出し
NamedRectangle ns = new NamedRectangle("長方形"); //04の呼び出し
}
メインメソッドで03の呼び出しを行った場合、引数がありませんので、this(“NO NAME”);が読み込まれます。その際、thisを使っていますので、String型の引数を持つ04が呼び出されます。04は、this.name = name;となっており、仮引数としてnameを持っていますので、仮引数のnameは03から渡された引数である”NO NAME” に置き換わります。結果的には、インスタンスを引数なしで呼び出したときは、初期値である”NO NAME”が使われることになります。
今度は、メインメソッドで04の呼び出しを行った場合、引数がありますので、this.name = name;が読み込まれます。nameという仮引数に”長方形”というインスタンス側の引数が渡されて、”長方形”が使われることになります。
まとめ
ここまでに作ったソースコードをまとめておきます。
class Rectangle{
int width;
int height;
Rectangle(){ //01
width = 0;
height = 0;
}
Rectangle(int width,int height){ //02
this.width = width;
this.height = height;
}
・・・
}
class NamedRectangle extends Rectangle{
String name;
NamedRectangle(){ //03
//name = "NO NAME";
this("NO NAME"); //thisでコンストラクタ04を呼び出している
}
NamedRectangle(String name){ //04
super(200,32);
this.name = name;
}
}
public static void main(String[] args){
Rectangle r = new Rectangle(); //01の呼び出し
Rectangle s = new Rectangle(123,45); //02の呼び出し
NamedRectangle nr = new NamedRectangle(); //03の呼び出し
NamedRectangle ns = new NamedRectangle("長方形"); //04の呼び出し
}