Code Complete
Code Complete

Reputation: 3206

Cast to generic type (T) is never checked by the compiler?

static <T> void f1(Base arg1, T arg2) {
    T loc = (T) arg1; // why Derived is "kind of cast" to String?
    System.out.println(loc.getClass().getName()); // would print: Derived
}

f1(new Derived(), "sdf");  // T is String - inferred from arg2

class Base { }

class Derived extends Base { }

Am I correct in my thoughts: writing cast (T) means "compiler cannot and is not going to check this cast anyway". At compile-time the compiler does not know what arg2 would be (and it could be anything), so compiler cannot rule out that the cast could work and has to trust the programmer. Thus this cast is just never checked at compile time. In runtime local var declaration looks like Object loc = arg1; (after type erasure). So everything works fine just because compiler never cares about this (T) cast?

P.S: My research: this, and this. This is also very interesting ("casting primitive to generic": (T) true) My question is more clearly pinpointed on the problem, the question is also about whether the cast (T) checked by the compiler and there are no distractions in the code sample in question.

Upvotes: 6

Views: 1034

Answers (2)

Alexey Romanov
Alexey Romanov

Reputation: 170723

At compile-time the compiler does not know what arg2 would be (and it could be anything), so compiler cannot rule out that the cast could work and has to trust the programmer.

I'd rather say the compiler doesn't know what T will be. It could be e.g. Object in which case the cast will be legal. The designers decided to prefer allowing possibly-illegal casts to disallowing possibly-legal casts in this case.

The other problem is that the compiler can't produce actual "cast to T" because JVM only has "cast to specific class" instruction. So the cast appears to succeed at runtime as well.

If you actually want to have a checked cast, you can achieve it by passing Class<T> as an extra argument:

static <T> void f1(Base arg1, Class<T> clazz) {
    T loc = clazz.cast(arg1); // will throw exception
    System.out.println(loc.getClass().getName());
}

In your case you could write

static <T> void f1(Base arg1, T arg2) {
    T loc = (T) arg2.getClass().cast(arg1);
    System.out.println(loc.getClass().getName());
}

It should be instructive to figure out in what cases it's different from the previous code.

Upvotes: 1

Andy Turner
Andy Turner

Reputation: 140319

This is covered in JLS Sec 5.5.1:

Given a compile-time reference type S(source) and a compile-time reference type T (target), a casting conversion exists from S to T if no compile-time errors occur due to the following rules.

...

If T is a class type, then either |S| <: |T|, or |T| <: |S|. Otherwise, a compile-time error occurs.

...

If T is a type variable, then this algorithm is applied recursively, using the upper bound of T in place of T.

So the compiler is using Object in place of T, and thus it considers the cast legal.

Upvotes: 3

Related Questions