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);
}
}
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;
}