前回に続いて「継承」について解説します。
継承関係におけるメンバアクセス
ポイント!protectedアクセス指定
スーパークラスはサブクラスの情報を知る必要はありませんし、利用することもできません。
逆に、サブクラスではスーパークラスのメンバをすべて持っている、と考えられます。
しかし、持っていてもアクセスできないメンバもあります。
privateなメンバは「そのクラス内のみ」に有効なので、たとえSalarymanがHumanクラスを継承していても、Humanクラスのprivateなフィールド、nameとかage、heightにはアクセスできません。
やはりアクセスしようとするとコンパイルエラーになります。
しかし、「すべてに公開したくはないが、継承しているような特別なクラスには公開しても良い」という場合も考えられます。まさにそういうアクセス指定があり、それが「protected」というアクセス指定子です。
【protectedキーワード】
public class SuperClass{ private int x; protected int y; }
続いて、スーパークラスを継承したサブクラスを作成します。
そして、protectedなデータにアクセスしてみます。
public class SubClass extends SuperClass{ public void foo( ){ x = 100; y = 200; System.out.println("protectedなデータにアクセスできました"); } public static void main(String[ ] args){ SubClass a = new SubClass( ); a.foo( ); } }
3行目でエラーが出ることを確認します。
privateで宣言されたフィールドxはアクセスできず、エラーになります。
SubClassクラスの3行目をコメントにし、エラーを取り除きます。
今度は、SuperClassクラスのprotectedデータにアクセスできるかどうか試してみます。
SubClassクラスを実行します。
実行結果
protectedなデータにアクセスできました
protectedで宣言されたフィールドyは、サブクラスSubClassからアクセスできます。
また、yはSuperClassクラスを継承していない、何の関係もないクラスからはアクセスできません。
クラスのフィールドはすべてprivateが基本ですが、場合によってはこのprotectedを使うこともあります。
ゲッター、セッターを経由するのは大変なのでそのときは便利です。
ただし、よほど理由がないときにはprotectedでさえ使わず、基本通りクラスのフィールドは全部privateにしておきましょう。
ポイント!クラス階層とObjectクラス
あるクラスAを継承したクラスBがあったとき、Bをまた他のクラスCが継承することもできます。
その場合Cは、ABCすべてのクラスで宣言されたメンバを持ちます(AやBで宣言されたものは、当然privateではアクセスできません)。
すると、たくさんのクラスを作ると継承関係で大きな構造ができます。
こちらの図では何か他のクラスを継承したものと、何も継承していないクラスがあります。Javaのコンパイラは何も継承していないクラスをコンパイルするとき、自動的にObjectクラスというクラスを継承させます。
そのため、
public class Hoge{ }
となっているクラスは
public class Hoge extends Object{ }
と同じ意味になります。
すると、すべてのクラスの頂点にObjectクラスが存在する、という構造になります。
すべてのクラスはObjectクラスの機能を使うことができます。
これは、多様性という性質と共に使うとかなりのメリットを持ちます。
多重継承
あるクラス「を」たくさんのクラス「が」継承している、ということはまったく問題ありません。
Objectクラスなどはそれこそ無数のクラスから継承されています。
逆に、あるクラス「が」複数のクラス「を」継承する事を「多重継承」と呼んでいます。
しかし、Javaではこの多重継承は禁止されています。
なぜ多重継承が認められていないのでしょうか?
例えばクラスAとクラスBがあり、これを両方継承したクラスCがあったとします。
AとBに同じメソッドfoo( )があったらどうなるでしょうか。
クラスCのオブジェクトがfoo( )を使おうとしたとき、どちらを使ったら良いのか分からなくなってしまいます。
こういう状況を「名前解決ができない」と言います。
さらにもっと複雑な場合を見ましょう。
クラスAをクラスB、クラスCが継承している場合に、この2つをクラスDが継承した場合です。
これをダイアモンド型継承(菱形の継承)というのですが、この場合はクラスAのメンバすべてをクラスB、クラスCが持つことになります。
するとクラスDからはクラスAの持っているメンバが全て2つになってしまうのです。
このような問題があり、Javaでは多重継承はできません。
クラス間の関係
アプリケーションを作成するためのプログラムでは、たくさんのクラスが出現します。
継承も、クラスの数を増やします。
クラスの数が多くなるのは、悪いことではありません。
大きなアプリケーションを一度に作るよりも、小さな部品の集まりを作ろうというのがオブジェクト指向のポリシーです。
しかし、クラスの数が増えたときには、それらの関係をしっかりと把握しておく必要があります。
例えば、前に出てきたHumanクラスとSalarymanクラスです。
これらは継承関係にありますが、他にも気になるクラスがあります。
Human.java
public class Human { private String name; private int age; private double height; …
Salaryman.java
public class Salaryman extends Human{ private int salary; private int bonus; …
Stringクラスがありますね。
HumanクラスではStringクラスの機能を使っていますが、これは継承しているわけではありません。
「HumanとStringの間の関係」は、「HumanとSalarymanの間の関係」とどう違うのでしょうか?
クラス間の関係には、主に以下の3つがあると考えられています。
- is-a関係(継承)
- has-a関係(包含)
- use(使用)
is-a関係
ここでいうと、SalarymanクラスとHumanクラスの関係です。
「Salaryman is a Human」であり、「Salaryman has a Human」ではありません。
クラスAがクラスBを継承するとは、クラスAがクラスBのメンバすべてを持ったのと同じになります。
そのうえで、追加したいメンバをクラスAに追加していくのです。
SalarymanクラスはHumanクラスの特別なケースです。
すべてのサラリーマンが人間ですが、すべての人間がサラリーマンとは限りません。
is-a関係は特殊化であると言えます。
is-a関係を実現するには継承を用います。
has-a関係
ここでは、HumanクラスとStringクラスの関係です。
「Human has a name」であり、これはStringがHumanの一部を構成しているときに用います。
has-a関係は、クラスのオブジェクトをフィールドに持つことで実現されます。
use
これも重要な概念です。
HumanクラスとSalarymanクラスの間にはなかったので、例えば次のようなクラスがあるとしましょう。
public class Car extends Vehicle { private Engine myEngine; public void drive( ){ Signal sign = new Signal( ); //信号がある! if(sign.isBlue( )) … else … } }
「Car is a Vehicle(乗り物)」
「Car has an Engine」
「Car use Signal(信号)」
と考えられます。
エンジンは車の一部ですが、信号は車の一部ではありません。
エンジンは車が存在する間は同様に存在しますが、信号は車と無関係なので、どちらかがなくなっても相手に影響はありません。
このように、自分を構成する要素として「持っている」のがhas-a関係で、それに対してあくまでも一時的に使っているだけなのがuseです。
プログラム的には、メソッドの中で宣言されているものがそうです。
useのことを「依存関係」とも言いますが、これはメソッドの中でオブジェクトを宣言することによって実現されます。
アプリケーションを開発中に、クラスがたくさん現れた場合はこういったことを考えます。
この基準を用いてどのクラスとどのクラスがどういう関係にあるのかを検討するのです。
この図はUMLという手法を使ってクラス間の関係を書いたものです。
図の描き方はJavaの仕様とはまったく関係ありませんが、矢印の使い方はよく使われるものと同じです。
現時点ではこの3つの関係について、違いを明確に把握しておきましょう。
ポイント!UML(Unified Modeling language)とは
アプリケーションを開発するときに重要なのがモデリングという作業です。
顧客の業務など、現実世界のアナログで無限のものをデジタルかつ有限な世界で取り扱うために、単純化して視覚化します。
このモデリングの表記法を統一したのが「UML(Unified Modeling Language)」です。
上記の図では、is-a関係である継承関係(extends)はサブクラスからスーパークラスに向かって実線の三角矢印で表現します。has-a関係は実線のひし形矢印で表現します。
useは破線で表現します。
「継承 Part3」へ続きます
独学で挫折しそうになったら、オンラインプログラミングスクール