Javaでの型の消去の説明

1概要

このクイック記事では、型消去と呼ばれるJavaの総称における重要なメカニズムの基本について説明します。

** 2タイプ消去とは

型消去は、コンパイル時にのみ型制約を強制し、実行時に要素型情報を破棄するプロセスとして説明できます。

例えば:

public static  <E> boolean containsElement(E[]elements, E element){
    for (E e : elements){
        if(e.equals(element)){
            return true;
        }
    }
    return false;
}

コンパイル時に、アンバインド型 E は実際の型 Object に置き換えられます。

public static  boolean containsElement(Object[]elements, Object element){
    for (Object e : elements){
        if(e.equals(element)){
            return true;
        }
    }
    return false;
}

コンパイラはコードの型安全性を保証し、実行時エラーを防ぎます。

3タイプ消去の種類

型の消去はクラス(または変数)とメソッドのレベルで起こります。

3.1. クラスタイプ消去

クラスレベルでは、クラスの型パラメータはコードのコンパイル中に破棄され、最初の範囲に置き換えられます。型パラメータがバインドされていない場合は Object に置き換えられます。

配列を使用して Stack を実装しましょう。

public class Stack<E> {
    private E[]stackContent;

    public Stack(int capacity) {
        this.stackContent = (E[]) new Object[capacity];
    }

    public void push(E data) {
       //..
    }

    public E pop() {
       //..
    }
}

コンパイル時に、非バインド型パラメータ E Object に置き換えられます。

public class Stack {
    private Object[]stackContent;

    public Stack(int capacity) {
        this.stackContent = (Object[]) new Object[capacity];
    }

    public void push(Object data) {
       //..
    }

    public Object pop() {
       //..
    }
}

型パラメータ E が束縛されている場合:

public class BoundStack<E extends Comparable<E>> {
    private E[]stackContent;

    public BoundStack(int capacity) {
        this.stackContent = (E[]) new Object[capacity];
    }

    public void push(E data) {
       //..
    }

    public E pop() {
       //..
    }
}

コンパイル時に、バインドされた型パラメータ E は最初のバインドされたクラス、この場合は Comparable に置き換えられます

public class BoundStack {
    private Comparable[]stackContent;

    public BoundStack(int capacity) {
        this.stackContent = (Comparable[]) new Object[capacity];
    }

    public void push(Comparable data) {
       //..
    }

    public Comparable pop() {
       //..
    }
}

3.2. 方法タイプ消去

メソッドレベルの型を消去する場合、メソッドの型パラメータは保存されず、バインドされていない場合は親の型、つまりバインドされている場合は最初にバインドされたクラスに変換されます。

任意の配列の内容を表示する方法を考えましょう。

public static <E> void printArray(E[]array) {
    for (E element : array) {
        System.out.printf("%s ", element);
    }
}

コンパイル時に、型パラメータ E Object に置き換えられます。

public static void printArray(Object[]array) {
    for (Object element : array) {
        System.out.printf("%s ", element);
    }
}

バウンドメソッドタイプパラメータの場合:

public static <E extends Comparable<E>> void printArray(E[]array) {
    for (E element : array) {
        System.out.printf("%s ", element);
    }
}

型パラメータ E を消去して__Comparableに置き換えます。

public static void printArray(Comparable[]array) {
    for (Comparable element : array) {
        System.out.printf("%s ", element);
    }
}

4エッジケース

タイプ消去プロセス中に、コンパイラは類似のメソッドを区別するために合成メソッドを作成します。これらは同じ最初の束縛クラスを拡張するメソッドシグネチャから来るかもしれません。

以前の Stack の実装を拡張する新しいクラスを作成しましょう。

public class IntegerStack extends Stack<Integer> {

    public IntegerStack(int capacity) {
        super(capacity);
    }

    public void push(Integer value) {
        super.push(value);
    }
}

それでは、次のコードを見てみましょう。

IntegerStack integerStack = new IntegerStack(5);
Stack stack = integerStack;
stack.push("Hello");
Integer data = integerStack.pop();

消去後、次のようになります。

IntegerStack integerStack = new IntegerStack(5);
Stack stack = (IntegerStack) integerStack;
stack.push("Hello");
Integer data = (String) integerStack.pop();

IntegerStack は親クラス Stack から push(Object) を継承しているため、 IntegerStack String をプッシュする方法に注意してください。

integerStack Stack <Integer> 型なので整数でなければならないため、これはもちろん間違っています。

したがって、当然のことながら、 String pop にして Integer に代入しようとすると、 Push の間にコンパイラによって挿入されたキャストから ClassCastException が発生します。

** 4.1. ブリッジ方法

上記のエッジケースを解決するために、コンパイラはブリッジメソッドを作成することがあります。これは、パラメータ化されたクラスを拡張する、またはメソッドシグネチャがわずかに異なるかあいまいな場合があるパラメータ化されたインタフェースを実装するクラスまたはインタフェースをコンパイルするときにJavaコンパイラによって作成される合成メソッドです。

上記の例では、Javaコンパイラは、消去後に IntegerStack s push(Integer) メソッドと Stack s push(Object) メソッドの間でメソッドシグネチャの不一致がないことを保証することによって、ジェネリック型の多態性を保持します。

したがって、コンパイラはここにブリッジメソッドを作成します。

public class IntegerStack extends Stack {
   //Bridge method generated by the compiler

    public void push(Object value) {
        push((Integer)value);
    }

    public void push(Integer value) {
        super.push(value);
    }
}

その結果、消去後の Stack クラスの push メソッドは、元の IntegerStack クラスの push メソッドに委譲します。

5結論

このチュートリアルでは、型パラメータ変数とメソッドの例を使用して、型消去の概念について説明しました。

これらの概念についてもっと読むことができます。

言語仕様:型消去]** Javaジェネリックの基礎

いつものように、この記事に付随するソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-lang-oop[over on GitHub]から入手できます。