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]から入手できます。