用語は大切です。ここらで一休みして、混乱しやすい用語の復習を軽くしておきましょう。「もうばっちり!」という方は読み飛ばしてください。
オブジェクトとインスタンス
「オブジェクトとは何ぞや、インスタンスとは何ぞや、オブジェクトとインスタンスはどうちがうのか?」古今東西あらゆる人たち(?)が悩む問題のようで、いろいろな考え方があるようです。
例えば第9章の「長方形を作ろう」の中の絵で、賢い長方形とボーッとした長方形がありましたが、これは私の中ではオブジェクトをイメージしたものになります。詳しくは第9章をご覧ください。
この絵の前に、メモリ内をイメージした図がありますが、ここでnewによりパッと生成された箱、これはインスタンスのつもりです。中に2辺の長さというデータが入っています。
混乱の元になってもいけないので、ここまでどちらもオブジェクトで統一してきました。さしあたりインスタンスもオブジェクトも、同じと考えても大丈夫でしょう。
型
変数には型があります。型にはどんなものがあるかをここで整理しておきましょう。
基本データ型はプリミティブ型ともいいます。気をつけてほしいのは、論理値を扱うbooleanが、数値型と完全に別になっていることです。trueかfalseのどちらかしか値としてとれません。trueのかわりに1、falseのかわりに0のように、数値で代用することはできません。
空型は空参照nullの型で、あまり気にしないでください。
参照型の変数には、nullかオブジェクトへの参照が代入できるのでしたね。第7章の図をもう一度出しておきます。
「変数の型」と「オブジェクトのクラス」
AもBもクラス名とします。左の図の場合、変数xの型はAになります。上の型の分類では、参照型の中のクラス型になります。変数の型はコンパイル時に決まってしまいます。
次にクラスBのオブジェクトを生成し、その参照をxに代入しています。xが参照しているので、黄色い部分を「オブジェクトx」と呼ぶことにします。これはクラスBから生成されたものなので、「オブジェクトxのクラスはBである」、ということになります。変数xの参照するオブジェクトは、クラスBから生成された、という意味です。
次にクラスCから生成されたオブジェクトを、xに代入しています。今度xは新しくできた青いオブジェクトを参照しています。この図の状態は、オブジェクトxのクラスはCである、ということになります。
このようにオブジェクトxのクラスは実行時にころころ変更できます。コンパイル時に決定したxの型Aに対比させ、オブジェクトxのクラスを、xの実行時型などということもあるようです。
BやCはAのサブクラスでなければならない、とかポリモーフィズムについては、第13章以降で解説してあります。
空文字と空参照
どちらの「空」も「くう」と読みます。
空参照は「null」のことです。nullはどんな参照型の変数にも代入できる特別な参照値です。どのオブジェクトも指していない、ということで、次のようにメソッドの呼び出しをすると実行時に例外が発生します。
A x = null; System.out.println(x.toString());
空文字は’\u0000’のことで、char型のデフォルト値になっていますが、C言語のように、文字列の最後を表す、といった働きはしません。
オーバーロードとオーバーライド
どちらもオーバーがついてややこしいこと。もう一度軽く復習しておきます。
同じメソッド名でも、引数の型や個数、順序が異なると、全くちがうメソッドと見なされます。メソッドのオーバーロードですね。第11章でやりました。下の図のように、同じ「ひろみ」でも、うしろに何をつけて呼ぶかで区別が付くのと同じです。「ひろみ」がメソッド名、「さん」や「くん」が引数の並びに対応します。
一方オーバーライドは上書きの意味で、メソッド名や戻り値の型のみでなく、引数についても本質的に同じものがサブクラスで定義されていると、元のメソッドは上書きされて跡形もなくなるのです。第13章、14章でやりました。下の図のように、スマートなひろみさんが、ふとっちょのひろみさんに押しつぶされていなくなっています。どちらも同じひろみさんですが、スーパークラスのメソッドがスマートなひろみさん、サブクラスのメソッドがふとっちょのひろみさんと見てください。
どちらも「オーバー」がついてまぎらわしいので、オーバーライドの方は「上書き」と呼んだ方がいいようなかんじです。
継承が絡んでくると、またわかりにくくなるので、例を挙げておきます。スーパークラスAのメソッドmethodAを上書きしているのは、クラスBのmethodAでしょうか、クラスCのmethodAでしょうか。考えてみてください。
[クラスA]
public class A { protected int x = 5; public int methodA(int y) { return x - y; } }
[クラスB]
public class B extends A { public int methodA(int q) { return x * q; } }
[クラスC]
public class C extends A { public int methodA(double q) { return x * (int)q; } }
クラスBのmethodAは、戻り値の型、引数の並び(int型1個)ともにスーパークラスAのmethodAと一致するので、オーバーライド、つまり上書きになります。
クラスCのmethodAは、AのmethodAと引数の並びが異なるので、上書きにならずメソッドのオーバーロードの方になります。次のmainでの実行結果を見るとわかります。
[クラスMain]
public class Main { public static void main(String[] args) { B b = new B(); System.out.println(b.methodA(10)); C c = new C(); System.out.println(c.methodA(20) + " " + c.methodA(3.5)); } }
出力結果は次のようになります。最後の出力の二つの「c.methodA」は、最初のはクラスAで定義されたもの、二番目はクラスCで定義されたものが呼び出されることになります。
50
-15 15