Search code examples
javainheritanceinterfacecastingscjp

What class does the target object take on after casting?


OK, noob question. I'm studying for the SCJP and got 3 questions on object reference casting wrong which all seem to point to the same misunderstanding. Just wanted to confirm what the right insight should be. Right, here are the questions:

    1.
1. class CodeWalkFour {
2.    public static void main(String[] args){
3.       Car c = new Lexus();
4.       System.out.print(c.speedUp(30) + " ");
5.       Lexus l = new Lexus();
6.       System.out.print(l.speedUp(30, 40, 50));
7.     }
8.  }
9.  class Car {
10.     private int i=0;
11.    int speedUp(int x){
12.        return i;
13.    }
14. }
15. class Lexus extends Car {
16.     private int j = 1;
17.     private int k = 2;
18.       int speedUp(int y){
19.       return j;
20.     }
21.     int speedUp(int... z){
22.         return k;
23.      }
24.  }

I thought that after line 3, c would be a Car, not a Lexus, so the Car.speedUp method would be called, not the Lexus.speedUp method. Turns out it's the latter that's called.

    2.
1. class StudentProb {
2.   private int studentId  =  0;
3.   void setStudentID(int sid) {
4.      student_id = sid;
5.      System.out.println("Student ID has been set to " + sid);
6.   }
7.   public static void main(String args[]) {
8.       int i = 420;
9.       Object ob1;
10.      StudentProb st1 = new StudentProb();
11.       ob1 = st1;
12.       st1.setStudentID(i);
13.   }
14. }

Same problem. I thought line 11 would make st1 an Object, not a StudentProb anymore. How does the compiler still know where to find setStudentID?

    3.
1.    LectureHall lh = new LectureHall();
2.   Auditorium a1;
3.   Facilities f1;
4.
5.    f1 = lh;
6.    a1 = f1;

Facilities is an interface. The class ClassRoom implements Facilities, and Auditorium and LectureHall are subclasses of ClassRoom. Same question: I thought after line 5, both f1 and lh would be LectureHall. But f1 is still Facilities. So what exactly does casting do here?

Thanks all!

PS: code formatting doesn't work for me somehow. Feel free to edit.


Solution

  • At runtime, every object knows what its own class is, that is, the class that it was actually created as. It can be assigned to a variable of that class or any superclass. When you execute a function, you get the "version" of that function for the class that the object was created as, NOT for the class that the variable holding the object reference was declared as.

    That is, take your Car/Lexus example. If you write "Lexus mycar=new Lexus(); mycar.speedUp();", what executes is Lexus.speedUp, not Car.speedUp. Maybe that's obvious. But even if you write "Car mycar=new Lexus(); mycar.speedUp();" what executes is still Lexus.speedUp, because that's the class of the actual object. You can reassign an object to different variables of different classes all you like, the object still knows its "real" class.

    Basically, just think of it as each object having a hidden variable that holds its own class type, and this is what it uses to find the function to execute.

    At COMPILE time, the compiler doesn't know the class of any given object. Like if you write:

    void speed1(Car somecar)
    {
      somecar.speedUp(1);
    }
    

    The compiler doesn't know whether Car here is a Lexus or a Honda or what. It just knows it's a car, because it doesn't know where or how this function will be called. The actual type of car won't be known until run-time.

    An implication of this is that if you tried to write:

    void speed1(Object somecar)
    {
        somecar.speedUp(1);
    }
    

    The compiler would give an error on this. It has no way of knowing that Object is a Car, so it doesn't know that speedUp is a valid function.

    Even if you wrote:

    Object mycar=new Lexus();
    mycar.speedUp(1);
    

    You'd get an error. As a human being reading the code, you can easily see that mycar must be a Lexus and therefore a Car, but the compiler just sees that mycar is declared to be an Object, and Object does not have a speedUp function. (A smart enough compiler could, I suppose, figure out in this trivial example that mycar must be a Lexus, but the compiler can't work on what it might know sometimes or most of the time, it has to deal in absolutes.)

    Edit: Answer to question in comment.

    RE question 3: I see where you're getting confused here. You need to distinguish COMPILE TIME from RUNTIME.

    When you execute a function on an object, you get the "version" of that function specific to the "real" class of that object. Like if you write:

    Car car1=new Lexus();
    Car car2=new Chrysler(); // assuming you defined this, of course
    car1.speedUp(1);  // executes Lexus.speedUp
    car2.speedUp(2);  // executes Chrysler.speedUp
    

    But this is a RUNTIME thing. At COMPILE time, all the compiler knows is the type of the variable that holds the reference. This could be the same as the "real" class of the object or it could be any superclass up to Object. In both cases above, it's Car. As Car defines a speedUp function, a call to car1.speedUp is legal. But here's the kicker: At compile time, Java knows that Car has a speedUp function so calling speedUp against a Car object is legal. But it doesn't know or care WHICH speedUp function you'll get. That is, when you say car2.speedUp, Java knows that car2 is a Car because that's the declared type. It doesn't know -- remember we're saying at compile time, not at run time -- it doesn't know whether it's a Lexus or a Chyrsler, just that it's a Car. It's not until run time that it knows which type of car it is.