这个论坛里有很多关于继承的帖子,今天看别人帖子的时候,想做一个汇总贴,结果,测试的时候问题出来了。
看 第一种情况package com.jwh.cn;public class ExtendsRun extends HelloWorld{
public int i=1;
public ExtendsRun() {
super();
System.out.println("hello world");
System.out.println(this+"2");
}

public static void main(String[] args) {
ExtendsRun er = new ExtendsRun();
}
@Override
public String toString() {
// return new Integer(i).toString();
return super.toString();
}
}class HelloWorld{
public int i=2;
public HelloWorld() {
System.out.println("Hello World");
System.out.println(this+"1");
}

@Override
public String toString() {
return new Integer(i).toString();
}

public int getI() {
return i;
}

public void setI(int i) {
this.i = i;
}
}
输出结果为
Hello World
21
hello world
22这很正常,但是请看第二种代码,和第一种几乎一样,唯一不同的就是子类的toString方法package com.jwh.cn;public class ExtendsRun extends HelloWorld{
public int i=1;
public ExtendsRun() {
super();
System.out.println("hello world");
System.out.println(this+"2");
}

public static void main(String[] args) {
ExtendsRun er = new ExtendsRun();
}
@Override
public String toString() {
return new Integer(i).toString();
// return super.toString();
}
}class HelloWorld{
public int i=2;
public HelloWorld() {
System.out.println("Hello World");
System.out.println(this+"1");
}

@Override
public String toString() {
return new Integer(i).toString();
}

public int getI() {
return i;
}

public void setI(int i) {
this.i = i;
}
}这种情况下输出的居然是
Hello World
01
hello world
12我不知道这个零是怎么来的。回答问题的请看清楚全文,代码不是很长,也很简单。请比较回答谢谢。。

解决方案 »

  1.   

    因为第一个中子类调用的是父类的toString  所以问题不存在
    第二种的子类没有调用父类的toString,因为创建子类对象的时候先初始化父类这个时候并没有显示的给子类的i进行初始化。所以默认为0.所以出来的是01
    LZ可以看看关于java初始化顺序后在回来看这个代码。
      

  2.   

    创建子类对象的时候先初始化父类这个时候并没有显示的给子类的i进行初始化。1情况. 调用父类构造函数的时候,父类的i已经初始化完毕了,说明父类的i是在父类构造函数被调用之前就初始化好的。 这没问题吧。如果说,调用之前没被初始化,那么1情况的结果应该是也是 01 02
    2. 既然调用一个函数构造函数时,其属性已初始化完毕,何以情况2的结果变了呢。
      

  3.   

    public String toString() {
            return new Integer(i).toString();
    //        return super.toString();
        }
    21
    22
    调用toString()方法时打印的是父类中的i,调用父类构造方法之前i已经赋值,i = 2
    所以打印出来的是 21 和22
    public String toString() {
            return new Integer(i).toString();
    //        return super.toString();
    }
    01 
    12 调用toString()方法打印的是子类中的i
    0是因为调用父类构造方法的时候子类中的i还没有赋值,是默认的值0
    调用子类构造方法的时候,子类的i已经赋值为1,所以打印1
      

  4.   

    1. 父类对象内存分配
    2. 父类字段默认初始化
    3. 子类对象内存分配
    4. 子类字段默认初始化
    5. 进入父类构造-> 父类成员显示初始化(定义字段时设置的字段初值)
                 -> 执行父类构造体(此时可以调用抽象方法,所有父类字段已初始
                 化完毕,但子类字段尚未进行显示初始化)
    6. 进入子类构造-> 子类成员显示初始化(定义字段时设置的字段初值)
               -> 执行子类构造体(此时所有字段已经显示初始化) 你的第二种情况就是走到了第五步此时子类字段尚未进行显示初始化,所以输出为0
    而接下来到了第六步,所以就成了1
      

  5.   

    你没有搞明白java的初始化顺序,在执行父类构造的时候子类并没有显示的给子类的成员变量进行初始化。
      

  6.   

    关于这个问题,我想提请注意一点是:
    即使父类的toString方法不存在,其结果也是01。
    这就驳斥了一个观点,即认为“父类的i还没有初始化,所其值为0”。
    事实是,父类没有显式的toString方法,就不会调用到i,
    不会调用到i的话,就说明这个0和i没有任何关系。至于为什么父类没有显式的toString却输出为零,
    我也不知道为什么。
      

  7.   

    先将成员变量执行默认初始化,例如int型的变量会赋值0
    然后调用构造方法
    然后给成员变量执行显式的初始化,例如int i=2等这就是显式初始化lz需要注意这是个递归形式的调用顺序,也就是说子类构造方法调用父类构造方法的时候,它已经执行了父类的默认初始化,只有父类的构造方法执行完毕,父类的显式初始化才会进行,以此类推所以,父类打印“System.out.println(this+"1");”的时候,它的显式初始化还没有做,故这时候打印出来的i是0,即默认初始化的值
    在第一种情况下之所以没有出现问题,是因为你调用了super.toString方法,从而访问父类的i,而这时候父类的i已被赋值为2,如果你调用自己的i,也会出现0.
      

  8.   

    理解这个问题的关键在于理解动态方法分配(dynamic method dispatch),简单来说就是,根据实际对象,调用相应的方法首先看第一段代码执行过程://main方法中只有一个语句:
    ExtendsRun er = new ExtendsRun();
    //看到new ExtendsRun(),自然去看子类ExtendsRun的构造器:
        public ExtendsRun() {
            super();
            System.out.println("hello world");
            System.out.println(this+"2");
        }
    //子类构造器中的第一句话,super();又把我们的视线引到父类HelloWorld的构造器
    //父类构造器:
        public HelloWorld() {
            System.out.println("Hello World");
            System.out.println(this+"1");
        }
    //第一句话好说,打印:Hello World
    //问题的关键在于第二句:这里的this虽然是一个父类的引用,但实际指向的其实是一个子类对象
    //所以当线程执行到这一句时:
    System.out.println(this+"1");
    (请注意注意这句话其实等于:System.out.println(this.toString()+"1");)
    由于这里的this指向的其实是一个子类对象,所以根据动态方法分配,JVM实际调用的toString()方法是子类中的重写方法,也就是:
        public String toString() {
    //        return new Integer(i).toString();
            return super.toString();
        }
    //比较绕的是,这个重写的toString()方法,返回的却是super.toString(),即父类的toString()方法
    //所以在这个例子中,其实是绕了一个圈又回到原处,接下来顺藤摸瓜去看父类的toString()方法:
        public String toString() {
            return new Integer(i).toString();
        }
    //由于在父类中,所以此时的i是父类的成员变量,因此i=2
    //绕了一大圈,最后确定:
      System.out.println(this+"1");
    //的打印结果为:21//接下来线程返回子类构造器中,继续执行剩下的语句:
            System.out.println("hello world");
           System.out.println(this+"2");
    //第一句话好说,打印:hello world
    //第二句话,在这第一个例子中,内部执行过程与上面是一样的,所以打印结果是:22
      

  9.   

    package com.jwh.cn;public class ExtendsRun extends HelloWorld{
        public int i=1;//程序执行六 super.i=2 super.i=1
        public ExtendsRun() {
            super();//程序执行顺序2,变量super.i=0,this.i=0;
            System.out.println("hello world");//程序执行7
            System.out.println(this+"2");//执行8  打印12
        }
        
        public static void main(String[] args) {
            ExtendsRun er = new ExtendsRun();//程序执行顺序1,变量super.i,this.i没有分配空间
        }
        @Override
        public String toString() {
            return new Integer(i).toString();//程序执行5.1
    //        return super.toString();
        }
    }class HelloWorld{
        public int i=2;//程序执行顺序三,super.i=2,this.i=0;
        public HelloWorld() {
            System.out.println("Hello World");//程序执行4,变量没变化
            System.out.println(this+"1");//程序执行5,变量没变化,打印01
        }
        
        @Override
        public String toString() {
            return new Integer(i).toString();
        }
        
        public int getI() {
            return i;
        }
        
        public void setI(int i) {
            this.i = i;
        }
    }
      

  10.   


    本身父类就不负责子类的成员初始化,而01又是父类的toString方法输出的结果,
    跟子类的i又何关系? 
    不要说子类的i了,就是父类自身的i也没有关系。
    你把父类的toString方法注释掉看看,一样是01。
      

  11.   

    下班回家吃完饭冲完凉哄娃娃睡着了,打开csdn准备继续分析第二段代码执行过程,结果发现楼上已经有高人回答了csdn速度也太快了吧~
      

  12.   

    开始上班,看见好多回答。首先先谢谢各位大哥了。特别是kaobaoxing 和 bigbro001 。仔细研究了下,问题还是归结于,进行子类构造函数的时候,父类成员变量和子类成员变量有没有初始化的问题===实例化子类时,初始化顺序问题。最近上班没啥事,研究研究
      

  13.   


    测试,同意其观点,即父类toString方法不存在时,会输出01,12呢。。我尝试着把后面的+1 删掉,结果为0,1.原理猜测为 多态,父类自动引用子类输出方法。但是使用子类的成员变量,因为似乎我把成员变量i也重写了。
    然后,我将子类的toString方法注释掉,父类的存在,结果为 2 2,说明默认情况下,父类的函数,子类函数若没有重写的情况下,默认会产生 某方法(var1,var2,...){super.某方法(var1,var2...)}.
    这里还有一个情况要注意的是,不管什么方法,没有写 和 简单的copy&parse是完全不一样的。特别是父类有成员变量,子类也有成员变量的时候。汗,以前老师没教我成员变量可以重写,并且可能造成不可估计的错误。这也可能是老师没说的缘故吧。
    用debug,来看下数据运行过程。。
      

  14.   

    说一下,toString方法在父类里默认是有的,是super.toString(),每个class都有,虽然没写,但是参考21楼本人说的。隐式的存在,构成了多态。
      

  15.   

    搞清楚了。重写了下文件,利用debug,看出了门道。呵呵
    package com.jwh.cn;public class ExtendsRun extends HelloWorld{
    public int i=1;
    public ExtendsRun() {
    super();
    System.out.println("hello world");
    // System.out.println(this+"2");
    System.out.println(this);
    }

    public static void main(String[] args) {
    ExtendsRun er = new ExtendsRun();
    }
    @Override
    public String toString() {
    return new Integer(i).toString();
    // return super.toString();
    }
    }class HelloWorld{
    public int i=2;
    public HelloWorld() {
    super();
    System.out.println("Hello World");
    // System.out.println(this+"1");
    System.out.println(this);
    }

    @Override
    public String toString() {
    return new Integer(i).toString();
    }

    public int getI() {
    return i;
    }

    public void setI(int i) {
    this.i = i;
    }
    }这样就可以清楚的表达出,若是构造函数里有super();那么其成员变量是在先运行完super();之后初始化的,若没有super();那么在构造函数第一步是隐式的初始化成员变量。另外修正下 4楼的说法,其成员变量初始化是在构造函数运行之后进行的,而非构造函数运行之前,这就解释了我为什么一直看不懂的原因。
    好了,结贴。
      

  16.   


    父类中的this是应该是指向子类的对象吧,相当时er.toString,所以应该是和子类的i有关系的