質問)
abstractを付与したクラスAがあると仮定します。
クラスAから、クラスBを継承させます。
クライアント側で、オブジェクトを生成する際に
クラスA 変数名 = new クラスB();
「変数として宣言しているクラスA」と「オブジェクト生成している(メモリ確保)クラスB」
と記述可能なメリットと理由がよくわかりません。
クラスA(absract)がオブジェクト生成できないため、
あえてクラスBを生成するってことなのでしょうか?
上の例だと、クラスBのメソッドは呼び出すことはできない筈なので。
サブクラスのアドレスを基底クラスの変数に格納しても問題ないってこと?
これは、基底クラスの領域しかアドレスを読み込めないってことなのでしょうか?
質問者様の疑問は、オブジェクト指向プログラミングをする上でよくある疑問だと思います。
すべてに言及すると本一冊以上のボリュームになってしまいますので
ここではヒントになりそうなコメントをさせていただきます。
なお、「オブジェクト脳のつくり方」という本が、このテーマをわかりやすく説明していると思います。
また、以下の説明ではabstractクラスのほかにinterfaceも説明に使いますが意味は同じです。
(それ自体で実体化できない・継承した実装クラスを使うなど)
まず「サブクラスのアドレスを基底クラスの変数に格納しても問題ないか?」ですが、
問題ないと考えてください。
「基底クラスの領域しかアドレスを読み込めない」ことになりますが、それで構いません。
なぜなら、基底クラスで定義しているメソッド/APIしか使わないために、あえてそうしているからです。
クライアントのコードでは
クラスA 変数名 = new クラスB(); ・・・(1)
とした後、変数名に対して、いろいろなメソッドを呼び出す使い方をするでしょうが、
これ以降ではクラスBのことは意識しないようにしています。
あくまでクラスAのメソッド/APIのみを使ってコーディングをするようにします。
「なぜ機能が多いクラスBでなく機能が制限されたクラスAを使うのか?」というのが次に感じる疑問だと思います。
それは、
・クラスBの機能をすべてクライアントに見せることはしたくない → 「隠蔽」
・クラスAを継承した別の実装クラスCを使いたくなったときに、上記(1)以降のクライアントコードをいじる必要がない → 「ポリモーフィズム」
というメリットがあるためです。
オブジェクト指向の入門本などを読むと、よく自動車や動物などの例を出して上記隠蔽やポリモーフィズムを説明していますが
他に具体例を知りたい場合は、Java言語自体のListインターフェースやその実装クラスであるArrayList,Vector,LinkedListクラスなどを勉強すると頭に入りやすいかもしれません。
クラスA 変数名あ = new クラスB();
クラスA 変数名い = new クラスC();
変数名あと変数名いは、同様のクラスの型として扱えるので便利です。
クラスAというのは「型名」で、生成するのはnew で書いたクラスです。
>クラスA(absract)がオブジェクト生成できないため、
>あえてクラスBを生成するってことなのでしょうか?
間違ってます。
>サブクラスのアドレスを基底クラスの変数に格納しても問題ないってこと?
はい
>これは、基底クラスの領域しかアドレスを読み込めないってことなのでしょうか?
間違っています。
サンプルで説明します。
○1. 前提
javax.swing.AbstractButton => abstract class
JButton => javax.swing.AbstractButton を継承した class
JMenuItem => javax.swing.AbstractButton を継承した class
JToggleButton => javax.swing.AbstractButton を継承した class
○2. 《AbstractButton型を使わない場合》
boolean configured; //何か適当な条件。 JButton[] jbuttonList; JMenuItem[] jmenuItemList; JToggleButton[] jtoggleButtonList; void setEnabledIfConfigured(JButton button) { if(configured) { button.setEnabled(true); jbuttonList[0] = button; } } void setEnabledIfConfigured(JMenuItem button) { if(configured) { button.setEnabled(true); jmenuItemList[0] = button; } } void setEnabledIfConfigured(JToggleButton button) { if(configured) { button.setEnabled(true); jtoggleButtonList[0] = button; } }
○3. 《AbstractButton型を使う場合》
boolean configured; //何か適当な条件。 AbstractButton[] buttonList; void setEnabledIfConfigured(AbstractButton button) { if(configured) { button.setEnabled(true); buttonList[0] = button; } }
○4. 解説
《AbstractButton型を使わない場合》では concrete class 1個毎に別々に定義が必要です。
《AbstractButton型を使う場合》では concrete class が何個あっても1個だけの定義で十分です。
「abstract class がオブジェクト生成できないから」ではなく、
「concrete class オブジェクトを abstract class 型として扱いたいから」、
abstract class 型の変数に代入するのです。
Java プログラムではオブジェクトがどのアドレスに格納されているかは扱いません。
○参考情報
Java 入門 | 多態性(ポリモーフィズム) - http://msugai.fc2web.com/java/polymorphism.html
質問者様の疑問は、オブジェクト指向プログラミングをする上でよくある疑問だと思います。
すべてに言及すると本一冊以上のボリュームになってしまいますので
ここではヒントになりそうなコメントをさせていただきます。
なお、「オブジェクト脳のつくり方」という本が、このテーマをわかりやすく説明していると思います。
また、以下の説明ではabstractクラスのほかにinterfaceも説明に使いますが意味は同じです。
(それ自体で実体化できない・継承した実装クラスを使うなど)
まず「サブクラスのアドレスを基底クラスの変数に格納しても問題ないか?」ですが、
問題ないと考えてください。
「基底クラスの領域しかアドレスを読み込めない」ことになりますが、それで構いません。
なぜなら、基底クラスで定義しているメソッド/APIしか使わないために、あえてそうしているからです。
クライアントのコードでは
クラスA 変数名 = new クラスB(); ・・・(1)
とした後、変数名に対して、いろいろなメソッドを呼び出す使い方をするでしょうが、
これ以降ではクラスBのことは意識しないようにしています。
あくまでクラスAのメソッド/APIのみを使ってコーディングをするようにします。
「なぜ機能が多いクラスBでなく機能が制限されたクラスAを使うのか?」というのが次に感じる疑問だと思います。
それは、
・クラスBの機能をすべてクライアントに見せることはしたくない → 「隠蔽」
・クラスAを継承した別の実装クラスCを使いたくなったときに、上記(1)以降のクライアントコードをいじる必要がない → 「ポリモーフィズム」
というメリットがあるためです。
オブジェクト指向の入門本などを読むと、よく自動車や動物などの例を出して上記隠蔽やポリモーフィズムを説明していますが
他に具体例を知りたい場合は、Java言語自体のListインターフェースやその実装クラスであるArrayList,Vector,LinkedListクラスなどを勉強すると頭に入りやすいかもしれません。
ご回答ありがとうございます。
>「基底クラスの領域しかアドレスを読み込めない」ことになりますが、それで構いません。
>なぜなら、基底クラスで定義しているメソッド/APIしか使わないために、あえてそうしているからです。
少し納得できました。基本は、クラスAのメソッド/APIのみアクセス可能だが、abstractを修飾したメソッドは、
クラスBのメソッドを参照するようなしかけを施しているですね。
>・クラスAを継承した別の実装クラスCを使いたくなったときに、上記(1)以降のクライアントコードをいじる必要がない → 「ポリモーフィズム」
確かに、クラスのとっかえが効きそうですね。
URLはダミーです
> 上の例だと、クラスBのメソッドは呼び出すことはできない筈なので。
これがメリットです。つまり、クラスBのコンストラクタを呼び出す行を除けば、このプログラムはクラスAにしか依存していないため、
クラスAを継承した別のクラスCを別途作った場合、このクラスCを使うように変更することが容易になります。
例えば、実行時に
ClassA aClass;
if (someCondition){
aClass = new ClassB();
} else {
aClass = new ClassC();
}
のように状況に応じ、実装クラスを変えることもできます。
このように、実装ではなく、インターフェイスにのみ依存させることで、プログラムの柔軟性を高め再利用しやすくなります。
もちろんそれができるように、抽象クラスやインターフェイスを設計することが重要ですが。
おそらくその問いに関するこたえは「デザインパターン」を学習すると効率よく理解できると思います。
サルでもわかる 逆引きデザインパターン 第1章 はじめてのデザインパターン デザインパターンとは
主に「振る舞いに関するパターン」各種に利用されていますので、一読しておくことをお勧めします。
ご回答ありがとうございます。
>「基底クラスの領域しかアドレスを読み込めない」ことになりますが、それで構いません。
>なぜなら、基底クラスで定義しているメソッド/APIしか使わないために、あえてそうしているからです。
少し納得できました。基本は、クラスAのメソッド/APIのみアクセス可能だが、abstractを修飾したメソッドは、
クラスBのメソッドを参照するようなしかけを施しているですね。
>・クラスAを継承した別の実装クラスCを使いたくなったときに、上記(1)以降のクライアントコードをいじる必要がない → 「ポリモーフィズム」
確かに、クラスのとっかえが効きそうですね。