Javaにおける汎型の実現原理を詳しく説明する。

6206 ワード

汎型はJava開発においてよく使われている技術で、汎型のいくつかの形式を理解し、汎型の基本原理を実現することで、より優れたコードを書くことに役立つ。本論文はJava汎型の3つの形式と汎型実現原理をまとめた。
汎型
汎型の本質は、タイプをパラメータ化し、コード論理が特定のデータタイプに注目しないときに使用する。例えば、一般的な順序付けアルゴリズムが実装され、この時点で注目されるのは、順序付けされたオブジェクトのタイプではなく、アルゴリズム自体である。
汎型法
次のように汎型法を定義し、パラメータ、戻り値、および方法内のコード論理に適用できる変数のタイプを宣言します。

class GenericMethod{
 public <T> T[] sort(T[] elements){
  return elements;
 }
}
汎型類
一般的な方法と同様に、汎型クラスもタイプ変数を宣言する必要がありますが、位置はクラス名の後ろにあります。作用の範囲は現在のメンバー変数タイプ、方法パラメータタイプ、方法戻りタイプ、および方法内のコードの中に含まれています。
サブクラスが汎型クラスを継承する場合や汎型クラスのオブジェクトを実例化する場合は、特定のパラメータタイプを指定するか、またはパラメータ変数を宣言する必要があります。以下のように、SubGeneraicClassは、一般的なタイプのGeneraicClassを継承しており、タイプ変数IDの値はIntegerであると同時に、サブクラスは別のタイプの変数Eを宣言し、Eを親声明のTに記入します。

class GenericClass<ID, T>{
 
}

class SubGenericClass<T> extends GenericClass<Integer, T>{
 
}
コアインターフェース
汎型インターフェースは汎型クラスと似ていますが、インターフェース名の後にタイプ変数を宣言する必要があります。インターフェースに作用する抽象的な方法はタイプとパラメータタイプを返します。サブクラスは、汎用インターフェースを実現するには、具体的なデータタイプまたはサブステートメントを記入するタイプ変数が必要です。

interface GenericInterface<T> {
 T append(T seg);
}
汎型の基本原理
汎型の本質は、データタイプをパラメータ化し、消去することによって達成される。一般的な.javaソースコードを宣言しました。作成.classファイルをコンパイルした後、汎型に関する情報がなくなりました。ソースコードには汎用的な関連情報がコンパイラに提供されると考えられます。汎型情報はJavaコンパイラに会えます。Java仮想マシンには見えません。
Javaコンパイラは以下のように消去を実現します。
  • は、汎型をObjectまたは定義型で置換し、生成されたバイトコードには元のクラス、インターフェースおよび方法のみが含まれている。
  • タイプのセキュリティを確保するために、適切な位置に強制変換コードを挿入する。
  • は、一般的なクラスまたはインターフェースを継承したクラスにブリッジ方法を挿入して多形性を保持する。
  • Java公式文書の原文
    Replace all type parameters in generanic types with their bounds or Object if the type parameters are unboded.The produced bytecode,therefore,contains only ordinary clases,interfaces,and methods.
    Insert type casts if necessary to preserve type safety.
    Generate bridge methods to preserve polymorphism in exted generanic types.
    以下では、Javaのタイプ消去について具体的なコードで説明します。
    実験原理:まずjavacで.javaファイルをコンパイルして.classファイルを作って、それから逆コンパイルツールjadを使って.classファイルを反対にJavaコードに編成して、逆コンパイルしたJavaコードの内容が反映されるのは.classファイルの中の情報です。
    以下のソースコードにより、Userクラスを定義し、Comprableインターフェースを実現し、タイプパラメータをUserに記入し、compreTo方法を実現します。
    
    class User implements Comparable<User> {
     String name;
    	
     public int compareTo(User other){
      return this.name.compareTo(other.name);
     }
    }
    JDKのComprableインターフェースのソースコードの内容は以下の通りです。
    
    package java.lang;
    public interface Comparable<T>{
     int compareTo(T o);
    }
    まず、そのインターフェースを逆コンパイルします。Comprableインターフェースのバイトコードファイルは、$JRE_ホーム/lib/rt.jarで見つけました。それをあるディレクトリにコピーします。jad.exeを使って、このComprable.classファイルを逆コンパイルします。
    
    $ jad Comparable.class
    逆コンパイルした内容はComprable.jadファイルにおいて、ファイルの内容は以下の通りです。
    
    // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
    // Jad home page: http://www.kpdus.com/jad.html
    // Decompiler options: packimports(3)
    // Source File Name: Comparable.java
    
    package java.lang;
    
    // Referenced classes of package java.lang:
    //   Object
    
    public interface Comparable
    {
    
     public abstract int compareTo(Object obj);
    }
    ソースコードComprable.javaと逆コンパイルコードComprable.jadの内容を比較すると、逆コンパイル後の内容にはすでにタイプ変数Tがないことが分かります。compreTo法におけるパラメータタイプTもObjectに置き換えられた。これは上記の第1条消去の原則に合致します。ここで実証したのはObjectでタイプパラメータを置換し、定義されたタイプの置換型パラメータを使用した例でCollection.classを逆コンパイルしてみてもいいです。
    javac.exeを使ってUser.javaを.classファイルにコンパイルし、jadを使って.classファイルをJavaコードに逆コンパイルします。
    
    $ javac User.java
    $ jad User.class
    User.jadファイルの内容は以下の通りです。
    
    // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
    // Jad home page: http://www.kpdus.com/jad.html
    // Decompiler options: packimports(3)
    // Source File Name: User.java
    
    
    class User
     implements Comparable
    {
    
     User()
     {
     }
    
     public int compareTo(User user)
     {
      return name.compareTo(user.name);
     }
    
     //     
     public volatile int compareTo(Object obj)
     {
      return compareTo((User)obj);
     }
    
     String name;
    }
    比較編集のソースコードUser.javaと逆コンパイルされたコードUser.jadは、簡単に発見されます。タイプパラメータがなくなりました。無参構造方法が多くなりました。compreTo(Object obj)方法があります。これらの内容により、消去ルール2とルール3の実現形態が見られます。
    強回転規則はよく理解できます。汎型がObjectに置き換えられているので、具体的なタイプの方法やメンバー変数を呼び出すには、もちろん先に強く具体的なタイプに変えてから使用できます。挿入するブリッジの方法はどうやって分かりますか?
    もし私たちが以下のようにUserクラスだけを使うなら、確かにパラメータタイプがObjectのブリッジ方法は必要ありません。
    
    User user = new User();
    User other = new User();
    user.comparetTo(other);
    しかし、Javaにおける多形特性は、親またはインターフェースの参照を使用して、サブクラスのオブジェクトを指すことを可能にする。
    
    Comparable<User> user = new User();
    Objectによって汎型パラメータを交替する原則によって、Comprableインターフェースの中でcompreTo(Object)の方法だけあって、もしブリッジの方法がないならば、明らかに下記のコードは運行できないのです。したがって、Javaコンパイラは、サブクラス(汎型のサブクラスまたは汎型インターフェースの実装クラス)のために、汎型の方法を使用して、追加的にブリッジを生成する必要があり、この方法によりJavaの多形特性を保証する。
    
    Comparable<User> user = new User();
    Object other = new User();
    user.compareTo(other);
    一般的なクラスの一般的な方法は、タイプの消去を行うときにブリッジが発生しません。たとえば:
    
    class Dog{
     <T> void eat(T[] food){
     }
    }
    タイプが消去されたら、
    
    class Dog
    {
    
     Dog()
     {
     }
    
     void eat(Object aobj[])
     {
     }
    }
    結び目
    Javaにおける汎型は3つの形式、汎型の方法、汎型の種類、汎型のインターフェースがあります。Javaはコンパイル時にタイプを消去することによって汎型を実現します。消去時にObjectを使用したり、汎型に代わるタイプを定義したりします。また、特定のタイプの方法やメンバー変数を呼び出したい時に、強変換コードを挿入します。多状態特性を保証するために、Javaコンパイラは汎型のサブクラスのためにブリッジを生成します。タイプ情報はコンパイル段階で消去された後、プログラムは実行中にタイプパラメータに対応する特定のタイプを取得できません。
    参照
    https://docs.oracle.com/javase/tutorial/java/generics/index.html
    https://stackoverflow.com/questions/25040837/generics-bridge-method-on-polymorphism
    以上はJavaの汎型実現原理の詳細を詳しく説明しました。Java汎型実現原理に関する資料は他の関連記事に注目してください。