Reputation:
Suppose, you have to call very often operation T get(int)
which returns an object from the underlying array. Basically, this can be implemented in two ways:
class GenericArray<T> {
final T[] underlying;
GenericArray(Class<T> clazz, int length) {
underlying = (T[]) Array.newInstance(clazz, length);
}
T get(int i) { return underlying[i]; }
}
and
class ObjectArray<T> {
final Object[] underlying;
ObjectArray(int length) {
underlying = new Object[length];
}
T get(int i) { return (T) underlying[i]; }
}
The first one is using reflection, so it would be slower at creation time. The second one is using downcasting which introduces some overhead. Due of generic type erasure at runtime, there has to be some implicit casting mechanism.
So is it true, that these two are equal when it comes to get(i)
?
Upvotes: 1
Views: 221
Reputation: 37645
This answer proves that the two get
methods are equivalent. Here is a bytecode free answer explaining how I understand the topic. Remember that generics in Java are implemented using type erasure. Loosely speaking this means that T
is replaced by Object
and casts are inserted where necessary (actually it's not always Object
- if you write class Foo<T extends Number> { ... }
, then T
within the body of the class is replaced by Number
).
This means that the code for your ObjectArray
class is transformed into something like this
class ObjectArray {
final Object[] underlying;
ObjectArray(int length) {
underlying = new Object[length];
}
Object get(int i) {
return underlying[i];
}
}
Notice that there is no cast in the get
method. The (T)
is only needed to make your code compile. It has no impact at runtime, and can never throw a ClassCastException
.
The code for the other class is transformed into something like this:
class GenericArray {
final Object[] underlying;
GenericArray(Class clazz, int length) {
underlying = (Object[]) Array.newInstance(clazz, length);
}
Object get(int i) {
return underlying[i];
}
}
So the get
methods are equivalent. The only difference between the two classes is that reflection is used to generate the array, so it would result in an ArrayStoreException
if you tried to store an object of the wrong type. Since this could only happen if you abused generics by using raw types anyway, it is probably not worth using reflection for this in most situations.
Upvotes: 0
Reputation: 43681
Let's check the bytecode:
Compiled from "ObjectArray.java"
class lines.ObjectArray<T> {
final java.lang.Object[] underlying;
lines.ObjectArray(int);
Code:
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: aload_0
5: iload_1
6: anewarray #3 // class java/lang/Object
9: putfield #13 // Field underlying:[Ljava/lang/Object;
12: return
T get(int);
Code:
0: aload_0
1: getfield #13 // Field underlying:[Ljava/lang/Object;
4: iload_1
5: aaload
6: areturn
}
Compiled from "GenericArray.java"
class lines.GenericArray<T> {
final T[] underlying;
lines.GenericArray(java.lang.Class<T>, int);
Code:
0: aload_0
1: invokespecial #13 // Method java/lang/Object."<init>":()V
4: aload_0
5: aload_1
6: iload_2
7: invokestatic #16 // Method java/lang/reflect/Array.newInstance:(Ljava/lang/Class;I)Ljava/lang/Object;
10: checkcast #22 // class "[Ljava/lang/Object;"
13: putfield #23 // Field underlying:[Ljava/lang/Object;
16: return
T get(int);
Code:
0: aload_0
1: getfield #23 // Field underlying:[Ljava/lang/Object;
4: iload_1
5: aaload
6: areturn
}
As you see, bytecode for get
is identical.
Upvotes: 7