Are type arguments passed along with an argument to a method?

Viewed 110

From my own experimenting I've concluded that any type arguments of an object or object reference is stripped when passed as an argument to a method. The question of type arguments pops up if a parameter of the method is parameterized with type parameters:

<T> void method (ArrayList<T> list) {
    list.add( (T) new Integer(4));
    sysout (list.get(0));
}

Then if we pass each of two parameterized ArrayLists to an invocation of this method:

method(new ArrayList<Integer>());
method(new ArrayList<String>());

We will see that neither produces an error and both print 4. I assume that the compiler retains the erasure of T as Object. Does this experiment not prove that type arguments aren't passed to a method?

2 Answers

The case of an ArrayList<T> is really the same case as a plain T. In both cases, T is used at compile time for the purposes of static type checking. However, at runtime, T is erased.

Looking at the code you posted, the compiled method is equivalent to this:

void method (ArrayList<Object> list) {
    list.add( (Object) new Integer(4));
    sysout (list.get(0));
}

And the invocations are effectively:

method(new ArrayList<Object>());
method(new ArrayList<Object>());

So from the runtime's perspective, you're storing Object in an ArrayList<Object>, which is fine.

Now, if memory serves, you should be getting a warning about an unchecked cast for the expression (T) new Integer(4). That's a good warning to pay attention to! Casts usually throw an exception if the target type is not compatible with the actual type. In this case, though, T does not exist at runtime and therefore cannot be dynamically checked - the cast is a no-op. But, if we modify the example to return the casted value, you can start to see problems at runtime:

<T> T method (ArrayList<T> list) {
    T value = (T) new Integer(4);
    list.add(value);
    return value;
}

And then

String result = method(new ArrayList<String>());

Even though the cast succeeds (it's just a no-op), an exception will be thrown when the return value is assigned to result since the value is an Integer and not a String.

This is just a simple case where the compiler isn't enabled to force you to use T within certain bounds. It's a bit as if your method() method doesn't care about T as a type, as you noticed.

The code is compiling, but with a warning. Your unchecked cast to T is not always without consequences:

ArrayList<String> strings = new ArrayList<>();
method(strings);
System.out.println(strings.get(0));

And:

Exception in thread "main" java.lang.ClassCastException: 
    java.lang.Integer cannot be cast to java.lang.String

This exception is raised by System.out.println(strings.get(3));, because this call to println is linked to the overload taking a String, and the cast doesn't pass.

That means that although method() doesn't check the cast to T (at compile time or at runtime), the caller does. In my main() method, T is inferred as String and the runtime is able to perform the relevant type checks.

I assume that the compiler retains the erasure of T as Object.

Roughly speaking, yes. But the compiler would be able to enforce more type safety in other cases, where T is bounded. The following won't even compile:

static <T extends String> void method(ArrayList<T> list) {
    list.add((T) new Integer(4));
    System.out.println(list.get(0));
}

So, "Does this experiment not prove that type arguments aren't passed to a method?" No. This is just one scenario of many.

Related