There are two different contexts to consider here: compile-time and run-time.
The difference between generic and non-generic classes at compile-time is the presence of one or more generic arguments in the signature of the class or method.
At compile time, Test<String>
and Test<Integer>
are treated as different types. The benefit of this is that if you try to use a Test<String>
someplace where a Test<Integer>
is expected, the compiler will complain.
sObj = sObj2; // compiler complains
This is good, because it was probably a simple mistake, which you want to catch and fix right away.
At run-time, each object has a little extra metadata associated with it to tell you what type it is. This is helpful for a lot of reasons, including Reflection (calling methods like .getClass()
) and preventing invalid casts:
Object obj = "foo";
Integer i = (Integer) obj; // compiler doesn't complain, but an exception is thrown
Type Erasure means that this extra little bit of metadata contains no information about generic types at run time.
if(sObj.getClass().equals(sObj2.getClass())) { // This will be true
sObj = (Test<String>) sObj1; // this will not produce an error.
}
There are some down-sides to this. For example, the code above is clearly where there's an error in the logic, but instead of seeing an invalid cast in the line above, you'll probably end up getting an invalid cast in a completely different part of code when you try to convert an Integer
to a String
.
But by the time the Java language added generics, implementing it any other way would have had a lot of other draw-backs in terms of effort and loss of backwards-compatibility. So they chose to use Type Erasure instead of Reified Generics.
See also: Why should I care that Java doesn't have reified generics?