ホームページ  >  記事  >  Java  >  Java ジェネリックス - 型消去

Java ジェネリックス - 型消去

高洛峰
高洛峰オリジナル
2016-12-19 15:35:051445ブラウズ

1. 概要

List.class が存在しない、List を List に代入できない (共変数ではない)、奇妙な ClassCastException など、Java ジェネリックを使用する場合には多くの問題があります。 Java ジェネリックスを正しく使用するには、このノートに記載されている共分散、ブリッジング メソッド、型消去などの Java の概念を深く理解する必要があります。 Java ジェネリックは、ほとんどすべてコンパイラで処理されます。コンパイラによって生成されたバイトコードには、ジェネリック型情報が含まれません。このプロセスは、型消去と呼ばれます。

2. コンパイラーはジェネリックスをどのように処理しますか?

通常、コンパイラーは次の 2 つの方法でジェネリックスを処理します。
1. コードの特殊化。ジェネリック クラスまたはジェネリック メソッドをインスタンス化すると、新しいオブジェクト コード (バイトコードまたはバイナリ コード) が生成されます。たとえば、汎用リストの場合、文字列、整数、および浮動小数点の 3 つのターゲット コードを生成する必要がある場合があります。
2.コード共有。ターゲット コードの一意のコピーのみがジェネリック クラスごとに生成され、ジェネリック クラスのすべてのインスタンスがこのターゲット コードにマップされ、必要に応じて型チェックと型変換が実行されます。
C++ のテンプレートは、典型的なコード特化の実装です。 C++ コンパイラは、各ジェネリック クラス インスタンスの実行コードを生成します。整数リストと文字列リストは、実行コードにおける 2 つの異なるタイプです。これはコードの肥大化につながりますが、経験豊富な C++ プログラマはコードの肥大化を巧みに回避できます。
コード特殊化のもう 1 つの欠点は、参照型コレクション内の要素が本質的にポインターであるため、参照型システム内のスペースの無駄であることです。種類ごとに実行コードを生成する必要はありません。これは、Java コンパイラーがジェネリックスを処理するためにコード共有を使用する主な理由でもあります。
Java コンパイラは、コード共有を通じてジェネリック型ごとに一意のバイトコード表現を作成し、ジェネリック型のインスタンスをこの一意のバイトコード表現にマップします。複数のジェネリック型インスタンスを一意のバイトコード表現にマッピングすることは、型消去によって実行されます。

3. タイプ消去とは何ですか?

型の消去とは、型パラメーターのマージを通じてジェネリック型のインスタンスを同じバイトコードに関連付けることを指します。コンパイラはジェネリック型のバイトコードを 1 つだけ生成し、そのインスタンスをこのバイトコードに関連付けます。型消去の鍵は、ジェネリック型から型パラメーターに関する情報を消去し、必要に応じて型チェックおよび型変換メソッドを追加することです。
型消去は、汎用 Java コードを通常の Java コードに変換することとして単純に理解できますが、コンパイラーはより直接的で、汎用 Java コードを通常の Java バイトコードに直接変換します。
型消去の主なプロセスは次のとおりです:
1. すべてのジェネリック パラメーターをその左端の境界 (最上位の親型) の型に置き換えます。
2. すべての型パラメータを削除します。
例えば、型消去後の

interface Comparable <A> {   
  public int compareTo( A that);   
}   
final class NumericValue implements Comparable <NumericValue> {   
  priva te byte value;    
  public  NumericValue (byte value) { this.value = value; }    
  public  byte getValue() { return value; }    
  public  int compareTo( NumericValue t hat) { return this.value - that.value; }   
}   
-----------------  
class Collections {    
  public static <A extends Comparable<A>>A max(Collection <A> xs) {   
    Iterator <A> xi = xs.iterator();   
    A w = xi.next();   
    while (xi.hasNext()) {   
      A x = xi.next();   
      if (w.compareTo(x) < 0) w = x;   
    }   
    return w;   
  }   
}   
final class Test {   
  public static void main (String[ ] args) {   
    LinkedList <NumericValue> numberList = new LinkedList <NumericValue> ();   
    numberList .add(new NumericValue((byte)0));    
    numberList .add(new NumericValue((byte)1));    
    NumericValue y = Collections.max( numberList );    
  }   
}<span style="color: #333333; font-family: Arial; font-size: 14px;">  
</span>

の型は
インターフェース Comparable {

  public int compareTo( Object that);   
}   
final class NumericValue implements Comparable {   
  priva te byte value;    
  public  NumericValue (byte value) { this.value = value; }    
  public  byte getValue() { return value; }    
  public  int compareTo( NumericValue t hat)   { return this.value - that.value; }   
  public  int compareTo(Object that) { return this.compareTo((NumericValue)that);  }   
}   
-------------  
class Collections {    
  public static Comparable max(Collection xs) {   
    Iterator xi = xs.iterator();   
    Comparable w = (Comparable) xi.next();   
    while (xi.hasNext()) {   
      Comparable x = (Comparable) xi.next();   
      if (w.compareTo(x) < 0) w = x;   
    }   
    return w;   
  }   
}   
final class Test {   
  public static void main (String[ ] args) {   
    LinkedList numberList = new LinkedList();   
    numberList .add(new NumericValue((byte)0));  ,  
    numberList .add(new NumericValue((byte)1));    
    NumericValue y = (NumericValue) Collections.max( numberList );    
  }   
}<span style="color: #333333; font-family: Arial; font-size: 14px;">  
</span>

最初のジェネリック クラス Comparable 消去後、A は左端の境界オブジェクトに置き換えられます。 Comparable の型パラメータ NumericValue が消去されましたが、これにより NumericValue が Comparable インターフェイスの CompareTo(Object that) メソッドを実装しなくなる直接的な原因となったため、コンパイラが善人として機能してブリッジ メソッドを追加しました。
2 番目の例では、型パラメータ
>A を拡張します。A は Comparable のサブクラスである必要があります。まず、すべての型パラメータ ti の置換について説明します。一番左の境界 Comparable を削除し、パラメータ タイプ A を削除して、最終的な消去結果を取得します。

4. 型消去によって引き起こされる問題

型消去が隠された存在であるからこそ、それは多くの一般的な超自然的な問題に直接つながります。
Q1. 同じジェネリック クラスのインスタンスを使用してメソッド シグネチャを区別しますか? - いいえ!
java.util.*;

    public class Erasure{  
  
            public void test(List<String> ls){  
                System.out.println("Sting");  
            }  
            public void test(List<Integer> li){  
                System.out.println("Integer");  
            }  
    }<span style="color: #333333; font-family: Arial; font-size: 14px;">  
</span>
をインポートします

このクラスをコンパイルします、

Java ジェネリックス - 型消去

参数类型明明不一样啊,一个List,一个是List,但是,偷偷的说,type erasure之后,它就都是List了⋯⋯
Q2. 同时catch同一个泛型异常类的多个实例?——NO!
同理,如果定义了一个泛型一场类GenericException,千万别同时catch GenericException和GenericException,因为他们是一样一样滴⋯⋯
Q3.泛型类的静态变量是共享的?——Yes!
猜猜这段代码的输出是什么?

import java.util.*;  
  
public class StaticTest{  
    public static void main(String[] args){  
        GT<Integer> gti = new GT<Integer>();  
        gti.var=1;  
        GT<String> gts = new GT<String>();  
        gts.var=2;  
        System.out.println(gti.var);  
    }  
}  
class GT<T>{  
    public static int var=0;  
    public void nothing(T x){}  
}<span style="color: #333333; font-family: Arial; font-size: 14px;">  
</span>

答案是——2!由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。

五、Just remember

1.虚拟机中没有泛型,只有普通类和普通方法
2.所有泛型类的类型参数在编译时都会被擦除
3.创建泛型对象时请指明类型,让编译器尽早的做参数检查(Effective Java,第23条:请不要在新代码中使用原生态类型)
4.不要忽略编译器的警告信息,那意味着潜在的ClassCastException等着你。



更多Java泛型-类型擦除相关文章请关注PHP中文网!

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。