<概要>多態性について学びます。C++言語よりもかなり分かり易くなっています。
●例題12-2-1 フィールド、メソッドをオーバーライドする。
○プログラム
class Rectangle{
int width;
int height;
double PI=3.14;
void setSize(int width,int height){
this.width=width;
this.height=height;
}
}
class NamedRectangle extends Rectangle{
double PI=3.1415;
double getSuperPI(){
return super.PI;
}
void setSize(int width,int height){
this.width=-width;
this.height=-height;
}
public static void main(String[] args){
NamedRectangle nr=new NamedRectangle();
nr.setSize(123,45);
System.out.println(nr.width);
System.out.println(nr.height);
System.out.println(nr.PI);
System.out.println(nr.getSuperPI());
}
}
○実行結果
D:\atsushi\Java\List12-8>java NamedRectangle
-123
-45
3.1415
3.14
-- Press any key to exit (Input "c" to continue) --
○解説
オーバーライドとはサブクラスで同じフィールド、同じメソッドを定義することです。
同じフィールドとは、型と名前が等しいこと、
同じメソッドとは、返却値型、メソッドの名前、引数列の数と型が等しいことを意味します。
オーバーライドすると上書きした新しいサブクラスのフィールド、メソッドが呼び出されます。
スーパークラスの古いフィールドを呼び出す為には super.PI のように、
スーパークラスの古いメソッドを呼び出す為には super.setSize(width,height)
のように書きます。
フィールドをオーバーライドすることは、スーパークラスのフィールドを隠すことになります。
これを、フィールドのハイディング( hiding )と呼びます。
○補足
オーバーライド……上書き定義
オーバーロード……多重定義
○補足2
final フィールド、final メソッドはオーバーライド出来ません。
同様に、final クラスは継承できません。
●例題12-2-2 オブジェクトの多態性。
○プログラム
class Rectangle{
int width;
int height;
void setSize(int width,int height){
this.width=width;
this.height=height;
}
}
class NamedRectangle extends Rectangle{
String name;
NamedRectangle(String name){
this.name=name;
}
void setSize(int width,int height){
this.width=-width;
this.height=-height;
}
public static void main(String[] args){
NamedRectangle nr=new NamedRectangle("ATHENS");
Rectangle r=nr;
r.setSize(123,45);
System.out.println(r.width);
System.out.println(r.height);
//System.out.println(r.name); //error
System.out.println("-----");
System.out.println(nr.width);
System.out.println(nr.height);
System.out.println(nr.name);
}
}
○実行結果
D:\atsushi\Java\P62>java NamedRectangle
-123
-45
-----
-123
-45
ATHENS
-- Press any key to exit (Input "c" to continue) --
○解説
サブクラスのインスタンスは、スーパークラスのインスタンスに代入できます。
そして、スーパークラスのインスタンスとして扱うことが出来ます。
つまり、アクセスできるのはスーパークラスのフィールドやメソッドであり、
サブクラスのフィールドやメソッドにはアクセスできません。
ただし、オーバーライドしたフィールドやメソッドは、
上書きした新しいサブクラスのフィールドやメソッドにアクセスします。
●例題12-2-3 サブクラスのインスタンスを代入されたスーパークラスのインスタンスをサブクラスにキャストする。
○プログラム
class Rectangle{
int width;
int height;
void setSize(int width,int height){
this.width=width;
this.height=height;
}
}
class NamedRectangle extends Rectangle{
String name;
NamedRectangle(String name){
this.name=name;
}
void setSize(int width,int height){
this.width=-width;
this.height=-height;
}
public static void main(String[] args){
NamedRectangle nr=new NamedRectangle("ATHENS");
Rectangle r=nr;
r.setSize(123,45);
System.out.println(r.width);
System.out.println(r.height);
if(r instanceof NamedRectangle){
System.out.println(((NamedRectangle)r).name);
}
System.out.println("-----");
System.out.println(nr.width);
System.out.println(nr.height);
System.out.println(nr.name);
}
}
○実行結果
D:\atsushi\Java\P63>java NamedRectangle
-123
-45
ATHENS
-----
-123
-45
ATHENS
-- Press any key to exit (Input "c" to continue) --
○解説
25. if(r instanceof NamedRectangle){
instanceof 演算子は、
r が NamedRectangle クラスのインスタンスを指していれば true を、
r が NamedRectangle クラスのインスタンスを指していなければ false を返します。
r は Rectangle クラスの変数ですが、
NamedRectangle クラスのインスタンスを指しているので、
r instanceof NamedRectangle は true です。
*=*=*=*=*=*=*=*=*=*= 多態性(ポリモルフィズム) *=*=*=*=*=*=*=*=*=*=
書籍「決定版 はじめてのC++/塚越一雄」では以下のように述べています。
一般にオブジェクト指向プログラミングというと、
カプセル化によるデータの隠蔽や継承だけが強調される傾向にある。
しかし、この多態性こそがオブジェクト指向プログラミングの威力が発揮される部分である。
いま私が読んでいる「Java言語プログラミングレッスン/結城浩」では、
多態性の扱いはぞんざいですが、塚越一雄氏によれば重要らしい。
らしい……というのは、理由は分かるんだけど、使用経験がないから(笑)。
多態性の威力が発揮されるのは、例えば以下のようなプログラムである。
class A{
……
show(){ …… }
}
class B extends A{
……
show(){ …… }
}
class C extends A{
……
show(){ …… }
public static void main(String[] args){
B b=new B();
C c=new C();
A a=b;
a.show();
a=c;
a.show();
}
}
一度目の a.show() は B クラスの show() を、
二度目の a.show() は C クラスの show() を呼び出します。
このように、全く同じプログラムにもかかわらず、
代入されているインスタンスにより、その振る舞いが変わります。
これが、多態性の意味です。
C++言語ではややこしく感じた多態性ですが、
Java言語では細かいところが改善され、
直感的で、かなり分かり易くなっていると思います。
●例題12-2-4 アクセス制御。
○プログラム
class Rectangle{
private int width;
private int height;
int getWidth(){
return width;
}
int getHeight(){
return height;
}
void setSize(int width,int height){
this.width=width;
this.height=height;
}
}
class NamedRectangle extends Rectangle{
private void method(){
setSize(123,456);
System.out.println(getWidth());
System.out.println(getHeight());
}
public static void main(String[] args){
NamedRectangle nr=new NamedRectangle();
//System.out.println(nr.width); //error
//System.out.println(nr.height); //error
nr.method();
}
}
○実行結果
D:\atsushi\Java\List12-10>java NamedRectangle
123
456
-- Press any key to exit (Input "c" to continue) --
○解説
private 修飾子を付けて宣言したフィールドやメソッドは、他のどのクラスからも参照できません。
アクセス制御の詳細は「パッケージ」で解説します。
Java言語のアクセス制御は継承というよりパッケージの為にあるものですから。
●例題12-2-5 抽象メソッド。
○プログラム
abstract class Player{
public abstract void play();
public void loop(int n){
for(int i=0;i<n;i++){
play();
}
}
}
public class TextPlayer extends Player{
String text;
public TextPlayer(String text){
this.text=text;
}
public void play(){
System.out.println(text);
}
public static void main(String[] args){
TextPlayer player=new TextPlayer("こんにちは");
player.loop(3);
}
}
○実行結果
D:\atsushi\Java\List12-12>java TextPlayer
こんにちは
こんにちは
こんにちは
-- Press any key to exit (Input "c" to continue) --
○解説
2. public abstract void play();
本体が無いメソッドのことを抽象メソッドと呼びます。
メソッド本体とは { と } でくくられた部分のことです。
抽象メソッドには abstract を付けなければなりません。
このプログラムは関数原型宣言に見えますが、Java言語に関数原型宣言はありません。
抽象メソッドを一つでも持つクラスを抽象クラスと呼びます。
抽象クラスにも abstract を付けなければなりません。
抽象クラスのインスタンスは作れません。
抽象メソッドはオーバーライドされることを期待しています。
抽象メソッドをオーバーライドしない限り、そのクラスも抽象クラスになります。
○補足
C++言語では多態性を利用するのに(純粋)仮想関数を必要としました。
純粋仮想関数を持つクラスが抽象クラスでしたね。
しかし、Java言語では多態性を利用するのに抽象メソッドは不要なので、
あまり活躍の場は無いように思われます。
ただサブクラスに、どのようなメソッドを宣言しなければならないかということを強要しているだけです。
利点はサブクラスのメソッドの名前が揃う為に、
プログラムが読みやすくなることぐらいでしょうか。