黑马程序员技术交流社区

标题: 这道题我想了很久都想不明白,谁能给一个详细的解释? [打印本页]

作者: 李敬卫    时间: 2013-1-21 19:43
标题: 这道题我想了很久都想不明白,谁能给一个详细的解释?
本帖最后由 张向辉 于 2013-1-23 09:34 编辑

class X {
X() {
System.out.print("X");
}
Y b = new Y();
}
class Y {
Y() {
System.out.print("Y");
}
}
public class Z extends X {
Y y = new Y();
Z() {
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}

我的结果是XYYZ可是错了,但是我想了很久就是想不明白为什么结果是YXYZ?现在我才发现继承还是没学明白啊,请彻底理解了继承的高手来详细解答一下这道题?要详细 要详细

作者: 唐晓    时间: 2013-1-21 20:13
本帖最后由 唐晓 于 2013-1-21 20:16 编辑

如果有继承,父类的信息就优先加载到了子类。在子类还没new的时候就已经加载了。所以Z方法里会优先加载X类。
X类的话就涉及到了类的加载顺序了。类首先会加载静态成员/代码块,然后再加载非静态成员/代码块,在接下来才加载调用构造方法。
所以X类会先调用new Y方法。
作者: 陈丽莉    时间: 2013-1-21 20:23
本帖最后由 陈丽莉 于 2013-1-21 20:29 编辑

要明确的几个知识点:
1、子类中所有构造函数默认都会访问父类中空参数构造函数。
2、初始化子类对象时,会先执行父类构造对象,再执行子类构造函数。
3、当父类既有创建对象,又有构造函数时,先执行创建对象操作。

下面是楼主的代码,格式稍微整理了一下,加了注释:
class X
{
        X()
        {
                System.out.print("X");                         //创建b对象并初始化Y的构造函数后,初始化父类构造函数,所以在输出Y后,输出X
        }
        Y b = new Y();                                             //加载时,创建对象的操作要先于构造函数执行
}

class Y
{
        Y()
        {
                System.out.print("Y");                         //创建对象后就会初始化这个类的构造函数,所以先输出Y
        }
}

public class Z extends X
{
        Y y = new Y();
        /* 输出Y和X后,子类中创建对象的操作要先于子类构造函数,所以创建了新的Y
          类对象,由于不是同一个b对象,Y类的构造函数又执行了一次,所以在输出Y和X
           之后,继而又输出了Y。 */

        Z()
        {
                //super();                                                默认隐式的访问父类空参数构造函数
                System.out.print("Z");                          // 完成了子类中创建对象的操作后,初始化子类构造函数,最后输出了Z
        }

        public static void main(String[] args)
        {
                new Z();
                /*程序的入口,若没有这个创建Z类匿名对象的操作,则父类
               不加载构造函数,什么也不会输出。创建本类对象的操作,先加载父类X */
        }
}

作者: 黄金龙    时间: 2013-1-21 20:28
  1. class X {
  2. X() {
  3. System.out.print("X");//这里第5步执行
  4. }
  5. Y b = new Y();//这里第6步执行
  6. }
  7. class Y {
  8. Y() {
  9. System.out.print("Y");//这里第3步执行>>>>>//这里第7步执行
  10. }
  11. }
  12. public class Z extends X {
  13. Y y = new Y();//这里第2步执行
  14. Z() {
  15. //super();这里隐藏了 //这里第4步执行
  16. System.out.print("Z");//这里第8步执行
  17. }
  18. public static void main(String[] args) {
  19. new Z();//这里第1步执行
  20. }
  21. }
  22. //所以就是YXYZ
复制代码

作者: 黑马刘杰    时间: 2013-1-21 20:30
你看这里的解释可不可以http://bbs.itheima.com/forum.php ... mp;page=1#pid212264
作者: 小学生0987    时间: 2013-1-21 20:36
这主要是运行顺序的问题,java虚拟机在实例化子类时,先加载父类的成员变量,执行父类的构造方法,接着加载子类的成员变量,运行子类的构造方法,所以结果是YXYZ
作者: 陈丽莉    时间: 2013-1-21 20:37
看了楼上的,觉得用数字标志确实很清晰,那我也标记一下,顺序有点出入:
class X {
X() {
System.out.print("X");             //--------这里第四步
}
Y b = new Y();                   //--------这里第二步
}
class Y {
Y() {
System.out.print("Y");                  //--------这里第三步   第六步
}
}
public class Z extends X {
Y y = new Y();                          //--------这里第五步
Z() {
System.out.print("Z");                  //--------这里第七步
}
public static void main(String[] args) {
new Z();                             //--------这里第一步
}
}

注:如果将X中的Y b = new Y();  注释掉,结果为XYZ,可见并不是楼上说的先执行了Z类中的Y y=new Y();语句。
作者: 王晓斌    时间: 2013-1-21 20:37
一,说下对象的初始化过程
1,new(Z)用到了z.class,因此会先找到z.class文件并加载到内存中~
2,找到该类中的静态代码块,如果有的话~给z.class类进行初始化
3,在堆内存中开辟空间,分配内存地址
4,在堆内存中建立对象的特有属性,并进行默认的初始化
5,对构造代码块初始化
6,构造函数初始化

如果有继承,那么默认先对父类进行初始化
这样一来,此题也就清楚了~~~

对于Y b=new(Y)b可以算是X的特有属性,当然先于构造函数执行了~~~
作者: 黑马-小龙    时间: 2013-1-21 20:55
结果yxyz:原因是构造函数随着对象的加载而加载,当该类继承了父类的时候,子类的构造函数在加载的过程中,虚拟机会首先去父类找父类的的构造函数,在执行子类的构造函数
作者: 李敬卫    时间: 2013-1-21 21:11
感谢大家,特别是2楼的这句话:创建对象的操作要先于类构造函数。我想知道,这句话有为什么没?如果有还请给解释一下,让我学的明白点?
作者: 刘军亭    时间: 2013-1-21 21:50
class X {
X() {
        //super();//默认的super()调用,这里是第3步,因为这里默认也有super(),因为所有的类都是Object的子类,所以去调用父类的空参构造函数,但是Object类的默认构造函数没有输出。所以一般不会察觉到他执行了。
System.out.print("X");//这里第7步执行
}
Y b = new Y();//第4步,执行。第一个Y是这里调用Y 的构造函数输出的
}
class Y {
Y() {
        //super();//第5步同上                                                    // 第9步,执行第八步时候同样调用Y构造函数
System.out.print("Y");//第6步,执行                   // 第10步,第八步执行再次调用Y的构造函数
}
}
public class Z extends X {
Y y = new Y();//第8步,执行    先对y变量默认初始化为Null,然后显示初始化,    此语句会调用Y的构造函数,在Y的构造函数执行完毕后会执行第11步
Z() {
//super(); //这里第2步执行,去给父类初始化。父类初始化完毕然后去给此类成员变量默认初始化,如:Y y = 默认为null;int类型默认初始化0,如果是类对象(类类型)初始化null;再后给成员变量进行显示初始化,如:Y y = new Y();
//执行完第七步时候super()执行完毕,父类初始化完毕。
System.out.print("Z");//第11步,最后执行构造初始化
}
public static void main(String[] args) {
new Z();//第1步,执行创建Z的对象,调用Z的构造函数。
}
}
//所以就是YXYZ

/*不知道你能不能明白,我觉得自己写的挺乱的,类的初始化过程用语言描述就是:
先调用本类的构造函数,但是构造函数默认第一行都由super();关键字去调用父类的构造函数,给父类进行初始化。然后才是成员变量的默认初始化。再后就是成员变量的显示初始化,最后才是构造初始化。

如:*/
class A {
       
        int x = 1;        //3.默认初始化为0
        int y;                //3.默认初始化为0
        String s;        //3.默认初始化为null
        //4.成员变量都默认初始化完毕后后,才会进行显示初始化,x = 1; y=0;因为y没有显示初始化的值。s=null;因为s也没有显示的赋值,都显示初始化完毕后才会执行构造函数的初始化。
        public A(){
                //super();//2.因为构造函数都有默认的super();会先给父类进行初始化。父类的初始化过程和子类相同。初始化完父类,会给本类的成员变量进行初始化,显示默认初始化,再是显示初始化。
                x = 5;        //5.构造初始化
                y =55;        //5.构造初始化
                s = "str";        //5.构造初始化
        }//至此初始化完毕
        public static void main(String[] args){
               
                A a = new A();//1.在new A对象的时候,会先调用A的构造函数
        }
       
}
作者: 唐长智    时间: 2013-1-22 00:24
本帖最后由 唐长智 于 2013-1-22 00:27 编辑
唐晓 发表于 2013-1-21 20:13
如果有继承,父类的信息就优先加载到了子类。在子类还没new的时候就已经加载了。所以Z方法里会优先加载X类 ...

2楼的亲解答的简单明了。子类初始化。
         1.首先加载父类的静态成员/代码块。
         2.加载子类的静态成员/代码块。
         3.调用父类的非静态成员/代码块。
         4.调用父类的构造函数。
         5.调用子类的非静态成员/代码块。
         6.调用子类的构造函数。

附上代码。如有错误,请大家指正。
结果是:X的静态代码块
             Z的静态代码块
             Y
             X的构造代码块
             X
             Z的构造代码块
             Y
             Z
  1. class X{
  2.         X(){
  3.                 System.out.println("X");
  4.         }
  5.         
  6.         Y b = new Y();
  7.         {
  8.                 System.out.println("X的构造代码块");
  9.         }
  10.         
  11.         static{
  12.                 System.out.println("X的静态代码块");
  13.         }
  14. }

  15. class Y{
  16.         Y(){
  17.         System.out.println("Y");
  18.         }
  19. }

  20. public class Z extends X{
  21.         static{
  22.                 System.out.println("Z的静态代码块");
  23.         }
  24.         
  25.         {
  26.                 System.out.println("Z的构造代码块");
  27.         }
  28.         
  29.         Z(){
  30.                 System.out.println("Z");
  31.         }
  32.         
  33.         Y y = new Y();
  34.         
  35.         public static void main(String[] args){
  36.                 new Z();
  37.         }
  38. }
复制代码

作者: 陈丽莉    时间: 2013-1-22 12:07
李敬卫 发表于 2013-1-21 21:11
感谢大家,特别是2楼的这句话:创建对象的操作要先于类构造函数。我想知道,这句话有为什么没?如果有还请 ...

      个人理解是,构造函数有可能会用到类中创建的对象的,所以加载的时候先加载对象。八楼的第4和第5点是说这个顺序的,那个答案应该是挺官方的吧~




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2