Search code examples
javagenericspolymorphismtype-safety

What is the use of Generics when the ClassCastException in following code is Not detected at compile time, even after making the code Generic?


I have read that the entire point of Generics is that the add stability to our code by making more of our bugs (essentially that ones which occur when a variable is assigned a value whose type is not compatible with the type of the variable) detectable at compile time.

Following is a non generic class in which I get a RunTimeException, ClassCastException occures, at the statement B bForStoringReturnedAOne = (B) box.aMethod(c);. I was expecting that if I make this code Generic, this ClassCastException will not occur, as the use of Generics will somehow make the bug causing the exception, DETECTABLE AT COMPILE TIME.

So I posted a generic version of this code as well. The problem is that no bug is detected at COMPILE TIME, and I get the same ClassCastException at the same statement. So the question is that what is the difference? What have generics helped with? What is the point of existence of generics? Even after using generics, the bug/exception is still not detected at compile time.

NON-generic version:

public class SomeClass {

    private class A {}

    private class B extends A {}

    private class C extends A {}



    private class Box {
        private A aMethod(A a) {
            return a;
        }
    }

    public static void main(String[] args) {
        SomeClass someClass = new SomeClass();

        B b = someClass.new B();

        C c = someClass.new C();

        Box box = someClass.new Box();

        B bForStoringReturnedA = (B) box.aMethod(b);

        B bForStoringReturnedAOne = (B) box.aMethod(c);//*****ClassCastException

    }

}

Generic version:

public class AnotherClass {

    private class A {}

    private class B extends A {}

    private class C extends A {}


    private class Box<T> {
        private T aMethod(T t) {
            return t;
        }
    }

    public static void main(String[] args) {
        AnotherClass someClass = new AnotherClass();

        B b = someClass.new B();

        C c = someClass.new C();

        Box<A> box = someClass.new Box<>();

        B bForStoringReturnedA = (B) box.aMethod(b);

        B bForStoringReturnedAOne = (B) box.aMethod(c);//*****ClassCastException

    }

}

Solution

  • Generics are perfectly doing their job in the example you gave.

    You make a Box<A>, which has aMethod that takes in A and returns A (after type inference).

    You pass it a B, and the method returns it as an A. You then cast it to a B, which works since the object actually is a B.

    You then pass it a C and it is also returned as an A. You then cast it to a B which throws an exception since the object is not actually a B.

    This is basically the same as doing:

    Box<A> box = someClass.new Box<>();
    
    A a1 = box.aMethod(b);
    A a2 = box.aMethod(c);
    
    B b1 = (B) a1;
    B b2 = (B) a2;
    

    I don't see how you expected generics to help you there.


    If you however made a Box<B>:

    Box<B> box = someClass.new Box<>();
    
    B b1 = box.aMethod(b); // OK, + no need to cast
    B b2 = box.aMethod(c); // Compile time error
    
    error: method aMethod in class Box<T> cannot be applied to given types;
        B b2 = box.aMethod(c); // Compile time error
                  ^
      required: B
      found: C
      reason: argument mismatch; C cannot be converted to B
      where T is a type-variable:
        T extends Object declared in class Box
    1 error
    

    The compiler correctly guarantees type safety, by giving an error.