javaandroidinheritanceparameterized-types

Java object casting doesn't work like I need it to


I'm developing native Android app on Java and I faced some unexpected behavior. Look at these classes:

public class Parent<C> {

    protected C mCallback;

    public void setCallback(Object callback) {
        mCallback = (C)callback;
    }
}

class Child<T extends Vehicle>
        extends Parent<Child.Callback<Vehicle>>{

    public void invoke(){
        mCallback.onAction(new Vehicle());
    }

    public interface Callback<T> {
        void onAction(T vehicle);
    }
}

class Vehicle {

}

And now

    Child<Vehicle> child = new Child<Vehicle>();
    child.setCallback(new Object()); // I expect ClassCastException to be thrown here!
    child.invoke(); //But it's thrown only here!

Why ClassCastException isn't thrown in .setCallback() method?

Why it is thrown only when I try to access method of Callback interface?

How to check Object callback is instance of C? Or how can I at least get ClassCastException in setCallback() method?

P.S. This is simplified example! Please consider the same, but real life question here: How to check typed callback type inside Fragment.onAttach()


One of the solutions proposed by @luckydog32 in comments here

public abstract class Parent<C> {

    protected C mCallback;

    public void setCallback(Object callback) {
        mCallback = castCallback(callback);
    }

    protected abstract C castCallback(Object callback) throws ClassCastException;
}

class Child<T extends Vehicle>
        extends Parent<Child.Callback<Vehicle>>{
    @Override
    protected Callback<Vehicle> castCallback(Object callback) throws ClassCastException {
        return (Callback<Vehicle>)callback;
    }

    public void invoke(){
        mCallback.onAction(new Vehicle());
    }

    public interface Callback<T> {
        void onAction(T vehicle);
    }
}

Solution

  • This is because of something called Type Erasure. Basically after you program is compiled generic types are replaced with their Upper Bound. So for example

    class Child<T extends Vehicle>
    

    Every instance of T is just treated as if it were a Vehicle class.

    So in the code:

    public void setCallback(Object callback) {
        mCallback = (C)callback;
    }
    

    During run time you are pretty much saying:

    public void setCallback(Object callback) {
        mCallback = (Object)callback;
    }
    

    Since you are casting callback immediately, the simple fix for your code would to just do:

    public void setCallback(C callback) {
        mCallback = callback;
    }