下面这个程序运行的结果让人不理解。
运行结果为:
Parent is creating..//不理解
Son is eat: 0//不理解
Son is creating..
Son is eat: 1
主要是前2句不理解。请高手详细说明之。谢谢/**
 *Parent.java
 */
public class Parent {
private int eatID=0;
public Parent(){
System.out.println("Parent is creating..");
eat();
}
public void eat(){
System.out.println("Parent is eat: "+eatID);
}}/**
 *Son.java
 */
public class Son extends Parent {
private int eatID=1;
public Son(){
System.out.println("Son is creating..");
eat();
}
public void eat(){
System.out.println("Son is eat: "+eatID);
}
@SuppressWarnings(value={"unused"})
public static void main(String[] args){
Parent p=new Son();
}}

解决方案 »

  1.   

    1) 执行Son的构造方法之前,自动执行了Parent构造方法
    2) 在Parent的构造方法中调用eat()时,由于eat()已被Son重写,因此实际调用的是Son.eat()
      

  2.   


    如你所说那么eatID为什么第一次打印0呢?~
      

  3.   

    正如楼上所说,那eatID为什么第一次打印的是0呢?
      

  4.   


    因为存在两个eatID,一个是Parent的等于0,一个是Son的等于1
    调用的虽然是Son.eat(),但该方法中取到的eatID却是Parent的eatID
      

  5.   

    用debug跟踪了一下程序的执行过程!
    证明4L的说话是正确的
      

  6.   

    对第2行的输出有点不大清楚,是不是因为虽然子类重载了父类的eat()方法,
    但在子类自动调用父类的构造方法并在调用eat()方法时,此时所处在父类的
    “环境”中,故使用了父类的eatID,不知道是不是这样,期待大牛赐教。
      

  7.   

    这样写结果一样
     public static void main(String[] args){
            Son p=new Son();
        }
      

  8.   

    Classfile /Son.class
    Last modified 2011-6-26; size 373 bytes
    MD5 checksum f425bcb4f6d0dbd5e8450c356525a0eb
    Compiled from "Son.java"
    public class Son extends Parent
      SourceFile: "Son.java"
      minor version: 0
      major version: 50
      flags: ACC_PUBLIC, ACC_SUPER  Constant pool:
    const #1 = Method #6.#18; //  Parent."<init>":()V
    const #2 = Field #4.#19; //  Son.eatID:I
    const #3 = Method #4.#20; //  Son.eat:()V
    const #4 = class #21; //  Son
    const #5 = Method #4.#18; //  Son."<init>":()V
    const #6 = class #22; //  Parent
    const #7 = Asciz eatID;
    const #8 = Asciz I;
    const #9 = Asciz <init>;
    const #10 = Asciz ()V;
    const #11 = Asciz Code;
    const #12 = Asciz LineNumberTable;
    const #13 = Asciz eat;
    const #14 = Asciz main;
    const #15 = Asciz ([Ljava/lang/String;)V;
    const #16 = Asciz SourceFile;
    const #17 = Asciz Son.java;
    const #18 = NameAndType #9:#10; //  "<init>":()V
    const #19 = NameAndType #7:#8; //  eatID:I
    const #20 = NameAndType #13:#10; //  eat:()V
    const #21 = Asciz Son;
    const #22 = Asciz Parent;{
    public Son();
      flags: ACC_PUBLIC  Code:
       Stack=2, Locals=1, Args_size=1
       0: aload_0
       1: invokespecial #1; //Method Parent."<init>":()V
       4: aload_0
       5: iconst_1
       6: putfield #2; //Field eatID:I
       9: aload_0
       10: invokevirtual #3; //Method eat:()V
       13: return
      LineNumberTable: 
       line 3: 0
       line 2: 4
       line 5: 9
       line 6: 13
    public void eat();
      flags: ACC_PUBLIC  Code:
       Stack=1, Locals=2, Args_size=1
       0: aload_0
       1: getfield #2; //Field eatID:I
       4: istore_1
       5: return
      LineNumberTable: 
       line 9: 0
       line 10: 5
    public static void main(java.lang.String[]);
      flags: ACC_PUBLIC, ACC_STATIC  Code:
       Stack=2, Locals=2, Args_size=1
       0: new #4; //class Son
       3: dup
       4: invokespecial #5; //Method "<init>":()V
       7: astore_1
       8: return
      LineNumberTable: 
       line 13: 0
       line 14: 8
    }Classfile /Parent.class
    Last modified 2011-6-26; size 298 bytes
    MD5 checksum 3a9133d4b82f26cac2da21e973f55e2e
    Compiled from "Son.java"
    class Parent extends java.lang.Object
      SourceFile: "Son.java"
      minor version: 0
      major version: 50
      flags: ACC_SUPER  Constant pool:
    const #1 = Method #5.#15; //  java/lang/Object."<init>":()V
    const #2 = Field #4.#16; //  Parent.eatID:I
    const #3 = Method #4.#17; //  Parent.eat:()V
    const #4 = class #18; //  Parent
    const #5 = class #19; //  java/lang/Object
    const #6 = Asciz eatID;
    const #7 = Asciz I;
    const #8 = Asciz <init>;
    const #9 = Asciz ()V;
    const #10 = Asciz Code;
    const #11 = Asciz LineNumberTable;
    const #12 = Asciz eat;
    const #13 = Asciz SourceFile;
    const #14 = Asciz Son.java;
    const #15 = NameAndType #8:#9; //  "<init>":()V
    const #16 = NameAndType #6:#7; //  eatID:I
    const #17 = NameAndType #12:#9; //  eat:()V
    const #18 = Asciz Parent;
    const #19 = Asciz java/lang/Object;{
    public Parent();
      flags: ACC_PUBLIC  Code:
       Stack=2, Locals=1, Args_size=1
       0: aload_0
       1: invokespecial #1; //Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_0
       6: putfield #2; //Field eatID:I
       9: aload_0
       10: invokevirtual #3; //Method eat:()V
       13: return
      LineNumberTable: 
       line 20: 0
       line 19: 4
       line 22: 9
       line 23: 13
    public void eat();
      flags: ACC_PUBLIC  Code:
       Stack=1, Locals=2, Args_size=1
       0: aload_0
       1: getfield #2; //Field eatID:I
       4: istore_1
       5: return
      LineNumberTable: 
       line 26: 0
       line 27: 5
    }
    用javap看字节码可以看到调用Son()时首先进入父类Parent()的构造函数中
    然后将parent的成员eatID压入
    这个时候invokevirtual即调用重写的方法,但是子类的eatID还没有压入,所以第一次输出是0吧 = =对字节码不太了解,也是连蒙带猜的 ,这部分还得请教ticmy大神
      

  9.   

    这个真是自己坑自己,你把父类的ID数值改成其他看看,输出一样是0.不要被父类的数值迷惑了,跟父类一点关系都没(除了在过程中会调用父类的构造外)。自己用debug看下构造过程就清楚了,开始ID值没还赋值,默认为0.
      

  10.   

    public class Parent {
        private int eatTag=0;
        public Parent(){
            System.out.println("Parent is creating..");
            eat();
        }
        public void eat(){
            System.out.println("Parent is eat: "+eatTag);
        }}
    public class Son extends Parent {
        private int eatID=1;
        public Son(){
            System.out.println("Son is creating..");
            eat();
        }
        public void eat(){
            System.out.println("Son is eat: "+eatID);
        }
        @SuppressWarnings(value={"unused"})
        public static void main(String[] args){
            Son p=new Son();
        }}
    /*
    Output:
    Parent is creating..
    Son is eat: 0
    Son is creating..
    Son is eat: 1
    */
      

  11.   

    下图展示了在调用栈上的构造函数是如何起作用的:
    4.Object()
    3.Parent()调用super()
    2.Son()调用super()
    1.main()调用new Son()
      

  12.   

    先贴代码,然后再解释
    /**
     *Parent.java
     */
    class Parent {
        private int eatID=10; //修改这里来看看有无变化?
        public Parent(){
            System.out.println("Parent is creating..");
            eat();
        }
        public void eat(){
            System.out.println("Parent is eat: "+eatID);
        }}
    /**
     *Son.java
     */
    public class Son extends Parent {
        {System.out.println("i am ok.");}
        private int eatID=1;
        public Son(){
            System.out.println("Son is creating..");
            eat();
        }
        public void eat(){
            System.out.println("Son is eat: "+eatID);
        }
        @SuppressWarnings(value={"unused"})
        public static void main(String[] args){
            Parent p=new Son();
        }}
      

  13.   


    哦可能结果跟那个无关虽然LZ的题意是Parent p = new Son();
      

  14.   

    原来LS已经有人解释了。
    那我就大概说一下好了,LZ的代码打印出来的0不是父类的,是子类的
    先看一个类对象被创建的过程
    class A {
        {System.out.println("first");}    public A() {
            System.out.println("second");
        }
    }public class B extends A {
        {System.out.println("third");}    public B() {
            System.out.println("forth");
        }    public static void main(String[] args) {
            new B();
        }
    }打印
    first
    second
    third
    forth
    从这段代码我们可以看到,{}的初始化块会在构造函数之前调用,子类初始化之前会先构造父类对象private int eatID=1;这条语句相当于两条语句,即 private int eaiID;声明语句 和 eatID = 0; 赋值语句
    类初始化的时候,先把声明语句的变量加载到栈的数据区,并初始化变量,然后再初始化对象,初始化过程按照上面例子给出的过程,所以,先执行父类的private int eaiID;声明,把eatID加载到父类的数据区,然后执行子类的private int eatID;声明,把eatID加载到子类的数据区,然后执行父类的eatID=10赋值语句;然后调用父类的构造函数,然后父类的构造函数调用被子类重写的eat方法,eat方法打印子类的eatID,但是因为子类的eatID还没有执行赋值语句,所以是初始值0,然后父类构造函数结束,执行子类的eatID=1赋值语句,然后调用子类的构造函数,然后子类的构造函数调用eat方法打印eatID为1好了,过程分析了,再看红色那句,为什么是调用子类的而不是父类的一个类,被编译成class文件的时候,变量总是被编译为一个相对地址而写到class文件,那么,Parent的eatID被编译为一个相对地址写在Parent.class文件,而Son的eatID也被编译为一个相对地址写到Son.class文件里,这两个相对地址是不一样的,再看子类的eat方法,当它被编译为指令写到Son.class文件的时候,因为它用到eatID,所以编译器会查看当前的class中是否存在一个与eatID对应的地址,如果存在,则使用,如果不存在,继续往父类展开查找,直到找到一个eatID为止,然后把它的相对地址硬编码地写在代码指令中。很显然,这里在子类的class文件中就找到了eatID,所以编译器就直接使用子类class的eatID
      

  15.   

    变量是父类还是子类关系不大吧,反正方法里都是使用this指针调用各种方法和变量,怎样都是指向子类对象的。
      

  16.   

    很有内涵的题目
    有一点延伸思考
    如果两个eat方法都换成private,是什么结果
    都换成protected,又是什么结果上机前先推算一下看对不对
      

  17.   

    java动态绑定机制只对方法有效,对属性无效。
    ---------------------------------------------
    Parent p=new Son();这一句造成了多态机制的产生;
    分析一下:
    第一个输出结果是创建son实例先调用Parent的构造器;顺序执行输出Parent is creating..
    第二个输出结果是顺序调用eat()方法,首先子类的重写方法覆盖了父类的方法,多态发生,输出Son is eat:,p这个引用是不会访问到私有的子类属性(多态机制),更不会访问到本类私用属性(访问权限),按加载的顺序,子类的属性初始化类变量自动初始化,int型初始化即为0;所以此时输出结果为0;
    第三个不说了;
    第四个输出结果是重写的方法被调用,并且子类实例化(加载顺序),属性初始化并且赋值,输出结果为1。
    ---------------------------------------------
    学java只有一年 轻喷
      

  18.   

    楼主您好!
    在上面的代码中你把Parent中的privater int eatID=0;
    改为:eatID=2;(这个可以为任意不为0的数)
    你会发现程序的执行结果是一样的
    这就说明Son is eat: 0//不理解
    这里所产生的0与父类的属性eatID没有关系,你在运行上面的代码时,在Son类中的属性eatID前面加上一个关键字static  
    你会发现打印的变成了了
    Son is eat:1以下我会做出解释:
    但main()执行时,会首先执行Parent中的构造方法,但是由于eat()方法在子类Son中已被重写,故当执行父类中构造方法时会执行重写的子类eat()方法,
    private int eatID=1;
    这条语句相当于两条语句,即 private int eaiID;声明语句 和 eatID = 0; 赋值语句类初始化的时候,先把声明语句的变量加载到栈的数据区,并初始化变量,然后再初始化对象,初始化过程按照上面例子给出的过程,所以,先执行父类的private int eaiID;声明,把eatID加载到父类的数据区,然后执行子类的private int eatID;声明,把eatID加载到子类的数据区,然后执行父类的eatID=10赋值语句;然后调用父类的构造函数,然后父类的构造函数调用被子类重写的eat方法,eat方法打印子类的eatID,但是因为子类的eatID还没有执行赋值语句,所以是初始值0,然后父类构造函数结束,执行子类的eatID=1赋值语句,然后调用子类的构造函数,然后子类的构造函数调用eat方法打印eatID为1。
      

  19.   


    这里的理解有点问题如
    class A {
        public i = 10;
    }public class B extends A {
        public i = 5;    public static void main(String[] args) {
            A p = new B();
            System.out.println(p.i); //这里访问A的属性
            System.out.println(((B)p).i); //这里可以访问B的属性        //同样的
            B b = new B();
           System.out.println(b.i); //这里访问B的属性
            System.out.println(((A)b).i); //这里可以访问A的属性
        }
    }LZ的这个问题,不是多态机制的属性访问的问题,而是eat方法编译时,属性被如何编译的问题。
    方法之所以多态,是因为方法是动态绑定,也就是方法执行时,通过虚拟表去获得实际的方法入口地址,而属性之所以不能多态,是因为属性是静态绑定,也就是编译时,属性就被编译为一个相对地址(在java里是通过变量管理表的索引管理的,也就是说把属性编译为一个索引号),方法对属性的操作就是对这个相对地址的操作。
      

  20.   

    p作为一个引用,如果没有引用类型没发生变化(强制转化)的话,son实例属性是不可能被p访问到的
      

  21.   

    看了上面的回复,真的学习了。
    我运行了一下代码,代码的运行顺序是看明白了:
    顺序:
    但理解不知道是不是还有偏差:
    1、首先eat()方法确实被重载了,所以两次调用的都是子类的eat();
    2、第一次调用子类的eat()方法,由于是父类构造函数调用的,所以子类变量还没有赋值,但进行了初始化。
    3、第二次调用子类的eat()方法,由于是调用的子类自身构造函数,之前子类变量已经赋值了。
    4、加载顺序25楼发的代码已经说的很清楚了。不知道自己理解的是否有问题,请大神们指点。有两个地方原理不太明白:
    1、在运行构造函数前会初始化所有变量(未赋值)。--这个是否有什么条件,还是所有的都这样?
    2、在进入该类构造函数前,才会给变量赋值。--这个是怎么回事,不会根据是否调用构造函数来赋值吧?
      

  22.   

    访问属性除了直接访问的方式,还可以通过方法来访问,如果按你这么说,调用p.eat方法访问的不是son的属性,而是patent的属性了?
      

  23.   


    1 没有任何条件,所有都是这样。参看10L的反编译,这个步骤由new指令来完成。然后由invokespecial来完成构造函数的调用。2 这个就看你的定义语句是怎么写的,上面说了,private int eatID = 1;相当于两条语句,即private int eatID; {eatID=0;} ,{}这个叫做初始化块,在构造函数之前调用,如果前面加上static,即static{},这个叫静态初始化块,在类加载的时候调用。
      

  24.   

    一开始进来的时候的小看这个程序了
    看了大家的解释后,有种豁然开朗的感觉
    原来一个int eatID=1;有这么多门道,看来我学的还不深
    以后得多到论坛学习
      

  25.   

    1、你必须在构造器的第一行放置super构造器,否则编译器会自动地放一个空参数的super构造器,层层递推,调用成一个递归构造链,最后的结果是父类的构造器(可能有多级父类构造器)始终在子类的构造器之前执行,递归的调用父类构造器。
      这就会出现Parent is creating..
    2、你new的是Son,此时Parent的方法eat会被覆盖,且此时Son的private int eatID仅进行了声明(默认值0),而其赋值的son非静态语句块eatID=2,会在创建类时,在构造器执行前执行,所以还没执行eatID=2。
      这就会出现Son is eat: 0
      

  26.   

    你好,看了你的答复,我想问下,其实多态是不是可以这样理解:首先有继承,方法重写,还有父类指向子类然后因为虚拟机首先是先检查当前class文件的方法和属性,所以如果由以上的条件,就能实现多态??= =好像又有点不对,晕啊晕
      

  27.   

    原因很简单啊,因为构造房的执行过程是这样的
    1) 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零;
    2) 调用基类的构造方法;
    3) 按照声明的顺序调用成员的初始化方法;
    4) 调用导出类的构造器主体。
    在第二部的时候,调用了基类的构造方法,里面有eat()方法,是子类的eat();但是这个时候还没有给eatID赋值(因为那是第三步做的),所以就输出了默认值0(这个0跟父类的初始值无关,甚至父类中没有这个字段,输出还是0)。
    就是这样,所以不要在构造函数中调用可被重写的方法,容易造成隐含的错误。