以前質問して、答えていただいた
http://q.hatena.ne.jp/1150644538
に関係していることだと思いますが、サブクラスの実体を
作成するとき、特に指定がなければスーパークラスの
引数のないコンストラクタが呼ばれます。
この理由が、"スーパークラスのコンストラクタはサブ
クラスに継承されない"からとのことですが、なぜ継承
されないのでしょうか?ご存知の方おりましたら、ご教授
下さいますよう、宜しくお願いします。
#推測なのでポイント不要です。開封分のポイントはごめんなさい。
まず、メソッドが継承されて嬉しいのは、「多態性」が利用できるからです。例えば、特定のコンポーネントで起きた例外を表すために、次のようなクラスを作ったとします。
import java.awt.Component; public ComponentException extends Exception { protected Component source; public ComponentException(String message, Component c) { super(message); if (c == null) throw new NullPointerException(); this.source = c; } /** * 例外発生元のコンポーネントを取得します。 */ public Component getSource() { return this.source; } }
この場合、スーパークラスの性質 (具体的にはクラスメンバ) が継承されるので、個々のサブクラス (この例で言えば、自作した ComponentException を始め、大量に存在する Exception のサブクラス) を意識せずに扱えるのが嬉しいわけです。例えばこんな場面。
catch (Exception e) { e.printStackTrace(); }
他にも、各種のデザインパターンで多態性が利用されていることと思います。
一方、コンストラクタが仮に継承されたとしても、メソッドのように「サブクラスを意識しない」わけにはいきません。コンストラクタを呼ぶときには、サブクラス自体の名前を明記しなければならないわけですから。
にもかかわらずコンストラクタが継承されるとすると、スーパークラスのコンストラクタを利用して、次のように書けてしまうことになります。
Exception e = new ComponentException("指定ミスですよ!");
このままでは、ComponentException でありながら、メンバ変数 source は初期化されないままになってしまいます (ここでいう初期化は、初期値 null のセットではなく、必ずマトモな値がセットされることを保証するという、コンストラクタの役割の方を指しています)。
ちょっと考えると、親クラスにあるコンストラクタをすべてオーバーライドする (そして、使われたくないコンストラクタでは何らかの例外を投げるようにする) ことで防げるようにも思えます。しかし、後でスーパークラスにコンストラクタが追加された場合を考えると、どうしても「初期化がちゃんと終わっていないオブジェクトができてしまう」ことを避けるのは実質不可能になってしまいますよね。
まとめると
ということから、継承されない仕様になっているのだと思います。
95%憶測です。
問題は、
メソッドは暗黙でオーバーライドを指定しなくても、public や protected のメソッドはサブクラスで使えます。
でもコンストラクタは明にオーバーライドを指定しなければ、サブクラスで使えません。この違いはどこから来ているのでしょうか?
ということだと思います。
メソッドとコンストラクタの違いは、ここ↓にあります。
http://java.sun.com/docs/books/jls/third_edition/html/execution....
コンストラクタの記述や、Class.forName で新しいインスタンスを生成する時に、そのクラスの情報(というのは java.lang.Class クラスのインスタンス)が、そのコンテクストのClassLoader(およびその親ClassLoader達)によって読みこまれているのかどうか? のチェックが入ります。
もし初めて出てきたクラスであれば、委譲親を辿って一番上位の ClassLoader から下に向かってそのクラスを見つけようとします。
その時にクラスに明に定義されているコンストラクタのパラメータとの照合があるはずです(ここが自信なし……)。
コンストラクタの呼び出しは単なるメソッド呼び出しだけではなくて、クラスを探すという行為も含まれているため、スーパークラスのコンストラクタが継承されると不都合がある、というシナリオの例を書こうとしたのですが……。あまりにも嘘くさくなってしまって、逆に説得力がなくなってしまって困りました。
ただ、そのあたりの違いが仕様の違いを生んでいるのではないかと、思うわけです。
ちなみに、
class A { B = null; C = null; }
とか書いてコンパイルすると、B や C が無いとエラーになります。
ところが、実行時に B.class や C.class というファイルが無い場合は、エラーにならないはずです(古い記憶を頼りにしてます。もしかしたら違うかもしれません。今回テストしてないで書いてますが悪しからず)。
この記憶があるので、ClassLoader が Classクラスのインスタンスを作ろうとするのは、コンストラクタが実行された時点のはずだ、と考えたわけです。
# ますます憶測になってきたので、この回答もポイント不要です。
>→(★)どちらの初期化もこの記述で行えるとしたら??
>なんてことを考えてしまいます。
>この場合、どんな不都合が起きるのだろうか?
行える場合についてだけ考えるなら、大きな不都合は起きないかもしれません。下記のようなコンストラクタを書く手間が減って、少し楽になるでしょう。
public ComponentException(String message) { super(message); }
しかし、回答 #1 で書いたような「行えない場合」に、そのことをコード上で示す方法がありません。仮に構文を追加して「親クラスのこのコンストラクタは使わないでね」という指示ができるようにしたとしても、親クラスにコンストラクタが追加された場合を考えるとアウトです。
これによる現実的な不都合としては、デザインパターンのうち Singleton や FactoryMethod の実装がやりづらくなります。これらのパターンでは「外部から勝手にインスタンス化されないように、コンストラクタを protected や private にする」ということが行われますが、ここにスーパークラスの public なコンストラクタが継承されると、外部から直接インスタンス化できてしまいます。つまり、間違った使い方をしたコードが書けてしまうのです。
Java の言語仕様を思い出してみると、ある程度の実装ミスをコンパイルエラーとして検出できるように設計されていることがわかると思います。俗に言う「強い型付け」をはじめ、一見面倒なだけの throws 宣言や、変数が初期化されていないのに使おうとした場合のエラー検出などもその一環です。
question:1150644538 の質問で quantia さんが書かれているように、「そうあるべきだ」という考え方によるものなのではないでしょうか。
ところで、#2 の回答で quantia さんが引用されているページに「コンストラクタはクラスのメンバではない」というくだりがあります。ではどこのメンバなのか。
Java の先祖にあたる Smalltalk では、「Class オブジェクトのインスタンスメソッド」という位置づけになっていました。Java 風に書くなら、こんなイメージです。
ComponentExceptionClass c = ComponentException.class; Exception e = c.new("背景色がサイケすぎです。", label);
もしかすると、この辺の歴史的な背景も影響しているかもしれません。
ご回答ありがとうございます!
>catch (Exception e) {
> e.printStackTrace();
>}
例えばどんな例外が発生しても、同じ対処を
取りたいならこんなふうに、クラスの多態性の
性質が役に立つのですね。
一方、
>Exception e = new ComponentException("指定ミスですよ!");(★)
↑これが、スーパークラスのコンストラクタを継承
するとなるとスーパークラス側の初期化ばかり
行われ、サブクラスの初期化が行われない。。
それでは初期化が完全じゃないから困る!
ということなら納得いきます。
→(★)どちらの初期化もこの記述で行えるとしたら??
なんてことを考えてしまいます。
この場合、どんな不都合が起きるのだろうか?
残念ですが私にはまだ想像できません。
もう少しご意見いただけるとありがたいです。