I did a code review for my colleague two days ago. I felt that I didn’t have a good grasp of Java Generics, so I took out the book "Effective Java" 1 and then looked at the relevant chapters. In Item 24: Eliminate unchecked warnings section, the author uses the public <T> T[] toArray(T[] a) method in the ArrayList class as an example to illustrate how to use @SuppressWarnings annotation for variables.
ArrayList is a generic class, which is declared like this:
Javapublic class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
The toArray(T[] a) method of this class is a generic method, which is declared and implemented like this:
@SuppressWarnings("unchecked")public <T> T[] toArray(T[] a) {if (a.length < size)// Make a new array of a's runtime type, but my contents:return (T[]) Arrays.copyOf(elementData, size, a.getClass());System.arraycopy(elementData, 0, a, 0, size);if (a.length > size)a[size] = null;return a;} This method is actually declared in the Collection interface. Because we often use it through ArrayList, we will use ArrayList as an example here.
1 Why is it declared as a different type?
My question is: Why does this method use type T instead of type E of ArrayList? That is, why is this method not declared like this:
Javapublic E[] toArray(E[] a);
If the types are the same, the parameter type error can be found during compilation. If the types are different, it is easy to generate runtime errors. For example, the following code:
//Create an ArrayListList<String> strList = new ArrayList<String>();strList.add("abc");strList.add("xyz");//Convert the current strList into a Number array. Note that there are no compilation errors in the following statement. Number[] numArray = strList.toArray(new Number[0]); Run the above code and Line 6 will throw a java.lang.ArrayStoreException exception.
If the toArray method uses type E, statement 2 will generate a compilation error. Compilation errors are more friendly than runtime errors. Moreover, the main purpose of generics is to eliminate type conversion errors (ClassCastException) during compilation. This method is the opposite. Is this a big bug? I've encountered a Java bug, but I still can't believe it.
After checking the Internet, this issue has been discussed many times 2, 3, 4.
2 Can improve flexibility
This declaration is more flexible and can convert elements in the current list into an array of more general types. For example, the current list type is Integer, and we can convert its elements into a Number array.
List<Integer> intList = new ArrayList<Integer>();intList.add(1);intList.add(2);Number[] numArray = intList.toArray(new Number[0]);
If this method is declared as type E, the above code will have a compilation error. It seems that it would be more appropriate to declare the method as follows:
Javapublic <T super E> T[] toArray(T[] a);
However, syntax like <T super E> does not exist in Java. And even if it exists, it doesn't work for arrays. It is also for this reason that when using this method, even if T is the parent class of E, or T is the same as E, java.lang.ArrayStoreException 5, 6, 7 cannot be completely avoided. Please see the following two codes. In the first code, T is the parent class of E, and in the second code, T is the same as E. Both pieces of code throw exceptions.
Code 1:
List<Integer> intList = new ArrayList<Integer>();intList.add(1);intList.add(2); Float[] floatArray = new Float[2];//Float is a subclass of Number, so Float[] is a subclass of Number[] Number[] numArray = floatArray;//The following statement will throw an ArrayStoreException exception numArray = intList.toArray(numArray);
Code 2:
List<Number> intList = new ArrayList<Number>();//The type of List is Number. But Number is an abstract class, and can only store instances of its subclass intList.add(new Integer()); intList.add(new Integer()); Float[] floatArray = new Float[];//Float is a subclass of Number, so Float[] is a subclass of Number[] Number[] numArray = floatArray;//The following statement will throw an ArrayStoreException exception numArray = intList.toArray(numArray);
The above exceptions are all caused by this fact: if A is the parent class of B, then A[] is the parent class of B[]. All classes in Java inherit from Object, and Object[] is the parent class of all arrays.
This post 8 gives an example, showing that even if the type of this method is declared as E, the ArrayStoreException cannot be avoided.
This exception is also mentioned in the documentation for this method:
ArrayStoreException if the runtime type of the specified array is not a supertype of the runtime type of every element in this list.
3 It can be compatible with versions before Java 1.5
This method appeared before the introduction of Generics in Java (Generics was introduced in JDK1.5). It was declared at that time:
Javapublic Object[] toArray(Object[] a)
After Generics appears, many classes and methods become generic. This method also declares it like this:
Javapublic <T> T[] toArray(T[] a)
This declaration is compatible with versions prior to Java 1.5.
4 A few more words
This method requires an array parameter. If the length of this array is greater than or equal to the size of the current list, the elements in the list will be stored in the array; if the length of this array is less than the size of the current list, a new array will be created and the elements in the current list will be stored in the newly created array. To improve efficiency, if possible, the length of the passed array must be greater than or equal to the size of the list to avoid creating a new array by this method.
List<Integer> intList = new ArrayList<Integer>();intList.add();intList.add();//Paste in an array, its length is Number[] numArray = intList.toArray(new Number[]); //Statement//Paste in an array, its length is equal to the length of intList Number[] numArray = intList.toArray(new Number[intList.size()]); //Statement
In addition, the array as parameters cannot be null, otherwise a NullPointerException will be thrown.
Footnotes:
1
Effective Java (2nd Edition)
2
Link
3
Link
4
Link
5
Link
6
Link
7
Link
8
Link
9
Link
10
Link
Created: 2016-04-06 Wed 21:14
Emacs 24.5.1 (Org mode 8.2.10)
Validate
The above content is the reason why the parameter type of Java ArrayList.toArray(T[]) method introduced to you by the editor is T instead of E. I hope it will be helpful to everyone!