Home > front end >  Why does this unchecked cast NOT cause a ClassCastException?
Why does this unchecked cast NOT cause a ClassCastException?

Time:11-08

I am doing an experiment with generics, and I recently came across this phenomenon. In a constructor for a parameterized class, I am doing an unchecked cast. I know this is bad practice (my IDE also gives me a warning, as it should). In my main() method, I am expecting the first line to run successfully and for the second to cause a ClassCastException to be thrown. However, this does not happen.

I expect the line Animal<String> bad = new Animal() to throw an exception, as (T) this in the Animal constructor would effectively become (String) this where this is of type Animal; which is of course an invalid cast. However, this does not happen. The program runs successfully and exits.

public class Generics {

    public static class Animal<T> {

        private final T value;

        @SuppressWarnings("unchecked")
        public Animal() {
            this.value = (T) this;
            System.out.println(value.getClass());
        }

    }

    public static class Cat extends Animal<Cat> {

        public Cat() {
            super(); /* of course, this works */
        }

    }

    public static void main(String[] args) {
        Cat cat = new Cat(); /* all good */
        Animal<String> test = new Animal<>();
    }

}

Furthermore, printing the value of value.getClass() yields an unexpected output:

Generics$Cat    /* as expected */
Generics$Animal /* expected a ClassCastException here */

I know this is an odd way to use generics, but as I said earlier, this is simply an experiment. I just want to know why this code runs without an exception being thrown. If you need any more information, please let me know in the comments and I will edit the question accordingly. Thank you!

I also realize that this post is similar this question. However, I'm not entirely sure what the author of that question is trying to do there as his code is structured differently from mine.

CodePudding user response:

Louis Wasserman correctly notes that your "cast" to T is erased to Object at compile-time, but you're on the right track. Instead, however, the cast that fails will be inserted in the code that relies on the generic:

public class Animal<T> {
  T value;

  @SuppressWarnings("unchecked")
  public Animal() {
    this.value = (T) this;
  }

  T getValue() {
    return value;
  }
}

So far this in itself won't cause an exception, since the physical field type of value is Object. However, this is where you'll get the ClassCastException:

void thisFails() {
  Animal<String> animal = new Animal();
  // ^ animal.value is not a String!

  String value = animal.getValue();
  // This is implemented by the compiler as
  // `String value = (String) animal.getValue()`
  // and *this* will fail at runtime with ClassCastException.
}
  • Related