Search code examples
javaandroidinheritancecastingclasscastexception

Superclass can not be cast to subclass in one example, while casts smoothly in another example. Why?


Edit # 1 add at the bottom of the question.


I got the ClassCastException in an Android project, but the problem seemed to be in the domain of Java, so I wrote the following SSCCE. In this SSCCE, I get the following exception on PVC parentViewHolder = (PVC) holder;:

Exception in thread "main" Overridden onBindViewHolder in my ExpandableRecyclerAdapter called.
java.lang.ClassCastException: practice_programs.inheritance.class_cast_to_super.RecyclerView$ViewHolder cannot be cast to practice_programs.inheritance.class_cast_to_super.ParentViewHolder
    at practice_programs.inheritance.class_cast_to_super.ExpandableRecyclerAdapter.onBindViewHolder(ExpandableRecyclerAdapter.java:9)
    at practice_programs.inheritance.class_cast_to_super.ExpandableRecyclerAdapter.main(ExpandableRecyclerAdapter.java:15)

As you can see in the code given, we are trying to cast a RecyclerView.ViewHolder to ParentViewHolder (which is actually a subclass of RecyclerView.ViewHolder)


Although I do understand that Superclass can not be cast to subclass, but my confusion is the following:

The Android project that I am working on actually uses this ExpandableRecyclerView implementation as a library project. In this project, the same thing happening does not cast any problems. That is see Line # 146 on in this class in which RecyclerView.ViewHolder is being cast to ParentViewHolder which, you can see on Line#19 here, extends RecyclerView.ViewHolder.


SSCCE:

public class ExpandableRecyclerAdapter<PVC extends ParentViewHolder> extends RecyclerView.Adapter{

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        System.out.println("Overridden onBindViewHolder in my ExpandableRecyclerAdapter called.");
        PVC parentViewHolder = (PVC) holder;//EXCEPTION***************************************
        super.onBindViewHolder(parentViewHolder, position);
    }

    public static void main (String [] args) {
        new ExpandableRecyclerAdapter().onBindViewHolder(new RecyclerView.ViewHolder(), 1);
    }

}

ParentViewHolder.java

public class ParentViewHolder extends RecyclerView.ViewHolder {

    String string = "XAR!";

}

RecyclerView.java

public class RecyclerView {

    public static class ViewHolder {}

    public static class Adapter {
        public void onBindViewHolder(ViewHolder viewHolder, int position) {
            System.out.println("onBindViewHolder of RecyclerView.ViewHolder called."); 
            System.out.println("The string of the passed viewHolder is " + ((ParentViewHolder) viewHolder).string); 
        }
    }
}

_____________________________________________________________________________

EDIT 1:

Another piece of information that may be important is that according to my needs, I made a tiny change to ExpandableRecyclerAdapter:

  1. Line # 119 and Line # 123 are method calls in the method body of onCreateViewHolder method which receives an argument int viewType. I added this viewType argument to the calls to onCreateParentViewHolder and onCreateChildViewHolder on Line#119 and Line#123.

  2. In accordance with ^, I add the int viewType arguments to the corresponding method declarations on Line#187 and Line#195


Solution

  • I think you have a wrong mental picture of what "casting" is - you seem to imagine that casting changes class of an object. This never happens!

    Casting only allows us to reveal the true type of an object that is referenced by a variable of superclass type. This is sometimes necessary, because in Java both references and objects have types, and they do not have to exactly match.

    In this example mySuperRef is a reference of type MySuper, but it really points to an object of type MySub. That's why we can cast it to reveal the true type:

    MySuper mySuperRef = new MySub();
    MySub mySubRef = (MySub)mySuperRef;
    

    In this example, the same cast will give as an error, because the "true type" of an object referenced by mySuperRef is different:

    MySuper mySuperRef = new MySuper();
    MySub mySubRef = (MySub)mySuperRef;
    

    To wrap it up: in Java you can only cast an object to a type that the object already has. Casting does not allow us to modify objects in any way, it is only used to assign the object to it's proper reference variable.