オブジェクト指向
初心者

なぜアップキャストは安全で、ダウンキャストは危険なのか

More than 1 year has passed since last update.


はじめに

オブジェクト指向プログラミング初学者で、特にC言語のポインタを学習していない人を主なターゲットに、クラスキャストをなるべく簡単かつわかりやすく説明することを目的としています。


用語説明

今回の登場人物の紹介です。


アップキャストとは?

基底(親)クラス型の変数に派生(子)クラスのインスタンスを代入する際に行われる型変換です。


Derived.java

class Derived extends Base {

}


Main.java

// アップキャストして代入

Base base = new Derived();


ダウンキャストとは?

派生(子)クラス型の変数に基底(親)クラスのインスタンスを代入する際に行われる型変換です。

(アップキャストとは逆の代入。)


Main.java

// ダウンキャストして代入

Derived derived = new Base();


よくあるミスリード

入門書の多くは変数のことを「入れ物(容器)」と表現しています。

また、派生クラスは基底クラスの機能を持ちつつ新たな機能を追加したものとも解説されています。

これを受けて入れ物を想像すると以下のような発想になりませんか?

001.png

「派生クラスの入れ物に綺麗に基底クラスが収まるから問題なし」

とはなりません。


インスタンス化するとはどういうことか

クラスをインスタンス化するとはどういうことかを見てみましょう。


基底クラスのインスタンス化

まずは、基底クラスのインスタンス化についてです。


Main.java

// インスタンス化

Base base = new Base();

上記の処理はざっくりどのようなことをしているかというと、

1. メモリにBaseクラスのインスタンスが入るだけの空きを見つける

2. 「メモリのこの空間を使いますよ」とシステムにお知らせする

3. クラスの定義に沿って確保したメモリ空間を整地する

図にすると以下のような感じです。

002.png

枠内がメモリ全体で、赤丸部分が確保したメモリ部分(Baseクラスのインスタンス)です。


派生クラスのインスタンス化

次に派生クラスのインスタンス化についてです。


Main.java

// インスタンス化

Derived base = new Derived();

図にすると以下のような感じです。

003.png

緑の四角が確保したメモリ部分(Derivedクラスのインスタンス)です。

内部の影になっている部分は基底クラスの機能が詰まっている部分です。

派生クラスは基底クラスの機能を維持したまま機能拡張していますので、必ず基底クラスより大きくなります。

また、確保したメモリ空間には図のように基底クラスの機能を置く場所もちゃんと用意されています。


インスタンス化する際のクラス型はこうイメージする

new演算子の右側に登場するクラス型は以下のようにイメージすると後々理解がしやすくなります。

004.png

出来上がるインスタンスを「型」どった枠(クッキーをつくるときの型取りみたいなもの)

この枠内の空いている部分を塗りつぶすと綺麗に作図(インスタンス化)が出来る事に例えることができます。


代入するとはどういうことか

クラス型の変数にインスタンスを代入するとはどういうことか見ていきましょう。


代入は移動やコピーではない

インスタンスを他の変数へ代入した場合、実態はコピーされないところまでは理解していると思いますが念のため。

つまり、インスタンスはメモリ空間内を移動している訳ではありませんということです。

変数とは、インスタンスがメモリ空間のどこにいるのかを指す地図の役割があります。

それともう1つ重要な役割があります。次項に続く。


変数はそのクラス型分だけメモリを覗くことができる

変数を使用してフィールドやメソッドにアクセスするということは、その変数を通じてメモリを覗くことを意味しています。

前項よりクラス型はインスタンスの形をした穴が空いている枠に例えました。

変数も同様のイメージで解釈することができます。

005.png

変数は枠内の空いている穴の部分のメモリを覗く事ができます。

例えば、基底クラスのインスタンスが代入されている場合は、

006.png

ちょうどインスタンス化した際に整地したメモリ空間だけを覗く事ができます。

また、派生クラスのインスタンスが代入されている場合は、

007.png

派生クラスのインスタンスは緑の四角ですが、変数の穴から見るとまるで基底クラスのインスタンスのように見えます。

実際のところ、この見えている部分に基底クラスの機能が詰まっていますので、本当に基底クラスのように振舞う事ができます。


  • 結論

    「アップキャストは派生クラスのインスタンス中の基底クラスの機能を覗いているだけなので安全である」
    と言えます。


整地前のメモリを覗いてはいけない

ここがもう一方の本題のダウンキャストが危険な理由です。

派生クラス型の変数に基底クラスのインスタンスを代入した場合以下のようになります。

008.png

基底クラスのインスタンスは全て覗けるまでは良いですが、

その外側に「整地していないメモリ空間」まで見えてしまっています。

ダウンキャストをしたとしても、この赤丸部分の機能を使用する分にはエラーは発生しません。

しかし、整地前のメモリ空間にアクセスしてしまうと・・・

(言語処理系やその他色々な環境により、様々な酷い現象が発生します)

この整地前のメモリ空間とは、「派生クラスで追加した機能があるはずの領域」です。


  • 結論

    「ダウンキャストは覗いてはいけないメモリ空間を覗けるので危険がある」
    と言えます。


終わりに

この資料は新人研修で使用するために作成しました。

ですので、インスタンスはGCでメモリの位置が移動する事がある等細かいことには触れていません。

new演算子の機能ももっと複雑ですが・・・