好的,noob问题。我正在为SCJP学习并得到3个关于对象引用错误的问题,这些问题似乎都表明了同样的误解。只想确认正确的见解应该是什么。对,这是问题:
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. }
我认为在第3行之后,c将是Car而不是Lexus,因此将调用Car.speedUp方法,而不是Lexus.speedUp方法。事实证明,后者被称为。
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. }
同样的问题。我认为第11行会使st1成为一个对象,而不再是一个StudentProb。编译器如何知道在哪里找到setStudentID?
1. LectureHall lh = new LectureHall(); 2. Auditorium a1; 3. Facilities f1; 4. 5. f1 = lh; 6. a1 = f1;
设施是一个界面。 ClassRoom类实现了Facilities,Auditorium和LectureHall是ClassRoom的子类。同样的问题:我想在第5行之后,f1和lh都是LectureHall。但是f1仍然是设施。那么铸造到底在做什么呢?
全部谢谢!
PS:代码格式化对我来说不起作用。随意编辑。
答案 0 :(得分:2)
对象始终是特定类的实例,您可以使用任何超类引用实例,但实例不会更改。我认为第二个剪辑最好地说明了这一点,你不能写ob1.setStudentID(i);
因为ob1是一个Object
变量,即使实际的类是StudentProb
在您的第3个片段中,第5行无效,因为Facilities是Auditorium的超类,因此您可以将Auditorium实例分配给Facilities变量,但不能反过来。
答案 1 :(得分:2)
在运行时,每个对象都知道它自己的类是什么,也就是它实际创建的类。它可以分配给该类的变量或任何超类。当你执行一个函数时,你得到该对象被创建的类的“版本”,而不是那个持有对象引用的变量被声明为的类。
也就是说,以你的Car / Lexus为例。如果你写“Lexus mycar = new Lexus(); mycar.speedUp();”,执行的是Lexus.speedUp,而不是Car.speedUp。也许这很明显。但即使你写“Car mycar = new Lexus(); mycar.speedUp();”执行的仍然是Lexus.speedUp,因为那是实际对象的类。您可以将对象重新分配给不同类的不同变量,对象仍然知道它的“真实”类。
基本上,只需将其视为具有隐藏变量的每个对象,该隐藏变量拥有自己的类类型,这就是它用于查找要执行的函数的内容。
在COMPILE时,编译器不知道任何给定对象的类。就像你写的那样:
void speed1(Car somecar)
{
somecar.speedUp(1);
}
编译器不知道这里的Car是雷克萨斯还是本田或者是什么。它只知道它是一辆汽车,因为它不知道这个函数的调用位置和方式。直到运行时才会知道实际的汽车类型。
这意味着如果你试着写:
void speed1(Object somecar)
{
somecar.speedUp(1);
}
编译器会给出错误。它无法知道Object是Car,因此它不知道speedUp是一个有效的函数。
即使你写了:
Object mycar=new Lexus();
mycar.speedUp(1);
你会收到一个错误。作为人类阅读代码,您可以很容易地看到mycar必须是雷克萨斯,因此是汽车,但编译器只是看到mycar被声明为Object,而Object没有speedUp函数。 (我认为,一个聪明的编译器可以在这个简单的例子中弄清楚mycar必须是雷克萨斯,但是编译器无法处理它有时或者大多数时候可能知道的事情,它必须处理绝对值。)
编辑:在评论中回答问题。
RE问题3:我看到你在这里感到困惑的地方。您需要将COMPILE TIME与RUNTIME区分开来。
当您在对象上执行函数时,您将获得该特定于该对象的“真实”类的该函数的“版本”。就像你写的那样:
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
但这是一个RUNTIME的事情。在COMPILE时,所有编译器都知道是保存引用的变量的类型。这可以与对象的“真实”类相同,也可以是对象的任何超类。在上面两种情况下,都是Car。由于Car定义了speedUp函数,因此调用car1.speedUp是合法的。但这里是踢球者:在编译时,Java知道Car有一个speedUp函数,所以对Car对象调用speedUp是合法的。但是它不知道或关心你将获得什么speedUp功能。也就是说,当你说car2.speedUp时,Java知道car2是Car,因为那是声明的类型。它不知道 - 记得我们在编译时说,而不是在运行时 - 它不知道它是雷克萨斯还是Chyrsler,只是它是一辆汽车。直到运行时它才知道它是哪种类型的汽车。
答案 2 :(得分:1)
对象始终保持不变。如果将其分配给其他类型的其他变量,则无法在不将其转换回原始类型的情况下访问特殊成员。
您只能访问正在使用的变量类型的成员和方法。但是,此变量引用的对象可以具有更专用的类型。该对象存储自己的类型,因此运行时能够识别其真实类型。
答案 3 :(得分:1)
这对我很有帮助 - 它可能对你没有帮助,但我会把它扔出去。
可视化将对象投射为在该对象上放置新衣服。它有不同的外观,但在你穿上它的任何衣服下面,它仍然是同一个物体。
例如,如果你取一个String并将它强制转换为Object,它就不会成为一个对象 - 但它会穿一件物体的衣服。你不能在一个对象上调用.length,因为该方法在“对象”的衣服中不存在,但是如果你调用.equals或.hashCode,你会得到与你在字符串上调用它们时相同的答案。他们把衣服叫做底层物品。
衣服只是隐藏了班级无法使用的方法。 Object o = new String();在新衣服下隐藏String中但不是Object的所有方法,使其看起来像一个Object。
您可以稍后将对象装扮成字符串。
你穿的衣服与物品的操作无关,只与外界交互/观察物体的方式有关。
这可能是类比较远,但也要注意,如果你的“衣服”有你方体中不存在的方法,试穿这些衣服没有任何意义,所以你可以不做:
String s=new Object();
因为一个对象无法填写String的套装,所以String的套装上有“长度”和“附加”的洞以及对象根本无法填充的许多其他方法 - 就像没有人试图戴手套的人一样。 / p>
答案 4 :(得分:0)
还要记住一件事。子类引用不能包含超类对象。class A{} class B extends A{}
您可以在此处创建A a=new A(); A a=new B();B b=new B()
但是您无法创建B b=new A()