The question can also be viewed as: Why does a generic of String assigned a value of Integer issue a run time error when it's printed? But the answer to that question seems obvious, the title question does not to me. I do not understand why there is an error for one and not the other. The problem applies to this example. Let's say you have a parameterized Box class:
class Box <T> {
T value;
void setValue(T value) {
this.value = value;
}
T getValue() {
return value;
}
}
and you have three instances of this class, one has no type argument and the other two have either String or Integer. The Box of String and Integer are instantiated to the raw object:
Box rawBox = new Box();
Box <Integer> intBox = rawBox;
Box <String> stringBox = rawBox;
When we pass setValue() values via the raw reference and print getValue() via respective parameterized references the question I am having arises:
Integer integerVal = 4;
rawBox.setValue(integerVal);
System.out.println(intBox.getValue());
// System.out.println(stringBox.getValue()); // ClassCastException a Integer cannot be assigned to a String
rawBox.setValue("hi");
System.out.println(intBox.getValue()); // Why is there no ClassCastException for assigning a String to Integer?
System.out.println(stringBox.getValue());
The ClassCastException error is only issued when the getValue() is printed, not when setValue() is invoked. So why is it that a parameterized object with a type argument of Integer instantiated to a rawtype can assign it's generic a value of String via the raw reference and have no run time error when the getValue() is printed but if the same parameterized type has a type argument of String and it's generic is assigned a value of Integer via raw type it will throw a ClassCastException when it's getValue() is printed?
You probably understand that Box.value
is an Object
at runtime and that therefore Box.getValue
must return Object
.
There are overloads of PrintStream.println
for all the primitive types, for Object
, and for String
. This is presumably to avoid an extra call to Object.toString
when the value being printed is known to be a String
.
So imagine what the generated bytecode will look like for the two calls to println
:
Integer integerVal = 4;
rawBox.setValue(integerVal);
System.out.println(intBox.getValue());
In this case, intBox.getValue()
returns Object
, and so we're going to call the version of println
that accepts Object
. The compiler knows that the return value is supposed to be Integer
, but that doesn't matter, because there's no overload of println
that accepts Integer
.
System.out.println(stringBox.getValue());
Here, while stringBox.getValue()
returns Object
, the compiler knows that it should be a String
and wants to call the version of println
that accepts a String
. That requires downcasting the return value to String
, which fails because it's actually an Integer
.