5.1.3動的バインド
呼び出しオブジェクトメソッドの実行手順を明らかにすることが重要です.呼び出しプロセスの詳細は、次のとおりです.
1)コンパイラは、オブジェクトの宣言タイプとメソッド名を表示します.x.f(param)が呼び出され、暗黙的なパラメータxがCクラスのオブジェクトとして宣言されると仮定します.fという名前が複数あるかもしれませんが、パラメータタイプが異なる方法があります.例えば、方法f(int)および方法f(String)が存在する場合がある.コンパイラは、すべてのCクラスのfというメソッドと、そのスーパークラスのpublicという名前のfという名前のアクセスプロパティのメソッドを1つずつ列挙します.
これで、コンパイラは呼び出される可能性のあるすべての候補メソッドを取得しました.
2)次に、コンパイラは呼び出しメソッドのパラメータタイプを表示します.fという名前のすべてのメソッドに、提供されるパラメータタイプと完全に一致するメソッドが存在する場合、このメソッドを選択します.このプロセスは、オーバーロード解析(overloading resolution)と呼ばれます.たとえば、x.f(「Hello」)を呼び出す場合、コンパイラはf(int)ではなくf(String)を選択します.タイプ変換を許可する(intはdoubleに変換でき、ManagerはEmployeeに変換できるなど)ため、このプロセスは複雑である可能性があります.コンパイラがドメインパラメータタイプマッチングの方法を見つけなかったり、タイプ変換後に複数の方法が一致していることを発見したりすると、エラーが報告されます.
これで、コンパイラは呼び出す必要があるメソッドの名前とパラメータのタイプを取得しました.
注記:署名は、メソッドの名前とパラメータのリストをメソッドの署名と呼びます.例えば、f(int)とf(String)は、同じ名前で異なる署名を持つ2つの方法である.サブクラスにスーパークラス署名と同じメソッドが定義されている場合、サブクラスのこのメソッドはスーパークラスの同じ署名のメソッドを上書きします.
ただし、戻りタイプは署名の一部ではないため、メソッドを上書きする際には、戻りタイプの互換性を保証する必要があります.Java SE 5.0以前のバージョンでは、返却タイプは同じである必要があります.サブクラスは、上書きメソッドの戻りタイプを元の戻りタイプのサブタイプとして定義できます.たとえば、Employeeクラスに
次のサブクラスManagerでは、このメソッドを以下のように上書きできます.
この2つのgetBuddyメソッドはコヒーレントな戻りタイプを持つと述べた.
3)privateメソッド、staticメソッド、finalメソッド(final修飾子の意味については後述する)またはコンストラクタであれば、コンパイラはどのメソッドを呼び出すべきかを正確に知ることができ、この呼び出し方式を静的バインドと呼ぶ(static binding).これに対応して、呼び出しの方法は暗黙的なパラメータの実際のタイプに依存し、実行時に動的バインドが実現される.我々が列挙した例では、コンパイラは動的バインド方式でf(String)を呼び出す命令を生成する.
4)プログラムが実行され、動的バインド呼び出し方法が採用される場合、仮想マシンは、xが参照するオブジェクトの実際のタイプに最も適したクラスの方法を必ず呼び出す.xの実際のタイプはDであり,Cクラスのサブクラスであると仮定する.Dクラスがメソッドf(String)を定義した場合、直接呼び出す.そうでなければ、Dクラスのスーパークラスでf(String)を探します.
メソッドを呼び出すたびに検索され、時間のオーバーヘッドがかなりかかります.したがって、仮想マシンは、各クラスに対してメソッドテーブル(method table)を予め作成しており、すべてのメソッドの署名と実際の呼び出しのメソッドがリストされている.これにより、実際の呼び出しメソッドの場合、仮想マシンはこのテーブルのみを検索すればよい.前述の例では、仮想マシンは、呼び出しf(String)と一致するメソッドを探すために、Dクラスのメソッドテーブルを検索する.この方法はD.f(String)でもX.f(String)でもあり,ここでのXはDのスーパークラスである.superを呼び出すと注意する必要があります.f(String)は,コンパイラが暗黙パラメータスーパークラスのメソッドテーブルを探索する.
次に、例5-1でe.getSalary()を呼び出す詳細な手順を確認します.e Employeeタイプとして宣言します.EmployeeクラスにはgetSalaryというメソッドしかありません.このメソッドにはパラメータはありません.したがって,ここでは重荷解析の問題を心配する必要はない.
getSalaryはprivateメソッド、staticメソッド、finalメソッドではないため、動的バインドが使用されます.仮想マシンはEmployeeとManagerの2つのクラスのメソッドテーブルを生成します.Employeeのメソッドテーブルには、このクラス定義のすべてのメソッドがリストされています.
実際、上記の方法は完全ではありません.後でEmployeeクラスにはスーパークラスObjectがあります.Employeeクラスはこのスーパークラスから多くの方法を継承しています.ここでは、これらの方法を省略します.
Managerメソッドテーブルが少し異なります.3つの方法が継承され、1つの方法は再定義され、もう1つの方法は新しく追加されました.
実行時にe.getSalary()を呼び出す解析手順は、次のとおりです.
1)まず,仮想マシンがeの実際のタイプのメソッドテーブルを抽出する.Employee、Managerのメソッドテーブル、またはEmployeeクラスの他のサブクラスのメソッドテーブルです.
2)次に,仮想マシンはgetSalary署名を定義するクラスを検索する.このとき、仮想マシンはどのメソッドを呼び出すべきか知っています.
3)最後に,仮想マシンがメソッドを呼び出す.
ダイナミックバインドには、既存のコードを変更する必要がなく、プログラムを拡張できるという非常に重要な特性があります.新しいクラスExecutiveを追加し、変数eがクラスのオブジェクトを参照する可能性があると仮定し、e.getSalary()を呼び出すコードを再コンパイルする必要はありません.eがExecutiveクラスのオブジェクトを適切に参照すると、自動的にExecutiveが呼び出される.getSalary()メソッド.
警告:1つのメソッドを上書きする場合、サブクラスメソッドはスーパークラスメソッドの可視性を下回ってはいけません.特に、スーパークラスメソッドがpublicの場合、サブクラスメソッドはpublicとして宣言する必要があります.サブクラスメソッドを宣言するときにpublic修飾子が漏れているというエラーがよく発生します.コンパイラは、アクセス権を低下させようとしていると解釈します.
1)コンパイラは、オブジェクトの宣言タイプとメソッド名を表示します.x.f(param)が呼び出され、暗黙的なパラメータxがCクラスのオブジェクトとして宣言されると仮定します.fという名前が複数あるかもしれませんが、パラメータタイプが異なる方法があります.例えば、方法f(int)および方法f(String)が存在する場合がある.コンパイラは、すべてのCクラスのfというメソッドと、そのスーパークラスのpublicという名前のfという名前のアクセスプロパティのメソッドを1つずつ列挙します.
これで、コンパイラは呼び出される可能性のあるすべての候補メソッドを取得しました.
2)次に、コンパイラは呼び出しメソッドのパラメータタイプを表示します.fという名前のすべてのメソッドに、提供されるパラメータタイプと完全に一致するメソッドが存在する場合、このメソッドを選択します.このプロセスは、オーバーロード解析(overloading resolution)と呼ばれます.たとえば、x.f(「Hello」)を呼び出す場合、コンパイラはf(int)ではなくf(String)を選択します.タイプ変換を許可する(intはdoubleに変換でき、ManagerはEmployeeに変換できるなど)ため、このプロセスは複雑である可能性があります.コンパイラがドメインパラメータタイプマッチングの方法を見つけなかったり、タイプ変換後に複数の方法が一致していることを発見したりすると、エラーが報告されます.
これで、コンパイラは呼び出す必要があるメソッドの名前とパラメータのタイプを取得しました.
注記:署名は、メソッドの名前とパラメータのリストをメソッドの署名と呼びます.例えば、f(int)とf(String)は、同じ名前で異なる署名を持つ2つの方法である.サブクラスにスーパークラス署名と同じメソッドが定義されている場合、サブクラスのこのメソッドはスーパークラスの同じ署名のメソッドを上書きします.
ただし、戻りタイプは署名の一部ではないため、メソッドを上書きする際には、戻りタイプの互換性を保証する必要があります.Java SE 5.0以前のバージョンでは、返却タイプは同じである必要があります.サブクラスは、上書きメソッドの戻りタイプを元の戻りタイプのサブタイプとして定義できます.たとえば、Employeeクラスに
- public Employee getBuddy() {....}
次のサブクラスManagerでは、このメソッドを以下のように上書きできます.
- public Manager getBuddy() {....}
この2つのgetBuddyメソッドはコヒーレントな戻りタイプを持つと述べた.
3)privateメソッド、staticメソッド、finalメソッド(final修飾子の意味については後述する)またはコンストラクタであれば、コンパイラはどのメソッドを呼び出すべきかを正確に知ることができ、この呼び出し方式を静的バインドと呼ぶ(static binding).これに対応して、呼び出しの方法は暗黙的なパラメータの実際のタイプに依存し、実行時に動的バインドが実現される.我々が列挙した例では、コンパイラは動的バインド方式でf(String)を呼び出す命令を生成する.
4)プログラムが実行され、動的バインド呼び出し方法が採用される場合、仮想マシンは、xが参照するオブジェクトの実際のタイプに最も適したクラスの方法を必ず呼び出す.xの実際のタイプはDであり,Cクラスのサブクラスであると仮定する.Dクラスがメソッドf(String)を定義した場合、直接呼び出す.そうでなければ、Dクラスのスーパークラスでf(String)を探します.
メソッドを呼び出すたびに検索され、時間のオーバーヘッドがかなりかかります.したがって、仮想マシンは、各クラスに対してメソッドテーブル(method table)を予め作成しており、すべてのメソッドの署名と実際の呼び出しのメソッドがリストされている.これにより、実際の呼び出しメソッドの場合、仮想マシンはこのテーブルのみを検索すればよい.前述の例では、仮想マシンは、呼び出しf(String)と一致するメソッドを探すために、Dクラスのメソッドテーブルを検索する.この方法はD.f(String)でもX.f(String)でもあり,ここでのXはDのスーパークラスである.superを呼び出すと注意する必要があります.f(String)は,コンパイラが暗黙パラメータスーパークラスのメソッドテーブルを探索する.
次に、例5-1でe.getSalary()を呼び出す詳細な手順を確認します.e Employeeタイプとして宣言します.EmployeeクラスにはgetSalaryというメソッドしかありません.このメソッドにはパラメータはありません.したがって,ここでは重荷解析の問題を心配する必要はない.
getSalaryはprivateメソッド、staticメソッド、finalメソッドではないため、動的バインドが使用されます.仮想マシンはEmployeeとManagerの2つのクラスのメソッドテーブルを生成します.Employeeのメソッドテーブルには、このクラス定義のすべてのメソッドがリストされています.
- Employee:
- getName() -> Employee.getName()
- getSalary() -> Employee.getSalary()
- getHireDay() -> Employee.getHireDay()
- raiseSalary(double) -> Employee.raiseSalary(double)
実際、上記の方法は完全ではありません.後でEmployeeクラスにはスーパークラスObjectがあります.Employeeクラスはこのスーパークラスから多くの方法を継承しています.ここでは、これらの方法を省略します.
Managerメソッドテーブルが少し異なります.3つの方法が継承され、1つの方法は再定義され、もう1つの方法は新しく追加されました.
- Manager:
- getName() -> Employee.getName()
- getSalary() -> Manager.getSalary()
- getHireDay() -> Employee.getHireDay()
- raiseSalary(double) -> Employee.raiseSalary(double)
- setBonus() -> Manager.setBonus()
実行時にe.getSalary()を呼び出す解析手順は、次のとおりです.
1)まず,仮想マシンがeの実際のタイプのメソッドテーブルを抽出する.Employee、Managerのメソッドテーブル、またはEmployeeクラスの他のサブクラスのメソッドテーブルです.
2)次に,仮想マシンはgetSalary署名を定義するクラスを検索する.このとき、仮想マシンはどのメソッドを呼び出すべきか知っています.
3)最後に,仮想マシンがメソッドを呼び出す.
ダイナミックバインドには、既存のコードを変更する必要がなく、プログラムを拡張できるという非常に重要な特性があります.新しいクラスExecutiveを追加し、変数eがクラスのオブジェクトを参照する可能性があると仮定し、e.getSalary()を呼び出すコードを再コンパイルする必要はありません.eがExecutiveクラスのオブジェクトを適切に参照すると、自動的にExecutiveが呼び出される.getSalary()メソッド.
警告:1つのメソッドを上書きする場合、サブクラスメソッドはスーパークラスメソッドの可視性を下回ってはいけません.特に、スーパークラスメソッドがpublicの場合、サブクラスメソッドはpublicとして宣言する必要があります.サブクラスメソッドを宣言するときにpublic修飾子が漏れているというエラーがよく発生します.コンパイラは、アクセス権を低下させようとしていると解釈します.