Generic Type cast [duplicate] - java

This question already has answers here:
Casting to generic type in Java doesn't raise ClassCastException?
(5 answers)
Closed 7 years ago.
I have the following class (simplified but still a working example):
class Test<T> {
List<T> l = new ArrayList<>();
public Test() {
}
public void add(Object o) {
l.add((T)o);
}
}
And the test code:
Test<Double> t = new Test<>();
t.add(1);
t.add(1.2);
t.add(-5.6e-2);
t.add("hello");
Everything is working fine, and that's not what I was expecting. Shouldn't the add method throw a ClassCastException? If I add a get method that's more or less the same thing:
public T get(int i) {
return l.get(i);
}
.../...
t.get(1); // OK.
t.get(3); // OK (?)
Double d = t.get(3); // throws ClassCastException
Why is it only on variable assignment that the exception is thrown? How can I enforce type consistency if the (T) cast doesn't work?

Shouldn't the add method throw a ClassCastException?
No, it shouldn't (although I wish it did). Long story short, Java implementation of generics discards type information after compiling your code, so List<T> is allowed to take any Object, and the cast inside your add method is not checked.
Why is it only on variable assignment that the exception is thrown?
Because the cast to Double there is inserted by the compiler. Java compiler knows that the return type of get is T, which is Double, so it inserts a cast to match the type of the variable d, to which the result is being assigned.
Here is how you can implement a generic-safe cast:
class Test<T> {
private final Class<T> cl;
List<T> l = new ArrayList<>();
public Test(Class<T> c) {
cl = c;
}
public void add(Object o) {
l.add(cl.cast(o));
}
}
Now the cast is performed by a Class<T> object, so you will get a ClassCastException on an attempt to insert an object of an incorrect type.

As an alternative solution you can use Collections.checkedList:
class Test<T> {
List<T> l;
public Test(Class<T> c) {
l = Collections.checkedList(new ArrayList<T>(), c);
}
public void add(Object o) {
l.add((T) o);
}
}
This way you will get the following exception:
Exception in thread "main" java.lang.ClassCastException: Attempt to insert
class java.lang.Integer element into collection with element type class java.lang.Double
at java.util.Collections$CheckedCollection.typeCheck(Collections.java:3037)
at java.util.Collections$CheckedCollection.add(Collections.java:3080)
at Test.add(Test.java:13)

For the completeness of this resource, here's the difference in compiled bytecode between a cast to a generic:
public void add(java.lang.Object);
Code:
0: aload_0
1: getfield #4 // Field l:Ljava/util/List;
4: aload_1
5: invokeinterface #7, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
10: pop
11: return
And an explicit cast to a Double with no generics:
public void add(java.lang.Object);
Code:
0: aload_0
1: getfield #4 // Field l:Ljava/util/List;
4: aload_1
5: checkcast #7 // class java/lang/Double
8: invokeinterface #8, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
13: pop
14: return
You can see that the version with generics doesn't perform the checkcast instruction at all (thanks to type erasure, so you shouldn't expect an exception when giving it data with an unmatched class. It's unfortunate that this isn't more strictly enforced, but this makes sense as generics are for making stricter compile-time type checks, and aren't much help at runtime due to type erasure.
Java will check the types of function arguments to see if there is a type match, or if a type promotion can be performed. In your case, String is the type of the argument, and that can be promoted to Object, which is the extent of the compile-time type checks that ensure that function call works.
There are a few options, and dasblinkenlight's solution is probably the most elegant. (You may not be able to change the method signature, say, if you are overriding an inherited add method, or plan on passing down the add method, etc).
Another option that may help is using a bounded type parameter instead of an unbounded one. Unbounded type parameters are completely lost after compilation due to type erasure, but using a bounded type parameter will replace instances of the generic type with that/those it must extend.
class Test<T extends Number> {
Of course, T is not truly generic at this point, but using this class definition will enforce types at runtime since the cast will be checked against the Number superclass. Here's the bytecode to prove it:
public void add(java.lang.Object);
Code:
0: aload_0
1: getfield #4 // Field l:Ljava/util/List;
4: aload_1
5: checkcast #7 // class java/lang/Number
8: invokeinterface #8, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
13: pop
14: return
This class definition generates the desired ClassCastException when trying to add the string.

Related

Java Generic Singleton Factory Pattern

I am having a stump of a time understanding the condundrum below. Here is a code snippet that compiles, yet throws the exception
Exception in thread "main" java.lang.ClassCastException:
TestGenericSingleton$$Lambda$1/303563356 cannot be cast to
TestGenericSingleton$IntegerConsumer
at TestGenericSingleton.main(TestGenericSingleton.java:23)
import java.util.function.Consumer;
public class TestGenericSingleton
{
static final Consumer<Object> NOOP_SINGLETON = t -> {System.out.println("NOOP Consumer accepting " + t);};
#SuppressWarnings("unchecked")
static <R extends Consumer<T>, T> R noopConsumer()
{
return (R)NOOP_SINGLETON;
}
static interface IntegerConsumer extends Consumer<Integer> {};
public static void main(String[] argv)
{
Consumer<Boolean> cb = noopConsumer();
cb.accept(true);
IntegerConsumer ic = t -> {System.out.println("NOOP Consumer2 accepting " + t);} ;
ic.accept(3);
ic = noopConsumer();
ic.accept(3);
System.out.println("Done");
}
}
What stumps me is that the Java compiler can generate a proper IntegerConsumer-compatible object out of the lambda on line 20, yet the previously constructed non-generic lambda constructed as the singleton on line 8 can not be used. Is that because the lambda on line 20 has a reifiable subtype of the Consumer that fits the type of the IntegerConsumer reference immediately, whereas the lambda that is cast on line 10 can not be cast at runtime to a real subtype of Consumer ? But then shouldn't the generic bounded type declaration on line 8 take care of that? Any help is really appreciated !
the previously constructed non-generic lambda constructed as the singleton on line 8 can not be used
We will remove lambda and understand the root cause of the exception. Let's consider the below simpler example.
public class TestGenericObject {
static final Object NOOP_SINGLETON = new Object();
static <R extends Object> R noopConsumer() {
return (R) NOOP_SINGLETON;
}
public static void main(String[] argv) {
Object cb = noopConsumer();
Integer ic = noopConsumer(); // Throws java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.Integer
}
}
The exception is justified. NOOP_SINGLETON's actual type is Object but we are trying to cast it to Integer. This would be same as trying, Integer ic = (Integer) new Object(). In your case, for the same reason you cannot cast Consumer<Object> type to IntegerConsumer.
One interesting observation is the exception is not thrown within the noopConsumer() and is rather thrown in the main().
Below is the output of javap -v -c for the method noopConsumer
... // Removed lines for clarity
static <R extends java.lang.Object> R noopConsumer();
...
Code:
stack=1, locals=0, args_size=0
0: getstatic #2 // Field NOOP_SINGLETON:Ljava/lang/Object;
3: areturn
You can see there is no op code present for casting. But for the main()
public static void main(java.lang.String[]);
Code:
stack=1, locals=3, args_size=1
0: invokestatic #3 // Method noopConsumer:()Ljava/lang/Object;
3: astore_1
4: invokestatic #3 // Method noopConsumer:()Ljava/lang/Object;
7: checkcast #4 // class java/lang/Integer
10: astore_2
11: return
at this line 7:checkcast #4, it checks if returned type is of Integer. This behaviour is due to 2 reasons
In noopConsumer(), the tighter bound of R is Object and NOOP_SINGLETON is of type Object as well. Hence, post type erasure, the casting is redundant and removed.
The reason main() has a cast check is again due to Type Erasure. As mentioned in the link, if required, type casting will be inserted during type erasure.
Back to Lambdas. Lambdas use invokedynamic opcode to generate code at runtime. This and this are excellent resources to understand better about lambda handling at runtime.
For the below code,
public static void main(String[] argv) {
Consumer<Object> NOOP_SINGLETON = t -> {System.out.println("NOOP Consumer accepting " + t);};
TestGenericSingleton.IntegerConsumer ic = t -> {System.out.println("NOOP Consumer2 accepting " + t);} ;
}
lets analyse the byte code.
public static void main(java.lang.String[]);
...
Code:
stack=1, locals=3, args_size=1
0: invokedynamic #2, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
5: astore_1
6: invokedynamic #3, 0 // InvokeDynamic #1:accept:()Lcom/TestGenericSingleton$IntegerConsumer;
11: astore_2
12: return
The invokedynamic passes 2 different expected types Ljava/util/function/Consumer and ()Lcom/TestGenericSingleton$IntegerConsumer to LambdaMetafactory.metafactory().
So though the code t -> {System.out.println("NOOP Consumer accepting " + t);} within the lambdas are same, they are 2 different types.
To summarize, the lambdas are built at runtime and the returned instance will have the type specified in the declaration. Hence, NOOP_SINGLETON is of type Consumer and ic is of type IntegerConsumer. Casting from type Consumer to IntegerConsumer will fail for the same reason, Integer ic = (Integer)new Object() fails.

getClass() returns derived class name after upcasting

I've come across a weird thing while learning Java. Consider the following program:
public class GetClassNameInheritance {
public static void main(String[] args) {
Employee e = new Employee();
Person p = (Person) e;
System.out.println(p.getClass());
}
}
class Person {
}
class Employee extends Person {
}
I was expecting the output to be Person because of the cast, but it is Employee! Honestly, I'm stumped and can't find an explanation. The official Oracle tutorial doesn't mention this behavior, and the docs seem too terse to be helpful. I can make out from other StackOverflow examples that this is something to do with "runtime class" but I don't understand the underlying idea. Can someone explain what's going on here?
In Java, Type casting does not change the type of object. Once an Employee, your object is always an Employee. Type casts do not work like in other languages such as C/C++.
Though the p reference is of type Person, it is in fact referring to an object of type Employee in the JVM heap. When you invoke the p.getClass() method, the reference in turn invokes the method on the object and not on the reference. That's the reason you see Employee in your output.
The only type of casts that would work is the one that you can perform on primitives.
int x = (int)2.5f;
The return value will be type cast to int.
You can read more about type casts and conversions here:
http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html
http://www.wideskills.com/java-tutorial/java-object-typecasting
Well let's look what happens when the main method is compiled:
0: new #2 // class Employee
3: dup
4: invokespecial #3 // Method Employee (Constructor)
7: astore_1 // Save local variable "e"
8: aload_1 // Load local variable "e"
9: astore_2 // Save to local variable "p"
10: getstatic #4 // Field System.out PrintStream;
13: aload_2 // Load local variable "p"
14: invokevirtual #5 // Method Object.getClass()
17: invokevirtual #6 // Method PrintStream.println(Object)
And if we were to look at a decompiler's interpretation of this bytecode:
public static void main(String[] args) {
Employee e = new Employee();
System.out.println(e.getClass());
}
The compiler has "optimized" the code which results in your cast being removed since it was deemed unnecessary. Anacron's answer explains why the compiler deemed it unnecessary.
If you want to know the super class of current class, you can use
p.getClass().getSuperclass() //returns person.
From the docs
getClass() always returns the runtime class of this Object. The returned Class object is the object that is locked by static synchronized methods of the represented class.
And a runtime class is one which has been instantiated with the constructor call.
Actually, if you will see, here Person p = (Person) e; the cast is not needed, you can always assign a derived class object to a base class.
For Example
List<String> list = new ArrayList<>();
Above will not throw an error.

Different methods for casting an object in Java

I know the three following methods that can be used for casting objects.
Object o = "str";
String str1 = (String) o; // Method 1
String str2 = o.toString(); // Method 2
String str3 = String.class.cast(o); // Method 3
Which approach is better, and what are the pros/cons of one method as compared to the others?
What happens to the object during the time of casting internally?
The second method that you show is not casting; it's simply calling the toString() method on an object, which is not different than any other method call:
String str2 = o.toString();
The effect of the first and third methods is essentially the same. I would prefer using the first method.
What happened with object on the time of casting internally?
Nothing happens to the object. Casting is not a way to somehow automatically convert objects from one type to another type. The only thing that a cast does, is tell the compiler to accept the assignment statement and not check the types for this statement. You're saying to the compiler "I know better than you what kind of object this is, so let me do this assignment and don't complain about the type".
In your example, the type of the variable o is Object. When you assign o to a variable of type String, the compiler won't allow it because it checks the types, and it can't be sure that o in fact refers to a String object. So you use a cast to tell the compiler "I know this is a String object, so let me do this assignment".
The type will still be checked, but at runtime, not at compile time. If, at runtime, the type of the object is not String, you'll get a ClassCastException.
The first one casts the o reference, whose declared type is Object, and whose actual, concrete type is String, to a String. This is the canonical way of casting. That will only work if the object referenced by o is actually of type String. If it isn't, you'll have a ClassCastException.
The second one doesn't cast at all. It calls the toString() on the object referenced by o. That will always work, but it's really really different from a cast.
The third one uses reflection to do a cast. That will have the same effect as the first one, and will work in the same circumstances. But this will generally only be used when the code doesn't actually know the type of the class to cast to:
Class<?> someClassToCastTo = ...; // could be String.class or anything else, you don't know)
String str3 = someClassToCastTo.cast(o);
Nothing happens to the object when it's cast. Casting checks that the object is indeed of class String, and fails otherwise, that's all. But once cast to a variable of type String, you can access the methods that exist in String, and that you couldn't have called when you had a variable of type Object.
Here it goes:
Object o = "str";
String str1 = (String) o;
This works only when the object actually is a string.
String str2 = o.toString();
When you use toString() on a String object, you obtain the string itself. It will throw an exception when object o is null.
String str3 = String.class.cast(o);
Mainly used when using reflection, i.e. when you want to retrieve the Class token via reflection.
You can actually reason this out yourself.
$ cat Cast.java
public class Cast {
private Cast() {}
public static void main(String[] args) {
Object o = "str";
String str1 = (String) o; // Method 1
String str2 = o.toString(); // Method 2
String str3 = String.class.cast(o); // Method 3
}
}
$ javac Cast.java
$ javap -c Cast
Compiled from "Cast.java"
public class Cast {
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String str
2: astore_1
3: aload_1
4: checkcast #3 // class java/lang/String
7: astore_2
8: aload_1
9: invokevirtual #4 // Method java/lang/Object.toString:()Ljava/lang/String;
12: astore_3
13: ldc #3 // class java/lang/String
15: aload_1
16: invokevirtual #5 // Method java/lang/Class.cast:(Ljava/lang/Object;)Ljava/lang/Object;
19: checkcast #3 // class java/lang/String
22: astore 4
24: return
}
As you can see,
Method 1 is just a checkcast opcode.
Method 2 is an invokevirtual opcode.
Method 3 is an ldc (load class), followed by invokevirtual and checkcast.
Obviously, Method 3 is inferior in terms of verbosity, readability, and performance.
Of 1 and 2, which is better?
checkcast means "look at this object: is it really a String?" — if so, proceed with the assignment; if not, throw a ClassCastException.
invokevirtual means "look up which toString() method to call based on the class of o" — in this case, it's String.toString(). The obvious implementation of that method is
public String toString() {
return this;
}
Given the choice between asking "Is it a String? and "What type is it? which method do we call as a result? Let's execute String.toString() on with str2 as the implied parameter." — Method 1 should be quite a bit simpler.
Java is strongly typed language and it only allows casting an object to one of it parent classes or interfaces. That is, if you have the following:
class A {}
interface I {}
class B extends A implements I {}
class C {}
You can cast an object of type B like so:
B b = new B();
A a = b; // no need for explicit casting
I i = b;
Object o = b; // Object is implicit parent of A
C c = b; // ERROR C isn't a parent class of b
This is called upcasting. You can also downcast:
A a = new B();
B b = (B) b;
you need to use explicit cast here and JVM will check in runtime if the cast is really allowed.
The String.class.cast(o) casting is useful, when you don't know the specific type you're casting to in compile time.
The .toString() isn't casting. It's just a method, which is supposed to return a String representation of your object. But the representation isn't the object, it's just a representation. This method is defined in the Object class, so it's present in all classes and is baked in the language somewhat. That is, if you write
String s = "Hell " + o;
JVM will call o.toString() method for you to obtain it representation.

No ClassCastException when casting to generic type different to actual class [duplicate]

This question already has answers here:
Casting to generic type in Java doesn't raise ClassCastException?
(5 answers)
Closed 8 years ago.
I have some code which looks something like this (part of a negative test of the method get):
import java.util.*;
public class Test {
Map<String, Object> map = new HashMap<>();
public static void main (String ... args) {
Test test = new Test();
test.put("test", "value"); // Store a String
System.out.println("Test: " + test.get("test", Double.class)); // Retrieve it as a Double
}
public <T> T get(String key, Class<T> clazz) {
return (T) map.get(key);
}
public void put(String key, Object value) {
map.put(key, value);
}
}
I was expecting it to throw a ClassCastException but it runs through successfully printing:
Test: value
Why doesn't it throw?
That's because you are casting to the generic type T, which is erased at runtime, like all Java generics. So what actually happens at runtime, is that you are casting to Object and not to Double.
Note that, for example, if T was defined as <T extends Number>, you would be casting to Number (but still not to Double).
If you want to do some runtime type checking, you need to use the actual parameter clazz (which is available at runtime), and not the generic type T (which is erased). For instance, you could do something like:
public <T> T get(String key, Class<T> clazz) {
return clazz.cast(map.get(key));
}
I found a difference in the byte code when a method is called on the returned "Double" and when no method is called.
For example, if you were to call doubleValue() (or even getClass()) on the returned "Double", then the ClassCastException occurs. Using javap -c Test, I get the following bytecode:
34: ldc #15 // class java/lang/Double
36: invokevirtual #16 // Method get (Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
39: checkcast #15 // class java/lang/Double
42: invokevirtual #17 // Method java/lang/Double.doubleValue:()D
45: invokevirtual #18 // Method java/lang/StringBuilder.append:(D)Ljava/lang/StringBuilder;
The checkcast operation must be throwing the ClassCastException. Also, in the implicit StringBuilder, append(double) would have been called.
Without a call to doubleValue() (or getClass()):
34: ldc #15 // class java/lang/Double
36: invokevirtual #16 // Method get:(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
39: invokevirtual #17 // Method java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder;
There is no checkcast operation, and append(Object) is called on the implicit StringBuilder, because after type erasure, T is just Object anyway.
You do not get a ClassCastException because the context in which you are returning the value from the map does not require the compiler to perform a check at this point as it is equivalent to assigning the value to a variable of type Object (See rgettman's answer).
The call to:
test.get("test", Double.class);
is part of a string concatenation operation using the + operator. The object returned from your map is just being treat as if it is an Object. In order to display the returned 'object' as a String a call to the toString() method is required and since this is a method on Object no cast is required.
If you take the call to test.get("test", Double.class); outside of the context of the string concatenation you will see that it does't work i.e.
This does not compile:
// can't assign a Double to a variable of type String...
String val = test.get("test", Double.class);
But this does:
String val = test.get("test", Double.class).toString();
To put it another way, your code:
System.out.println("Test: " + test.get("test", Double.class));
is equivalent to:
Object obj = test.get("test", Double.class);
System.out.println("Test: " + obj);
or:
Object obj = test.get("test", Double.class);
String value = obj.toString();
System.out.println("Test: " + value);
It is instructive to consider what the class looks like after removing type parameters (type erasure):
public class Test {
Map map = new HashMap();
public static void main (String ... args) {
Test test = new Test();
test.put("test", "value");
System.out.println("Test: " + test.get("test", Double.class));
}
public Object get(String key, Class clazz) {
return map.get(key);
}
public void put(String key, Object value) {
map.put(key, value);
}
}
This compiles and produces the same result that you see.
The tricky part is this line:
System.out.println("Test: " + test.get("test", Double.class));
If you had done this:
Double foo = test.get("test", Double.class);
then after type erasure the compiler would have inserted a cast (because after type erasure test.get() returns Object):
Double foo = (Double)test.get("test", Double.class);
So analogously, the compiler could have inserted a cast in the above line too, like this:
System.out.println("Test: " + (Double)test.get("test", Double.class));
However, it doesn't insert a cast, because the cast is not necessary for it to compile and behave correctly, since string concatenation (+) works on all objects the same way; it only needs to know the type is Object, not a specific subclass. Therefore, the compiler can omit an unnecessary cast and it does in this case.
It seems that Java is unable to process the cast with an inferred type, however if you use the Class.cast method, the call to get throws an exception as expected :
public <T> T get(String key, Class<T> clazz) {
return clazz.cast(map.get(key)) ;
}
Unfortunately, I am not able to explain that more thoroughly.
Edit : you might be interested by this Oracle doc.

Java: Couldn't comprehend type erasure on a non-static method

This question has to do with type erasure and its implication on arrays SCJP (by Mughal - 3rd edition on page 726). Here's the code in short:
public class MyStack<E> implements IStack<E> {
// Top of stack.
private Node<E> tos;
// Size of stack
private int numOfElements;
:
// Incorrect version
public E[] toArray3() {
E[] toArray = (E[])new Object[numOfElements];
int i=0;
for (E data : this) {
toArray[i++] = data;
}
return toArray;
}
// Correct version
public E[] toArray4(E[] toArray) {
if (toArray.length != numOfElements) {
toArray = E[])java.lang.reflect.Array.newInstance(toArray.getClass().getComponentType(), numOfElements);
}
int i=0;
for (E data : this) {
toArray[i++] = data;
}
return toArray;
}
}
According to the book, method toArray3 is the incorrect version to use while method toArray4 is the correct version since array is of reifiable type. I don't have problem on this. However, I have a problem when it comes to understanding why type parameter E is different for toArray3 and toArray4.
Here's how I found out. Supposedy I have the following:
MyStack<Integer> intStack = new MyStack<Integer>();
intStack.push(9);
Integer[] x = intStack.toArray4(new Integer[0]); // clause 1
System.out.println(x.length);
Integer[] y = intStack.toArray4(new Object[0]); // clause 2 - compiler error
System.out.println(y.length);
Integer[] z = intStack.toArray3(); // clause 3
System.out.println(z.length);
a. For clause 1, no problem. The type parameter E in toArray4 seems to be of Integer.
b. For clause 2, the error is saying that Integer[] is not applicable for Object[]. Does that mean that type erasure of toArray4(E[] toArray) is toArray4[Integer[] toArray)? Why not Object[]? (Though I know that intStack has been instiantiated with Integer.)
c. For clause 3, the runtime error is that saying Object[] cannot be assigned to Integer[]. I do understand this, but why now is the type parameter E on toArray3 Object and not Integer?
I would expect the type parameter E ON toArray3 to be the same as toArray4 since type parameter E itself for intStack is of integer type which are used by both toArray3 AND toArray4.
Please bear in mind that in the body of toArray3, the array Object has been cast back to E[].
On a, no question here.
On b, type erasure happens after type validation. In compile-time, all the types are here.
On c, you explicitly create an array of Object, not Integer— no magic here.
The type parameter stays the same. It's just that you cannot cast Object[] to Integer[].
Getting away from stacks, and narrowing your problem down:
public class MyStack<E> {
public static void main(String[] args) {
Integer[] test = new MyStack<Integer>().test();
}
public E[] test() {
return (E[]) (new Object[10]);
}
}
Gives us the following bytecode:
Compiled from "MyStack.java"
public class org.acm.afilippov.stacko.MyStack extends java.lang.Object{
public org.acm.afilippov.stacko.MyStack();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2; //class org/acm/afilippov/stacko/MyStack
3: dup
4: invokespecial #3; //Method "<init>":()V
7: invokevirtual #4; //Method test:()[Ljava/lang/Object;
10: checkcast #5; //class "[Ljava/lang/Integer;"
13: astore_1
14: return
public java.lang.Object[] test();
Code:
0: bipush 10
2: anewarray #6; //class java/lang/Object
5: checkcast #7; //class "[Ljava/lang/Object;"
8: areturn
}
Note that in the test() method, generic types are erased — it's new Object[], and checkcast Object[].
In main, you assign to a non-generic variable, which means type cast; hence checkcast #5; //class "[Ljava/lang/Integer;"
The trick which worries you in b is based on the fact that arrays are covariant, and the following is allowed:
Object[] array = new Integer[10];
So in case b the generic method has result type Object[]— but since you can assign Integer[] to Object[], it's okay. See more at Generics FAQ.
The difference between these methods is that toArray4() is using reflection to obtain the Class object that represents the correct component type of the array. This ensures that the type information is known at runtime in spite of type erasure.
The key is this line of toArray4():
Array.newInstance(toArray.getClass().getComponentType(), numOfElements);
Unlike generic objects, arrays can provide their component type, since something like Integer[] has a corresponding Class object that can be inspected for this information.
Because of this, toArray4() actually instantiates an Integer[] object when E is Integer, whereas toArray3() instantiates an Object[] and the calling code (through type erasure) attempts to cast it to Integer[], rightly causing a ClassCastException.
In general, reflection is often used as a workaround to type erasure.
As a note, arrays in Java tend to have certain "magic" associated with them, especially with regard to reflection. For example if you look at the source for Class#getComponentType() and Array.newInstance() they both lead to native code.
EDIT: I think the source of your confusion is the cast being done in toArray3():
E[] toArray = (E[])new Object[numOfElements];
This is not the cast that is causing your ClassCastException. Rather it's a second cast which is added by type erasure. To illustrate, let's rewrite toArray3() as if type erasure had been applied:
public Object[] toArray3() {
Object[] toArray = new Object[numOfElements]; //first cast no longer necessary
int i=0;
for (Object data : this) { //pretend MyStack is no longer generic
toArray[i++] = data;
}
return toArray;
}
Then in the calling code:
Integer[] z = (Integer[])intStack.toArray3(); //ClassCastException
As you can see, the ClassCastException is thrown not in the method, but when its result is assigned to z using a cast, which type erasure creates for you. We might verify this if you actually post the stacktrace.

Categories