1

好的,菜鸟问题。我正在为 SCJP 学习,并得到了 3 个关于对象引用转换错误的问题,这些问题似乎都指向了同样的误解。只是想确认正确的洞察力应该是什么。对了,问题如下:

    1.
1. 类 CodeWalkFour {
2. public static void main(String[] args){
3. 汽车 c = new Lexus();
4. System.out.print(c.speedUp(30) + " ");
5. 雷克萨斯 l = 新雷克萨斯 ();
6. System.out.print(l.speedUp(30, 40, 50));
7.}
8.}
9. 类车{
10.私有int i=0;
11. int speedUp(int x){
12. 返回 i;
13.}
14.}
15.类雷克萨斯扩展汽车{
16.私有int j = 1;
17.私有int k = 2;
18. int speedUp(int y){
19. 返回 j;
20.}
21. int speedUp(int...z){
22.返回k;
23.}
24.}

我以为在第 3 行之后,c 会是 Car,而不是 Lexus,所以会调用 Car.speedUp 方法,而不是 Lexus.speedUp 方法。原来是后者被调用。

    2.
1. 类 StudentProb {
2. private int studentId = 0;
3. 无效 setStudentID(int sid) {
4. student_id = sid;
5. System.out.println("学生ID已设置为" + sid);
6.}
7. public static void main(String args[]) {
8.int i = 420;
9.对象ob1;
10. 学生概率 st1 = 新学生概率();
11. ob1 = st1;
12. st1.setStudentID(i);
13.}
14.}

同样的问题。我认为第 11 行会使 st1 成为对象,而不是 StudentProb。编译器如何仍然知道在哪里可以找到 setStudentID?

    3.
1. LectureHall lh = new LectureHall();
2. 礼堂a1;
3.设施f1;
4.
5. f1 = lh;
6. a1 = f1;

设施是一个接口。ClassRoom 类实现了Facilities,Auditorium 和LectureHall 是ClassRoom 的子类。同样的问题:我认为在第 5 行之后,f1 和 lh 都将是 LectureHall。但 f1 仍然是设施。那么铸造在这里到底做了什么?

谢谢大家!

PS:代码格式对我不起作用。随意编辑。

4

5 回答 5

2

对象始终是特定类的实例,您可以使用任何超类引用实例,但实例不会更改。我认为第二个片段最好地说明了这一点,你不能写ob1.setStudentID(i);因为 ob1 是一个Object变量,即使实际的类是StudentProb

在您的第三个片段中,第 5 行无效,因为设施是 Auditorium 的超类,因此您可以将 Auditorium 实例分配给设施变量,但反之则不行。

于 2010-09-01T16:42:33.997 回答
2

在运行时,每个对象都知道它自己的类是什么,即它实际创建的类。它可以分配给该类或任何超类的变量。当您执行一个函数时,您将获得创建对象的类的该函数的“版本”,而不是声明持有对象引用的变量的类。

也就是说,以您的汽车/雷克萨斯为例。如果你写“Lexus mycar=new Lexus(); mycar.speedUp();”,执行的是 Lexus.speedUp,而不是 Car.speedUp。也许这很明显。但即使你写“Car mycar=new Lexus(); mycar.speedUp();” 执行的仍然是 Lexus.speedUp,因为那是实际对象的类。您可以随意将对象重新分配给不同类的不同变量,该对象仍然知道它的“真实”类。

基本上,只需将其视为每个对象都有一个隐藏变量,该变量包含自己的类类型,这就是它用来查找要执行的函数的方法。

在编译时,编译器不知道任何给定对象的类。就像你写:

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

编译器不知道这里的 Car 是 Lexus 还是 Honda 或什么。它只知道它是一辆车,因为它不知道该函数将在何处或如何被调用。汽车的实际类型要到运行时才能知道。

这意味着如果您尝试编写:

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

编译器会对此给出错误。它无法知道 Object 是 Car,因此它不知道 speedUp 是一个有效的函数。

即使你写了:

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

你会得到一个错误。作为阅读代码的人,您可以很容易地看到 mycar 必须是 Lexus,因此是 Car,但编译器只看到 mycar 被声明为 Object,而 Object 没有 speedUp 函数。(我想,一个足够聪明的编译器可以在这个简单的例子中找出 mycar 必须是雷克萨斯,但编译器有时或大部分时间无法处理它可能知道的东西,它必须处理绝对值。)

编辑:回答评论中的问题。

RE问题3:我知道您在这里感到困惑。您需要区分编译时间和运行时间。

当你在一个对象上执行一个函数时,你会得到那个函数的“版本”,该函数特定于那个对象的“真实”类。就像你写:

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

但这是一个运行时的事情。在编译时,编译器只知道保存引用的变量的类型。这可能与对象的“真实”类相同,也可能是直到 Object 的任何超类。在上述两种情况下,它都是 Car。由于 Car 定义了 speedUp 函数,所以对 car1.speedUp 的调用是合法的。但问题是:在编译时,Java 知道 Car 有一个 speedUp 函数,因此针对 Car 对象调用 speedUp 是合法的。但它不知道也不关心你会得到哪个加速功能。也就是说,当您说 car2.speedUp 时,Java 知道 car2 是 Car,因为这是声明的类型。它不知道——记住我们说的是在编译时,而不是在运行时——它不知道是 Lexus 还是 Chyrsler,只是知道它是 Car。它'

于 2010-09-01T17:40:25.120 回答
1

对象始终保持不变。如果将其分配给其他类型的不同变量,则无法访问特殊成员,除非将其转换回其原始类型。

您只能访问您正在使用的变量类型的成员和方法。但是这个变量引用的对象可以有更特殊的类型。对象存储自己的类型,因此运行时能够识别其真实类型。

于 2010-09-01T16:38:02.503 回答
1

这对我有帮助——它可能对你没有帮助,但我会把它扔在那里。

将铸造一个物体想象为在该物体上穿上新衣服。它有不同的外观,但无论你穿什么衣服,它仍然是同一个物体。

例如,如果您将 String 转换为 Object,则它不会成为对象——但它会穿上对象的衣服。您不能在对象上调用 .length ,因为“对象”的衣服中不存在该方法,但是如果您调用 .equals 或 .hashCode 您将得到与在字符串上调用它们时相同的确切答案,因为他们通过衣服呼唤底层物体。

衣服只是隐藏了类不可用的方法。对象 o=new String(); 将所有在String中但不是Object的方法隐藏在新衣服下,使其看起来像一个Object。

稍后您可以再次将您的对象装扮成字符串。

您穿的衣服与对象的操作无关,仅与外界如何与对象交互/查看对象有关。

这个类比可能有点远,但还要注意,如果你的“衣服”有你的对象中不存在的方法,那么尝试穿那些衣服没有任何意义,所以你不能这样做:

String s=new Object();

因为 Object 无法填充 String 的套装,所以 String 的套装有用于“长度”和“追加”的孔以及 Object 根本无法填充的许多其他方法——就像没有手试图戴手套的人一样。

于 2010-09-01T17:44:23.590 回答
0

记住一件事。子类引用不能包含超类对象。class A{} class B extends A{} 在这里你可以创建A a=new A(); A a=new B();B b=new B()但你不能创建B b=new A()

于 2017-02-12T10:32:44.487 回答