マルチステート(Polymorphism)の実現メカニズム(下)−Java編


最初はここに発表された.
 
   マルチステートがオブジェクト向けの3つの本質的特徴の1つである(他の2つはデータ抽象と継承である)以上、C++はなぜメソッド呼び出しのデフォルト方式を動的バインドに設定せず、キーワードvirtualでタグ付けするのか.Bruce Eckelは『Thinking in C+++』で、これは歴史的な原因によるもので、C++はCから発展したものであり、Cプログラマーが最も関心を持っているのは性能問題であり、動的バインドは静的バインドよりも複数の命令があるため、性能が低下している.動的バインドをデフォルトのメソッド呼び出し方式に設定すると、多くのCプログラマーは受け入れられない可能性がある.そのため、C++はダイナミックバインドをオプションに位置決めし、If you don't use it,you don't pay for it(Stroustrup)を保証します.    しかし、Javaは全く新しい完全なオブジェクト向け言語として、下向きの互換性の問題は存在しない.また、Javaの設計者も多態をオブジェクト向けの核心とし、オブジェクト向け言語は内蔵のサポートを提供すべきであると考えているため、Javaは動的バインドをメソッド呼び出しのデフォルト方式としている.    次に、Javaがマルチステートをサポートする方法について詳しく説明します.C++と同様にJavaにもインスタンスメソッドアドレスを格納するデータ構造があり、C++ではVTableと呼ばれ、javaではメソッドテーブル(Method Table)と呼ばれていますが、両者には多くの共通点があります.     1、それらの作用は同じで、同様に実現方法の動的バインドを補助するために使用される.     2、同じクラスレベルのデータ構造で、1つのクラスのすべてのオブジェクトが1つのメソッドテーブルを共有します.     3、いずれもオフセット量によってそのデータ構造の中である方法を検索する.     4.同様に、すべての派生クラスがベースクラスに中継される方法の方法テーブルにおけるオフセット量が、この方法のベースクラス方法テーブルにおけるオフセット量と一致することを保証する.     5、メソッドテーブルにはマルチステートメソッドしか保存できません(Javaのインスタンスメソッド、C++はvitualメソッド).
     しかし、結局、C++はコンパイル型の言語であり、Javaは解析型に偏っているため、上記のデータ構造の生成とメンテナンスは異なる.     1、C++のVTableとvptrはコンパイル段階でコンパイラによって自動的に生成され、すなわち、C++プログラムがメモリにロードされる前に、.obj(.o)ファイルにこれらの構造の情報がすでに存在していた.JavaのメソッドテーブルはJVMによって生成されるため、javacコマンドを使用してコンパイルした.classファイルにはメソッドテーブルの情報はありません.JVMが.classファイルをメモリにロードするのを待っている場合にのみ、この.classファイルに関連付けられたメソッドテーブルが動的に生成され、JVMのメソッド領域に配置されます.    2、C++のいずれかの方法はVTableのインデックス番号がコンパイル段階で明確に知られており、実行中に動的に知る必要はない.Javaのメソッドは,初期は1つのシンボルであり,明確なアドレスではなく,そのメソッドが最初に呼び出されるまでメソッドテーブルのオフセット量に解析される,すなわち,インスタンスメソッドのみが自分の発表中のオフセット量を明確に知ることができ,その前に解析の過程を経験しなければならない.
    また、Javaでは多重継承がサポートされていないため、C++のようにこの泥沼に絡み合うことはありませんが、Javaではインタフェース、Interfaceという新しい概念が導入されています.Interfaceを使用してインスタンスを呼び出す方法は、Classを使用して呼び出す方法とは異なります.
public class  Zoo
{
 public static void main(String[] args) 
 {
   Pet p1 = new Dog();
   Pet p2 = new Dog();
   p1.say(); //      ,     ,    
   p2.say(); //    ,             ,  

  Cute c1 = new Dog();  
  Cute c2 = new Dog();
  c1.cute();  //             ,         ,     ,      
  c2.cute(); //            ,                  ,     ,  
 }
}
interface Cute
{
 public void cute();
}
class Pet
{
  public void say(){ System.out.println("Pet say");  }
}
class Dog extends Pet implements Cute
{
     public void cute(){ System.out.println("Dog cute"); }
     public void say(){ System.out.println("Dog say");  }
}

 
    なぜこのような違いがあるのでしょうか.これは,同じインタフェースを実現するクラスが同じスーパークラスから継承されることを保証するものではなく,このスーパークラスも同様に同じインタフェースを実現するためである.したがって、インタフェースが宣言するメソッドは、メソッドテーブルの同じ場所にあることを保証するものではありません.たとえば、次のクラスを定義できます.
class Cat  implements Cute
{
     public void cute(){ System.out.println("Cat cute"); }
}

 
    では,DogはCatと同様にインタフェースCuteを実現しているので,いずれもCuteインタフェースで呼び出すことができるが,Dogメソッドテーブルにおけるメソッドcuteの位置はCatメソッドテーブルにおけるメソッドの位置が同じであることを保証するものではない.したがって,インタフェース呼び出し方法については,毎回再解析し,正確なオフセット量を得て呼び出すしかなかった.これにより、クラス呼び出しインスタンスメソッドを使用するよりも、インタフェース呼び出しメソッドを使用する方が効率的になります.もちろん、これは相対的にJVMが実装上最適化されるだけであり、インタフェースの効率が低いために使用しないとは言えないが、逆にオブジェクト向けの役割におけるインタフェースの強力な役割のためjavaはインタフェースの使用を提唱している点に注意する必要がある.    もう一つ、javaではクラスの多重継承はサポートされていませんが、複数のインタフェースを実現できるので、JavaではC++の多重継承のように必要な変換を行うのではないでしょうか.この問題は,両者の呼び出しの具体的な過程を考えるだけで,Javaのインタフェースメソッドは呼び出しのたびに解析が必要であり,ここでこそ真のオフセット量が得られることが分かるが,これはC++でコンパイル中にオフセット量を取得するのとは異なるため,Javaではいわゆる変換を行う必要はない.