I've been reading Java Generics Tutorials and few threads in Stackoverflow that deal with Generics, and still could not understand a specific case. Here it is:
public class Box<T>
{
private T t;
public T getT ()
{
return t;
}
public void setT (T t)
{
this.t = t;
}
public static void main (String[] args)
{
Box<Integer> intBox = new Box<Integer>();
Box rawBox = intBox;
rawBox.setT("NBA");
System.out.println(rawBox.getT());
System.out.println(intBox.getT());
/*1*/ //System.out.println(intBox.getT().toString());
}
}
Here's the deal,the first print I understand, that is,
System.out.println(rawBox.getT());
prints NBA, because rawBox is of raw type of Box T and it "gives" us Objects.
What I don't get is the second print:
System.out.println(intBox.getT());
which prints NBA. intBox is of a generic type (in this case Box of Integers), which means its getter method should return a value of type T (which is Integer in this case), so what I understand is that the String object held there should be converted to Integer (because this is the argument type given to Box T) and a ClassCastException should be raised in runtime, but it doesn't happen, why is that?
By the way, comment number /1/ adds to the confusion, because if I were to uncomment it, it'll cause ClassCastException to be raised in ruuntime (String cannot be cast to Integer), I don't understand that
Thanks all :)
It's sometimes hard to guess where Java will insert checked casts. In general, it'll only insert them where necessary. The best way to understand the behavior you see is by checking the bytecode!
If we run javap -c Box.class
(after compiling with /*1*/
uncommented), we see:
public static void main(java.lang.String[]);
Code:
...
20: invokevirtual #8 // Method getT:()Ljava/lang/Object;
23: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
26: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
29: aload_1
30: invokevirtual #8 // Method getT:()Ljava/lang/Object;
33: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
36: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
39: aload_1
40: invokevirtual #8 // Method getT:()Ljava/lang/Object;
43: checkcast #10 // class java/lang/Integer
46: invokevirtual #11 // Method java/lang/Integer.toString:()Ljava/lang/String;
49: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
52: return
You can see here that the only time the JVM actually requires a cast to integer is at #43 (checkcast). That's so that it can invokevirtual Integer.toString()
.
The call to println
(#33) does not require a cast because println
takes an Object
, not an Integer
(perhaps you thought you were calling println(int)
, but you're not). So the JVM never needs to check that it's an integer, because it doesn't need to be.
If instead of calling println(Object)
, you called a method that accepted an Integer
, you should see a ClassCastException
instead.
For example, this:
...
print(intBox.getT());
}
private static void print(Integer integer) {
System.out.println(integer);
}
Will perform a cast:
26: aload_1
27: invokevirtual #8 // Method getT:()Ljava/lang/Object;
30: checkcast #10 // class java/lang/Integer
33: invokestatic #11 // Method print:(Ljava/lang/Integer;)V
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With