参照型変数とインスタンスを別々に考える
前回紹介した「アップキャスト」をもう少し深く理解するため、同じクラスを題材に、参照型変数の色々な使用例を見ておきましょう。
/**
* 普通のクラス。
* (NormalSuperクラスと全く同じクラスです)
*/
class NormalSuper3
{
// int型変数のdataフィールドです。
int data;
/**
* フィールドを出力するメソッドです。
*/
void printData()
{
System.out.println( data );
}
}
/**
* NormalSuper3クラスから継承した、サブクラス。
* フィールドとメソッドを追加しています。
* (AddedSubクラスと全く同じクラスです)
*/
class AddedSub3 extends NormalSuper3
{
// int型変数のdataSubフィールドです。
int dataSub;
/**
* フィールドを出力するメソッドです。
*/
void printDataSub()
{
// このクラスで追加したフィールドを出力。
System.out.println( dataSub );
// ついでにスーパークラスのフィールドも出力。
System.out.println( data );
}
}
/**
* 実行用クラス。このクラスを実行してください。
*/
class AddedSub3Runner
{
public static void main( String[] args )
{
// インスタンスを1つ作り数値を入れます。
AddedSub3 refSub = new AddedSub3();
// 参照をスーパークラスの参照型へとアップキャストします。
NormalSuper3 refSuper = refSub;
// スーパークラスのフィールドに値を入れて出力します。
refSub.data = 100;
refSub.printData();
// 出力結果:
// 100
refSuper.printData();
// 出力結果:
// 100
// サブクラスのフィールドに値を入れて出力します。
refSub.dataSub = 1;
refSub.printDataSub();
// 出力結果:
// 1
// スーパークラスの参照型変数を通して、サブクラスの
// フィールド・メソッドを使うことはできません。
// refSuper.printDataSub();
// コンパイルエラー:
// AddedSub3Runner.java:74: シンボルを見つけられません。
// シンボル: メソッド printDataSub()
// 場所 : NormalSuper3 の クラス
// refSuper.printDataSub();
// ^
}
}
// AddedSub3Runner.java /** * 普通のクラス。 * (NormalSuperクラスと全く同じクラスです) */ class NormalSuper3 { // int型変数のdataフィールドです。 int data; /** * フィールドを出力するメソッドです。 */ void printData() { System.out.println( data ); } } /** * NormalSuper3クラスから継承した、サブクラス。 * フィールドとメソッドを追加しています。 * (AddedSubクラスと全く同じクラスです) */ class AddedSub3 extends NormalSuper3 { // int型変数のdataSubフィールドです。 int dataSub; /** * フィールドを出力するメソッドです。 */ void printDataSub() { // このクラスで追加したフィールドを出力。 System.out.println( dataSub ); // ついでにスーパークラスのフィールドも出力。 System.out.println( data ); } } /** * 実行用クラス。このクラスを実行してください。 */ class AddedSub3Runner { public static void main( String[] args ) { // インスタンスを1つ作り数値を入れます。 AddedSub3 refSub = new AddedSub3(); // 参照をスーパークラスの参照型へとアップキャストします。 NormalSuper3 refSuper = refSub; // スーパークラスのフィールドに値を入れて出力します。 refSub.data = 100; refSub.printData(); // 出力結果: // 100 refSuper.printData(); // 出力結果: // 100 // サブクラスのフィールドに値を入れて出力します。 refSub.dataSub = 1; refSub.printDataSub(); // 出力結果: // 1 // スーパークラスの参照型変数を通して、サブクラスの // フィールド・メソッドを使うことはできません。 // refSuper.printDataSub(); // コンパイルエラー: // AddedSub3Runner.java:74: シンボルを見つけられません。 // シンボル: メソッド printDataSub() // 場所 : NormalSuper3 の クラス // refSuper.printDataSub(); // ^ } }
NormalSuper3クラスとAddedSub3クラスは前ページで紹介したクラスと全く同じで、クラス名が違うだけです。
前ページの例との違いは、これらのクラスを使用している箇所(AddedSub3Runnerクラスのmain()メソッド内)です。
今回の例では、まずサブクラスの参照型変数に参照を受け取り、それをスーパークラスの参照型変数にアップキャストしている、という点です。
AddedSub3 refSub = new AddedSub3();
// 参照をスーパークラスの参照型へとアップキャストします。
NormalSuper3 refSuper = refSub;
// インスタンスを1つ作り数値を入れます。 AddedSub3 refSub = new AddedSub3(); // 参照をスーパークラスの参照型へとアップキャストします。 NormalSuper3 refSuper = refSub;
まずAddedSub3クラスのインスタンスを作り、その参照をAddedSub3クラスの参照型変数refSubに入れています。これはこれまで紹介してきたものと同じです。
次に、refSub変数に入ってる参照を、スーパークラスであるNormalSuper3クラスの参照型変数refSuperに入れています。
こうすることで、refSub変数の中の参照がrefSuper変数にコピーされ、その際アップキャストが行われます。
この状態では、refSub変数とrefSuper変数の両方からAddedSub3クラスのインスタンスを見ている形になります。
まずこの段階で注意して欲しいのは、インスタンスはまったく変化しないという点です。
refSuper変数にアップキャストして参照を入れると、refSuper変数がNormalSuper3クラスの参照型変数のため、なんだか「インスタンスまでNormalSuper3クラスになった」と感じるかもしれません。
でもそれは違います。参照をどの参照型変数に入れようと、それによってインスタンスの型が変化することはありません。参照型変数がどう変わろうが、インスタンスには何にも影響がないというわけです。
それともうひとつ、参照型変数同士で行う「参照のコピー」でも、アップキャストが行われることを確認してください。
前ページでは、インスタンスから返された参照を、直接スーパークラスの参照型変数に入れた時にアップキャストされましたが、今回は参照型変数から参照型変数へのコピーでアップキャストが行われています。
このようにアップキャストは「参照を、スーパークラスへの参照型変数に入れる時」に自動的に行われるんだ、ということを憶えておいてください。
さて次に、2つの参照型変数から、スーパークラスのインスタンス内にあるフィールド・メソッドを使ってみましょう。
refSub.data = 100;
refSub.printData();
// 出力結果:
// 100
refSuper.printData();
// 出力結果:
// 100
// スーパークラスのフィールドに値を入れて出力します。 refSub.data = 100; refSub.printData(); // 出力結果: // 100 refSuper.printData(); // 出力結果: // 100
このように、スーパークラスの参照型変数・サブクラスの参照型変数どちらを通しても同じように使用できます。
また、「refSuper.printData();」の結果から、両方とも同じインスタンスを参照していることも分かると思います。
今度はサブクラスのフィールド・メソッドを使ってみましょう。
まずはrefSub変数、つまりサブクラスの参照型変数から使ってみます。
refSub.dataSub = 1;
refSub.printDataSub();
// 出力結果:
// 1
// サブクラスのフィールドに値を入れて出力します。 refSub.dataSub = 1; refSub.printDataSub(); // 出力結果: // 1
サブクラスの参照型変数からは普通に使えます。
これはここまで説明してきたものと同じです。
さて次が問題です。
refSuper変数、つまりスーパークラスの参照型変数からサブクラスのフィールド・メソッドを使おうとすると、コンパイルエラーになります。
// フィールド・メソッドを使うことはできません。
refSuper.printDataSub();
// コンパイルエラー:
// AddedSub3Runner.java:74: シンボルを見つけられません。
// シンボル: メソッド printDataSub()
// 場所 : NormalSuper3 の クラス
// refSuper.printDataSub();
// ^
// スーパークラスの参照型変数を通して、サブクラスの // フィールド・メソッドを使うことはできません。 refSuper.printDataSub(); // コンパイルエラー: // AddedSub3Runner.java:74: シンボルを見つけられません。 // シンボル: メソッド printDataSub() // 場所 : NormalSuper3 の クラス // refSuper.printDataSub(); // ^
つまり、スーパークラスの参照型変数を通してサブクラスのフィールド・メソッドは使えないということです。
なぜかというと、「どのクラスのフィールド・メソッドが使えるか」ということは参照型変数の型で決まるからです。
参照型変数の型がスーパークラスの場合、スーパークラスのフィールド・メソッドしか使えません。これはインスタンスの型に関係なく、です。
参照型変数の型がサブクラスの場合は、スーパークラスのフィールド・メソッドも、サブクラスのフィールド・メソッドも使うことができます。
つまり、「参照型変数の型」で考えた場合に、その型で使えるフィールド・メソッドが、実際に使うことのできるフィールド・メソッドになるわけです。
参照型変数がスーパークラスの場合にはスーパークラスのフィールド・メソッドだけ使えます。
参照型変数がサブクラスの場合には、サブクラスのフィールド・メソッドと、スーパークラスのフィールド・メソッドが使えます。
このように、使えるフィールド・メソッドは「参照型変数の型」で決まります。インスタンスの型は関係ない、ということです。