ホームページ  >  記事  >  Java  >  Java ジェネリックの概要 (2) - ジェネリックと配列

Java ジェネリックの概要 (2) - ジェネリックと配列

黄舟
黄舟オリジナル
2017-03-22 10:23:431362ブラウズ

配列とジェネリックの関係は、Java ではまだ少し複雑です。この記事では、その理由を分析し、汎用配列を作成するいくつかの方法を要約します。非常に良い基準値を持っています。エディターで見てみましょう

はじめに

前回の記事ではジェネリックの基本的な使い方と型消去の問題について紹介しました。今度はジェネリックと配列の関係を見てみましょう。 Java クラス ライブラリのコンテナ クラスと比較すると、配列は特別であり、主に次の 3 つの側面に反映されています:

  • 配列のサイズは作成後に固定されますが、より効率的です

  • 配列は次のことができます。内部ストレージを追跡します。挿入された要素の型はコンパイル時にチェックされますが、オートボクシングを使用すると、コンテナ クラスもプリミティブ型を保持できます。

  • それでは、配列がジェネリックに遭遇すると何が起こるでしょうか? 汎用配列を作成できますか?これがこの記事の主な内容です。
  • このシリーズの別の 2 つの記事:

Java ジェネリックの概要 (1): 基本的な使用法と型消去

ジェネリック配列の作成方法

次のようなクラスがある場合:

 class Generic<T> {
 
}
ジェネリック配列を作成したい場合は、次のようにする必要があります: Generic11503d1c0592b9383b93e5051580cc09[] ただし、行コードはエラーを報告します。これは、汎用配列を直接作成できないことを意味します。 では、汎用配列を使用したい場合はどうすればよいでしょうか?解決策の 1 つは、次の例のように ArrayList を使用することです。

public class ListOfGenerics<T> {
 private List<T> array = new ArrayList<T>();
 public void add(T item) { array.add(item); }
 public T get(int index) { return array.get(index); }
}

実際の汎用配列を作成するには?直接作成することはできませんが、汎用配列の参照

を定義することはできます。例:

public class ArrayOfGenericReference {
 static Generic<Integer>[] gia;
}
Genericc0f559cc8d56b43654fcbe4aa9df7b4a ga = new Genericc0f559cc8d56b43654fcbe4aa9df7b4a[]  不过行代码会报错,也就是说不能直接创建泛型数组。

那么如果要使用泛型数组怎么办?一种方案是使用 ArrayList,比如下面的例子:

public class ArrayOfGeneric {
 static final int SIZE = 100;
 static Generic<Integer>[] gia;
 @SuppressWarnings("unchecked")
 public static void main(String[] args) {
 // Compiles; produces ClassCastException:
 //! gia = (Generic<Integer>[])new Object[SIZE];
 // Runtime type is the raw (erased) type:
 gia = (Generic<Integer>[])new Generic[SIZE];
 System.out.println(gia.getClass().getSimpleName());
 gia[0] = new Generic<Integer>();
 //! gia[1] = new Object(); // Compile-time error
 // Discovers type mismatch at compile time:
 //! gia[2] = new Generic<Double>();
 Generic<Integer> g = gia[0];
 }
} /*输出:
Generic[]
*///:~

如何创建真正的泛型数组呢?我们不能直接创建,但可以定义泛型数组的引用。比如:

public class GenericArray<T> {
 private T[] array;
 @SuppressWarnings("unchecked")
 public GenericArray(int sz) {
 array = (T[])new Object[sz]; // 创建泛型数组
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 public T get(int index) { return array[index]; }
 // Method that exposes the underlying representation:
 public T[] rep() { return array; } //返回数组 会报错
 public static void main(String[] args) {
 GenericArray<Integer> gai =
 new GenericArray<Integer>(10);
 // This causes a ClassCastException:
 //! Integer[] ia = gai.rep();
 // This is OK:
 Object[] oa = gai.rep();
 }
}

gia 是一个指向泛型数组的引用,这段代码可以通过编译。但是,我们并不能创建这个确切类型的数组,也就是不能使用 new Genericc0f559cc8d56b43654fcbe4aa9df7b4a[]  具体参见下面的例子:

public class GenericArray<T extends Integer> {
 private T[] array;
 @SuppressWarnings("unchecked")
 public GenericArray(int sz) {
 array = (T[])new Object[sz]; // 创建泛型数组
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 public T get(int index) { return array[index]; }
 // Method that exposes the underlying representation:
 public T[] rep() { return array; } //返回数组 会报错
 public static void main(String[] args) {
 GenericArray<Integer> gai =
 new GenericArray<Integer>(10);
 // This causes a ClassCastException:
 //! Integer[] ia = gai.rep();
 // This is OK:
 Object[] oa = gai.rep();
 }
}

数组能追踪元素的实际类型,这个类型是在数组创建的时候建立的。上面被注释掉的一行代码: gia = (Genericc0f559cc8d56b43654fcbe4aa9df7b4a[])new Object[SIZE],数组在创建的时候是一个 Object 数组,如果转型便会报错。成功创建泛型数组的唯一方式是创建一个类型擦除的数组,然后转型,如代码: gia = (Genericc0f559cc8d56b43654fcbe4aa9df7b4a[])new Generic[SIZE],gia 的 Class 对象输出的名字是 Generic[]。

我个人的理解是:由于类型擦除,所以 Genericc0f559cc8d56b43654fcbe4aa9df7b4a 相当于初始类型 Generic,那么 gia = (Genericc0f559cc8d56b43654fcbe4aa9df7b4a[])new Generic[SIZE] 中的转型其实还是转型为 Generic[],看上去像没转,但是多了编译器对参数的检查和自动转型,向数组插入 new Object()new Genericeafb63d086dd6c9bd19609d76bcc2869()均会报错,而 gia[0] 取出给 Genericc0f559cc8d56b43654fcbe4aa9df7b4a 也不需要我们手动转型。

使用 T[] array

上面的例子中,元素的类型是泛型类。下面看一个元素本身类型是泛型参数的例子:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at GenericArray.<init>(GenericArray.java:15)

在上面的代码中,泛型数组的创建是创建一个 Object 数组,然后转型为 T[]。但数组实际的类型还是 Object[]。在调用 rep()方法的时候,就报 ClassCastException 异常了,因为 Object[] 无法转型为 Integer[]。

那创建泛型数组的代码 array = (T[])new Object[sz] 为什么不会报错呢?我的理解和前面介绍的类似,由于类型擦除,相当于转型为 Object[],看上去就是没转,但是多了编译器的参数检查和自动转型。而如果把泛型参数改成 ba95df6fbb265e67dd0723036aced316 ,那么因为类型是擦除到第一个边界,所以 array = (T[])new Object[sz] 中相当于转型为 Integer[],这应该会报错。下面是实验的代码:

public class GenericArray2<T> {
 private Object[] array;
 public GenericArray2(int sz) {
 array = new Object[sz];
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 @SuppressWarnings("unchecked")
 public T get(int index) { return (T)array[index]; }
 @SuppressWarnings("unchecked")
 public T[] rep() {
 return (T[])array; // Warning: unchecked cast
 }
 public static void main(String[] args) {
 GenericArray2<Integer> gai =
 new GenericArray2<Integer>(10);
 for(int i = 0; i < 10; i ++)
 gai.put(i, i);
 for(int i = 0; i < 10; i ++)
 System.out.print(gai.get(i) + " ");
 System.out.println();
 try {
 Integer[] ia = gai.rep();
 } catch(Exception e) { System.out.println(e); }
 }
} /* Output: (Sample)
0 1 2 3 4 5 6 7 8 9
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
*///:~

相比于原始的版本,上面的代码只修改了第一行,把  8742468051c85b06f0a0af9e3e506b5c 改成了 ba95df6fbb265e67dd0723036aced316gia は汎用配列への参照です。このコードはコンパイルできます。ただし、この正確な型の配列を作成することはできません。つまり、new Genericc0f559cc8d56b43654fcbe4aa9df7b4a[] を使用することはできません。詳細については、以下の例を参照してください。

public class GenericArrayWithTypeToken<T> {
 private T[] array;
 @SuppressWarnings("unchecked")
 public GenericArrayWithTypeToken(Class<T> type, int sz) {
 array = (T[])Array.newInstance(type, sz);
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 public T get(int index) { return array[index]; }
 // Expose the underlying representation:
 public T[] rep() { return array; }
 public static void main(String[] args) {
 GenericArrayWithTypeToken<Integer> gai =
 new GenericArrayWithTypeToken<Integer>(
 Integer.class, 10);
 // This now works:
 Integer[] ia = gai.rep();
 }
}

配列は、実際の型を追跡できます。この型は配列の作成時に作成されます。 によってコメントアウトされたコード行: gia = (Genericc0f559cc8d56b43654fcbe4aa9df7b4a[])new Object [SIZE] の場合、配列は作成時にオブジェクト配列であり、変換するとエラーが報告されます。ジェネリック配列を正常に作成する唯一の方法は、コード gia = (Genericc0f559cc8d56b43654fcbe4aa9df7b4a[])new Generic[SIZE] のように、型消去された配列を作成してキャストすることです。 gia のクラス オブジェクト の出力名は Generic[] です。 私の個人的な理解は次のとおりです: 型消去により、Genericc0f559cc8d56b43654fcbe4aa9df7b4a は初期型 Generic と同等になり、その後 gia = (Genericc0f559cc8d56b43654fcbe4aa9df7b4a[])new Generic[SIZE] で変換されます。実際には、まだ Generic[] に変換されており、変換されていないように見えますが、コンパイラはパラメータをチェックして自動的に変換し、new Object()new Generic<Double> を挿入します。 ; () を配列に追加するとエラーが報告され、gia[0] は Genericc0f559cc8d56b43654fcbe4aa9df7b4a に取り出されるので、手動で変換する必要はありません。

🎜🎜T[] arrayを使用🎜🎜🎜🎜上記の例では、要素の型はジェネリッククラスです。要素自体の型がジェネリック パラメーターである例を見てみましょう: 🎜rrreee🎜 上記のコードでは、ジェネリック配列の作成は、Object 配列を作成し、それを T[] に変換することです。ただし、配列の実際の型は依然として Object[] です。 rep() メソッドを呼び出すと、Object[] を Integer[] に変換できないため、ClassCastException が報告されます。 🎜🎜🎜汎用配列を作成するコード 🎜array = (T[])new Object[sz] 🎜 エラーがないのはなぜですか? 🎜🎜私の理解は以前に紹介したものと似ていますが、型の消去により、Object[]への変換に相当しますが、追加のパラメータチェックと自動化が行われているようです。コンパイラによる変換。また、ジェネリック パラメーターを ba95df6fbb265e67dd0723036aced316 に変更すると、型は最初の境界まで消去されるため、 array = (T[])new Object[sz] code> は <code>Integer[] への変換と同等であり、エラーが発生します。以下は実験のコードです: 🎜rrreee🎜 元のバージョンと比較すると、上記のコードは最初の行のみを変更し、 8742468051c85b06f0a0af9e3e506b5cba95df6fbb265e67dd0723036aced316 に変更しています。 /code> この場合、rep() を呼び出す必要はありません。汎用配列の作成時にエラーが報告されます。実行結果は次のとおりです: 🎜rrreee🎜🎜🎜Using Object[] array🎜🎜🎜<p>由于擦除,运行期的数组类型只能是 Object[],如果我们立即把它转型为 T[],那么在编译期就失去了数组的实际类型,编译器也许无法发现潜在的错误。因此,更好的办法是在内部最好使用 Object[] 数组,在取出元素的时候再转型。看下面的例子:</p><pre class="brush:java;">public class GenericArray2&lt;T&gt; { private Object[] array; public GenericArray2(int sz) { array = new Object[sz]; } public void put(int index, T item) { array[index] = item; } @SuppressWarnings(&quot;unchecked&quot;) public T get(int index) { return (T)array[index]; } @SuppressWarnings(&quot;unchecked&quot;) public T[] rep() { return (T[])array; // Warning: unchecked cast } public static void main(String[] args) { GenericArray2&lt;Integer&gt; gai = new GenericArray2&lt;Integer&gt;(10); for(int i = 0; i &lt; 10; i ++) gai.put(i, i); for(int i = 0; i &lt; 10; i ++) System.out.print(gai.get(i) + &quot; &quot;); System.out.println(); try { Integer[] ia = gai.rep(); } catch(Exception e) { System.out.println(e); } } } /* Output: (Sample) 0 1 2 3 4 5 6 7 8 9 java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; *///:~</pre><p>现在内部数组的呈现不是 T[] 而是 Object[],当 get() 被调用的时候数组的元素被转型为 T,这正是元素的实际类型。不过调用 rep() 还是会报错, 因为数组的实际类型依然是Object[],终究不能转换为其它类型。使用 Object[] 代替 T[] 的好处是让我们不会忘记数组运行期的实际类型,以至于不小心引入错误。</p> <p><span style="color: #0000ff"><strong>使用类型标识</strong></span></p> <p>其实使用 Class 对象作为类型标识是更好的设计:</p><pre class="brush:java;">public class GenericArrayWithTypeToken&lt;T&gt; { private T[] array; @SuppressWarnings(&quot;unchecked&quot;) public GenericArrayWithTypeToken(Class&lt;T&gt; type, int sz) { array = (T[])Array.newInstance(type, sz); } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } // Expose the underlying representation: public T[] rep() { return array; } public static void main(String[] args) { GenericArrayWithTypeToken&lt;Integer&gt; gai = new GenericArrayWithTypeToken&lt;Integer&gt;( Integer.class, 10); // This now works: Integer[] ia = gai.rep(); } }</pre><p>在构造器中传入了 <code>Class8742468051c85b06f0a0af9e3e506b5c 对象,通过 Array.newInstance(type, sz) 创建一个数组,这个方法会用参数中的 Class 对象作为数组元素的组件类型。这样创建出的数组的元素类型便不再是 Object,而是 T。这个方法返回 Object 对象,需要把它转型为数组。不过其他操作都不需要转型了,包括 rep() 方法,因为数组的实际类型与 T[] 是一致的。这是比较推荐的创建泛型数组的方法。

总结

数组与泛型的关系还是有点复杂的,Java 中不允许直接创建泛型数组。本文分析了其中原因并且总结了一些创建泛型数组的方式。其中有部分个人的理解,如果错误希望大家指正。下一篇会总结通配符的使用。

以上がJava ジェネリックの概要 (2) - ジェネリックと配列の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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