Home  >  Article  >  Java  >  Summary of Java Generics (2) - Generics and Arrays

Summary of Java Generics (2) - Generics and Arrays

黄舟
黄舟Original
2017-03-22 10:23:431268browse

ArrayThe relationship with generics is still a bit complicated. Direct creation of generic arrays is not allowed in Java. This article analyzes the reasons and summarizes some ways to create generic arrays. Has very good reference value. Let’s take a look at it with the editor

Introduction

The previous article introduced the basic usage of generics and the problem of type erasure. Let’s take a look now. The relationship between generics and arrays. Arrays are special compared to the container classes in Java Class Library, mainly reflected in three aspects:

  • The size of the array is fixed after it is created, but the efficiency Higher

  • The array can track the specific type of the elements stored inside it, and the inserted element type will be checked at compile time

  • Arrays can Holds primitive types (int, float, etc.), but with autoboxing, container classes seem to be able to hold primitive types

So what happens when the array encounters generics ? Can I create a generic array? This is the main content of this article.

The other two articles in this series:

  • Summary of Java Generics (1): Basic usage and type erasure

  • Java Generics Summary (3): Use of Wildcard

Generic Array

How to create a generic array

If there is a class as follows:

 class Generic {
 
}

If you want to create a generic array, it should be like this: Genericaa5a6f1eae9aaeb331563a5057ed6ec2[] However, the code will report an error, which means that a generic array cannot be created directly.

So what if you want to use a generic array? One solution is to use ArrayList, such as the following example:

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

How to create a real generic array? We cannot create it directly, but we can define a reference to a generic array. For example:

public class ArrayOfGenericReference {
 static Generic[] gia;
}

gia is a reference to a generic array, and this code can be compiled. However, we cannot create an array of this exact type, that is, we cannot use new Genericc0f559cc8d56b43654fcbe4aa9df7b4a[] See the example below for details:

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

Arrays can track the actual type of elements, This type is established when the array is created. The line of code commented out above: gia = (Genericc0f559cc8d56b43654fcbe4aa9df7b4a[])new Object[SIZE], the array is an Object array when it is created. If transformed, it will Report an error. The only way to successfully create a generic array is to create a type-erased array and then cast it, as in the code: gia = (Genericc0f559cc8d56b43654fcbe4aa9df7b4a[])new Generic[SIZE], Class ## of gia #ObjectThe output name is Generic[].

My personal understanding is: due to type erasure, Genericc0f559cc8d56b43654fcbe4aa9df7b4a is equivalent to the initial type Generic, then

gia = (Genericc0f559cc8d56b43654fcbe4aa9df7b4a[])new Generic[SIZE] The transformation in is actually transformed to Generic[]. It looks like there is no transformation, but the compiler checks the parameters and automatically transforms them, inserting new Object() and new Genericc1328de40d2c0451a2e075f9694a599f() will report an error, and taking out gia[0] to Genericc0f559cc8d56b43654fcbe4aa9df7b4a does not require us to manually transform.

Use T[] array

In the above example, the type of the element is a generic class. Let's look at an example where the type of the element itself is a generic parameter:

public class GenericArray {
 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 gai =
 new GenericArray(10);
 // This causes a ClassCastException:
 //! Integer[] ia = gai.rep();
 // This is OK:
 Object[] oa = gai.rep();
 }
}

In the above code, the creation of a generic array is to create an Object array and then convert it to T[]. But the actual type of the array is still Object[]. When calling the rep() method, a ClassCastException is reported because Object[] cannot be converted to Integer[].

The code to create a generic array array = (T[])new Object[sz] Why does it not report an error? My understanding is similar to what was introduced before. Due to type erasure, it is equivalent to transformation to Object[]. It seems that there is no transformation, but there are more parameter checks and automatic compiler transformation. And if you change the generic parameter to ba95df6fbb265e67dd0723036aced316, then because the type is erased to the first boundary, array = (T[])new Object[sz] is equivalent to converting to Integer[], which should result in an error. The following is the code of the experiment:

public class GenericArray {
 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 gai =
 new GenericArray(10);
 // This causes a ClassCastException:
 //! Integer[] ia = gai.rep();
 // This is OK:
 Object[] oa = gai.rep();
 }
}

Compared with the original version, the above code only modified the first line, changing

8742468051c85b06f0a0af9e3e506b5c to ba95df6fbb265e67dd0723036aced316 Then there is no need to call rep(), an error will be reported when creating a generic array. The following is the running result:

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

Using Object[] array

由于擦除,运行期的数组类型只能是 Object[],如果我们立即把它转型为 T[],那么在编译期就失去了数组的实际类型,编译器也许无法发现潜在的错误。因此,更好的办法是在内部最好使用 Object[] 数组,在取出元素的时候再转型。看下面的例子:

public class GenericArray2 {
 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 gai =
 new GenericArray2(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;
*///:~

现在内部数组的呈现不是 T[] 而是 Object[],当 get() 被调用的时候数组的元素被转型为 T,这正是元素的实际类型。不过调用 rep() 还是会报错, 因为数组的实际类型依然是Object[],终究不能转换为其它类型。使用 Object[] 代替 T[] 的好处是让我们不会忘记数组运行期的实际类型,以至于不小心引入错误。

使用类型标识

其实使用 Class 对象作为类型标识是更好的设计:

public class GenericArrayWithTypeToken {
 private T[] array;
 @SuppressWarnings("unchecked")
 public GenericArrayWithTypeToken(Class 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 gai =
 new GenericArrayWithTypeToken(
 Integer.class, 10);
 // This now works:
 Integer[] ia = gai.rep();
 }
}

在构造器中传入了 Class8742468051c85b06f0a0af9e3e506b5c 对象,通过 Array.newInstance(type, sz) 创建一个数组,这个方法会用参数中的 Class 对象作为数组元素的组件类型。这样创建出的数组的元素类型便不再是 Object,而是 T。这个方法返回 Object 对象,需要把它转型为数组。不过其他操作都不需要转型了,包括 rep() 方法,因为数组的实际类型与 T[] 是一致的。这是比较推荐的创建泛型数组的方法。

总结

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

The above is the detailed content of Summary of Java Generics (2) - Generics and Arrays. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn