Javaにおけるクラスのアップキャストについて分かったこと
こんばんは、simulo_yuiです。
simulo_yui (@simulo_yui) | Twitter
主にタイトルの通りのことについて語ります。
以下の記事を読んでいて不思議に思う部分があったことがきっかけです。
その部分とはこちらです。
サブクラスのオブジェクトをスーパークラス型のオブジェクトでアップキャストする際、注意点があります。スーパークラスとサブクラスに、同じ名前のフィールド変数やメソッドがある場合には、フィールド変数はスーパークラス、メソッドはサブクラスが優先される、という点です。
この文章中に出てくる単語で分からないものがあったとしたら、上記の記事を読むことを推奨します。
なぜフィールドとメソッドに対して違う扱いがされるのでしょうか?上記の引用文の事柄について初めて知った方の多くがその疑問を持つと思います。本記事はそれについて調べました。
本編
Javaにおいて(おそらくは多くのオブジェクト指向言語において)クラスの継承はどのように内部で実現されてるのでしょうか。以下に図を用意しました。
継承される側のクラスをスーパークラス、する側のクラスをサブクラスと呼びます。
サブクラスはその最初の部分にスーパークラスのコピーを持っています。継承時にスーパークラスからサブクラスへとコピーされます。オーバーライドではないサブクラス独自のメソッドやサブクラス内で宣言される変数たちはそのコピーのあとに追加されます。この追加された部分を以降、便宜上非共有部分と呼びます。サブクラスからスーパークラスへとアップキャストをするときは、このコピーの部分が抜き取られます。スーパークラスへキャストされたサブクラスからは非共有部分に入っている情報を得ることはできません。
※抜き取られる、は適切な表現ではありません。その部分以外へのアクセスが制限される、という表現が適切だと考えられますが、直感的なので用いています。
サブクラスからスーパークラスへのキャストは行うことができますが、一般的にはスーパークラスからサブクラスへのキャスト(ダウンキャスト)は行うことができません。サブクラスはスーパークラスが持っているすべてのフィールド変数とメソッドを持っていることを保証できますが、スーパークラスがサブクラスの持っているすべての変数とメソッドを持っていることが保証できないためです。
クラスはフィールドとメソッドから構成されています。一般にフィールド変数はプリミティブ型なら変数の値、参照型なら変数のアドレスへの参照を持っています。対して、クラスはメソッドそのものを格納しません。メモリ内のどこかほかの場所に格納されているメソッド本体への参照を持っています。上の図における参照の矢印を見て下さい。
サブクラスにおいて、スーパークラスの持っているメソッドのオーバーライドを行った場合、コピーされた部分にあるスーパークラスのそのメソッドへの参照がオーバーライドされたメソッドへの参照に切り替わります。メソッドの名前が同じでも中身(参照先)が違うのです。こちらも上の図における関数ポインタb、メソッドb、メソッドb'を見ていただきたいです。
また、これは推奨されるコードではないですが、サブクラス内においてスーパークラスと同名の新しい変数を宣言した場合、その変数は非共有部分に置かれます。この際、サブクラス内に同名の変数が二つあるような状況に思えますが、そこは闇の処理が行われてサブクラスとして扱う際には非共有部分に入っている変数を使われるようになります。
ここで私が最初に疑問に思ったことに戻ります。サブクラスからスーパークラスへのキャストが行われる際、同名のフィールド変数やメソッドがある場合はフィールド変数はスーパークラスのものが優先され、メソッドはサブクラスのものが優先されるのはなぜでしょうか?
アップキャストが行われる際、サブクラス内にあるスーパークラスのコピーの部分が抜き取られるという話をしました。スーパークラスとサブクラスに同名の変数があっても、その変数は非共有部分に入っているため切り取られず、コピー部分に入っていたスーパークラスのコピーの変数(=スーパークラスのものと同じ変数)が抜き取られるのです。対してスーパークラスとサブクラスに同名のメソッド(つまりオーバーライドされたメソッド)が存在する場合でも、サブクラス内のオーバーライド後のメソッドへの参照はコピー部分に入っているため、サブクラスのメソッドがそのまま抜き取られます。
抜き取られたオーバーライド後のメソッドの中でサブクラス内で新しく宣言された非共有部分にあるスーパークラスに同名の変数がある変数が使用されている場合は、アップキャストされた後でもそのメソッド内では非共有部分にある変数を用います。これはその変数への参照先にあるメソッドの内部では非共有部分にある変数への参照が入っているためです。(サブクラスとして扱われているうちは非共有部分にある変数が使われるため。)
感想その他
ややこしいですね。