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.