问题
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:
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.
回答2:
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.
回答3:
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.
回答4:
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.
回答5:
Keep one more thing in mind. A subclass reference cannot hold superclass object.class A{} class B extends A{}
Here you can create A a=new A(); A a=new B();B b=new B()
But you can not create B b=new A()
来源:https://stackoverflow.com/questions/3620227/what-class-does-the-target-object-take-on-after-casting