views:

78

answers:

4

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.

+1  A: 

The object always stays the same. If you assign it to a different variable of an other type, you can't access the special members without casting it back to its original type.

You can only access members and methods of the type of the variable you are using. But the object referenced by this variable can have a more specialized type. The object stores its own type, so the runtime is able to identify its real type.

tangens
+2  A: 

An object is always an instance of a particular class, you can refer to an instance using any of the super classes, but the instance doesn't change. I think the 2nd snipped illustrates this best, you couldn't write ob1.setStudentID(i); because ob1 is an Object variable, even though the actual class is StudentProb

In your 3rd snippet, the 5th line isn't valid, as Facilities is a superclass of Auditorium, so you could assign an Auditorium instance to a Facilities variable, but not the other way round.

Jon Freedman
So is this right: ob1 contains a reference to st1, so ob1 points to a StudentProb, but that doesn't make ob1 a StudentProb - ob1 is still just an Object? What is the effect of conversion then - does it just change the reference in ob1 to point to st1? What if I'd used explicit casting, e.g. ob1 = (StudentProb) st1? Would ob1 actually BE a StudentProb then, and would I be able to call ob1.setStudentID?Thanks!
Anita
ob1 is always a `StudentProb` instance, but you can only iteract with it as an `Object` (e.g. call `toString()`, `hashCode()` etc.) You don't change the class of the instance, only how you reference it as a variable.
Jon Freedman
So once ob1 is declared as an Object, there's simply no way to make it so that ob1.setStudentID runs?
Anita
The only thing you can do is `((StudentProb)ob1).setStudentId()` - but this sort of code could throw a `ClassCastException` if ob1 isn't a `StudentProb` or subclass.
Jon Freedman
+1  A: 

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.

Jay
So the "real type" you mentioned is the class that the object was FIRST declared as? And that's the class that'll always be used no matter how often you reassign the object to other objects of other types?
Anita
Also, help clarify me on this: you said:"if you write "Car mycar=new Lexus(); mycar.speedUp();" what executes is still Lexus.speedUp"So the class that'll always stay with mycar is what's after the "new" keyword?
Anita
But then you said:Object mycar=new Lexus();mycar.speedUp(1);"the compiler just sees that mycar is declared to be an Object, and Object does not have a speedUp function"mycar is here declared as a Lexus just like the previous quote, but now the compiler (or JVM?) doesn't know that's a Lexus. The only difference between the two quotes that I can see is in the first case, it's Car mycar, and in the 2nd, it's Object mycar. How does that make a difference? Thanks!PS: Sorry, had to break up comments because of character limit.
Anita
To your first and second questions: What determines the "real" class of the object is what comes after the "new". (There are ways to create an object without using "new" that I won't get into here, except to say that they also have a means of specifying the class of the object created.) The variable you use to hold the reference doesn't matter. Whether you say "Lexus mycar=new Lexus()", "Car mycar=new Lexus()", or "Object mycar=new Lexus()", the object created is a Lexus and it knows it's a Lexus. If you said "Car mycar=new Car()", then it would just be a generic Car.
Jay
Question 3: I guess this needs more explanation. See my edit above.
Jay
+1  A: 

This helps me--it may not help you but I'll throw it out there.

Visualize casting an object as putting new clothes on that object. It has a different appearance but underneath whatever clothes you put on it, it's still the same object.

For instance if you take a String and cast it to Object, it doesn't become an object--but it wears an object's clothes. You can't call .length on an object because that method doesn't exist in "Object's" clothes, but if you call .equals or .hashCode you will get the same exact answers as you would if you called them on the string because they call through the clothes to the underlying object.

The clothes simply hide methods that aren't available to the class. Object o=new String(); hides all the methods that are in String but not Object under the new clothes to make it look like an Object.

You can later dress your Object up as a String again.

The clothes you wear aren't relevant to the object's operation, only how the outside world interacts with/views the object.

This may be taking the analogy a little far, but also note, if your "Clothes" have methods that don't exist in your object, it doesn't make any sense to try to wear those clothes, so you can't do:

String s=new Object();

Because an Object can't fill out String's suit, String's suit has holes for "length" and "append" and many other methods that Object simply can't fill--like someone with no hands trying to wear gloves.

Bill K