A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

周毅中

注册黑马

  • 黑马币:

  • 帖子:

  • 精华:

先上代码,大家看看到底i是多少?
  1. public class MyTest {
  2.         public static void main(String[] args) {
  3.                 new Dog();
  4.         }
  5. }

  6. class Animal {
  7.         private int i = 2;
  8.        
  9.         public Animal() {
  10.                 this.display();
  11.         }
  12.        
  13.         public void display() {
  14.                 System.out.println(i);
  15.         }
  16. }

  17. class Dog extends Animal {
  18.         private int i = 22;
  19.        
  20.         public Dog() {
  21.                 i = 222;
  22.         }
  23.        
  24.         public void display() {
  25.                 System.out.println(i);
  26.         }
  27. }
复制代码
求牛人解释。。。怎么也猜不到打印的竟然是0。

评分

参与人数 1技术分 +1 收起 理由
贠(yun)靖 + 1

查看全部评分

38 个回复

倒序浏览
哥表示,能解答,在调试中。。。。。稍等哈
回复 使用道具 举报
调试了一遍,关系明确,看不出什么问题,同惑,共求解。
回复 使用道具 举报
本帖最后由 王明(1988) 于 2012-4-25 09:45 编辑

看来见鬼的不是你啊,是你被鬼玩了。哈哈哈哈{:soso_e144:}{:soso_e144:}
我给程序加了注释,你仔细看下啊,给你上我改过的(加注释了)代码:
  1. package com.heima;

  2. public class MyTest {
  3. public static void main(String[] args) {
  4. new Dog();
  5. }
  6. }

  7. class Animal {
  8. private int i = 2;

  9. public Animal() {
  10. //由于你创建的是Dog对象,要明白this在这里究竟指向谁?我调用getClass()来看看
  11. System.out.println(this.getClass());
  12. this.display();
  13. }

  14. public void display() {
  15. System.out.println("Animal的display方法");
  16. System.out.println(i);
  17. }
  18. }

  19. class Dog extends Animal {
  20. private int i = 22;

  21. public Dog() {
  22. super();//加上super()是为了更直观点
  23. i = 222;
  24. }

  25. public void display() {
  26. System.out.println("Dog的display方法");
  27. System.out.println(this.i);
  28. }
  29. }
复制代码
现在给你解释。
首先new Dog();这时jvm给你在堆内存中申请空间,此刻i=0,而在调用父类构造方法之前,先去初始化实例变量的值和初始化块(先静态,后非静态),这时在进入构造函数之前i被初始化为2(这个i作为一份拷贝存在Dog当前对象中)。进入父类构造方法后,由于你使用this.display();你用到了this,这很关键,要明白this究竟指向谁呢?很显然this指向的不是Animal而是Dog(虽然这个this你写在Animal中),所以接下来调用的是Dog的display()方法而不是Animal的display()方法,在Dog的display方法中你打印i的值(注意这是的打印的i不是之前的拷贝,是原始的i),由于此i是在Dog类中,并且在Dog中没有进行初始化,所以仍是0,当this.dispaly();完了之后要先初始化Dog的实例变量值,这是i(不是拷贝i,拷贝i为2)为22,执行Dog构造方法中的i=222;后i(不是拷贝i)为222。注意哦亲,你的打印语句是在Dog的display方法里面,i是那时的i非此时的i。

其实你可以给程序加断点,进行调试跟踪,对i的变化那是一目了然。等会给你上图。。。
回复 使用道具 举报
代码忘上了等我修改
回复 使用道具 举报
表示你这个问题,很经典,很能说明对象在创建时,变量时怎么变化的。。。。
现在给你上我调试的图片,有图有真相。。。。。。我在其中加了断点,你注意哦,绿色的断点。
1.
回复 使用道具 举报
王勃 中级黑马 2012-4-25 10:04:47
7#
2.我step into(F5)进入父类,在调用父类构造方法前,先实例化拷贝i,注意变化哦,

F6后,上图:


回复 使用道具 举报
王勃 中级黑马 2012-4-25 10:25:14
8#
好,图片能看清楚,我继续。。。。
直接上图了,不文字说明了。


见证奇迹的时刻到了。输入的是0。

下来的i变化,你自己跟踪调试,我就不上图。已经打印你的i了,之后i会变成22,再变成222,此时i非彼时i。
调用毕老师的话,O了。{:soso_e128:}{:soso_e128:}

评分

参与人数 2技术分 +2 黑马币 +10 收起 理由
贠(yun)靖 + 2 你太给力了 呵呵
周毅中 + 10 很给力!多谢了,我把全部钱都给你。。。。.

查看全部评分

回复 使用道具 举报
感谢王同学的解答,对于对象的创建,以及其中变量的变化,我从此不会迷糊,也调用毕老师的一句话:O了。
回复 使用道具 举报
沈样 黑马帝 2012-4-25 10:40:51
10#
我是这样分析不知道对不对,new Dog();这个会在堆内存中先找到Dog.class并加载进来,但是在加载之前,方法区内的方法就已经加载了,因为public Dog() 的构造方法中在
i = 222;之前是先调用父类的默认构造方法,在父类默认函数调this.display() 时,this是调用他的类,即dog类的display,所以打印的是dog的i,因为方法区内的要先于对象创建,
所以调用时i还没初始化,如果加static关键字加载到方法区内,就能打印出i的值,不过在这里我也不太确定,因为属性初始化要先于构造函数,有知道的兄弟也发个信息给我,
共同学习一下
回复 使用道具 举报
学习了~~
回复 使用道具 举报
沈样 黑马帝 2012-4-25 10:53:12
12#
在网上找了一下,或许可以解释一下:(1)设置成员的值为默认的初始值(0,false,null)。

  (2)调用对象的构造方法(但是还没有执行构造方法体)。
  (3)调用父类的构造方法。
    (4)使用初始化程序和初始块初始化成员。
  (5)执行构造方法体。
这样就可以解释为什么构造方法前为什么没有初始化成员值
回复 使用道具 举报
本帖最后由 王明(1988) 于 2012-4-25 11:00 编辑

额,沈同学,加载顺序你错了,还有方法区的理解也有问题、、、、
在加载main方法后,静态变量不管父类还是子类的都执行了,然后才是父类和子类的的普通变量和构造器。这是因为,当要创建子类这个对象时,
发现这个类需要一个父类,所以把父类的.class加载进来,然后依次初始化其普通变量和初始化代码块,最后才是构造器,然后可以开始子类的工作,
把子类的.class加载进来,在做子类的工作。而方法区的代码是存放代码的地方,虽然jvm在程序执行前将代码放进去,但代码是静态的,还没真正执行,在执行时,初始化Dog中的i为默认的0.这个样子。

给你一个静态变量,静态初始化块,和普通变量,以及构造器的加载顺序:
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
*************in main***************
父类--变量
父类--初始化块
父类--构造器
子类--变量
子类--初始化块
子类--构造器
*************second subClass***************
父类--变量
父类--初始化块
父类--构造器
子类--变量
子类--初始化块
子类--构造器
回复 使用道具 举报
我给你代码,你自己看看吧>>>>>
回复 使用道具 举报
代码:
  1. package com.heima;

  2. class Parent {
  3. // 静态变量
  4. public static String p_StaticField = "父类--静态变量";
  5. // 变量(其实这用对象更好能体同这一点,如专门写一个类的实例)

  6. // 如果这个变量放在初始化块的后面,是会报错的,因为你根本没有被初始化
  7. public String p_Field = "父类--变量";
  8. // 静态初始化块
  9. static {
  10. System.out.println(p_StaticField);
  11. System.out.println("父类--静态初始化块");
  12. }
  13. // 初始化块
  14. {
  15. System.out.println(p_Field);
  16. System.out.println("父类--初始化块");
  17. }

  18. // 构造器
  19. public Parent() {
  20. System.out.println("父类--构造器");
  21. }
  22. }

  23. public class SubClass extends Parent {
  24. // 静态变量
  25. public static String s_StaticField = "子类--静态变量";
  26. // 变量
  27. public String s_Field = "子类--变量";
  28. // 静态初始化块
  29. static {
  30. System.out.println(s_StaticField);
  31. System.out.println("子类--静态初始化块");
  32. }
  33. // 初始化块
  34. {
  35. System.out.println(s_Field);
  36. System.out.println("子类--初始化块");
  37. }

  38. // 构造器
  39. public SubClass() {
  40. // super();
  41. System.out.println("子类--构造器");
  42. }

  43. // 程序入口
  44. public static void main(String[] args) {
  45. System.out.println("*************in main***************");
  46. new SubClass();
  47. System.out.println("*************second subClass***************");
  48. new SubClass();
  49. }
  50. }
复制代码
输出结果:

评分

参与人数 1技术分 +2 收起 理由
贠(yun)靖 + 2 哥们基础真心不错~~!

查看全部评分

回复 使用道具 举报
总之,原理都是文字,那里有实践来的更直接啊,所以i是多少,自己断点调试跟踪,一目了然,这个问题到此,大家都一起O了吧。。。。
{:soso_e128:}
回复 使用道具 举报
王明讲的已经非常清楚了,我就不多赘述了,支持王明的讲解,实在是很给力啊!
回复 使用道具 举报
哥表示是1988年的王明,论坛里还有和我重名的,注册王明不成功,郁闷的很那,只能说老爸太崇拜导致长征的左倾罪人啊。
送上一句:保持写下梦想时的状态,不要因为一开始的失败而放弃,80后三十而立,为明天伏笔,今天只有努力。
一起向上吧,少年!
回复 使用道具 举报
王明(1988) 发表于 2012-4-25 11:07
代码:输出结果:

理解的那是一个透彻啊 ,佩服!
回复 使用道具 举报
我也改了一份代码,这份代码可以找到根本原因:
  1. package test;
  2. public class MyTest {
  3.         public static void main(String[] args) {
  4.                 new Dog();
  5.         }
  6. }

  7. class Animal {
  8.         private int i = 2;
  9.         
  10.         public Animal() {
  11.                         System.out.println(this.getClass().getName());
  12.                 this.display();
  13.         }
  14.         
  15.         private void display() {
  16.                       System.out.println("Animal method");
  17.             System.out.println(i);
  18.         }   
  19. }

  20. class Dog extends Animal {
  21.         private int i = 22;
  22.         
  23.         public Dog() {
  24.                 this.i = 222;
  25.         }
  26.         
  27.         private void display() {
  28.                 System.out.println("Dog method");
  29.                 System.out.println(this.i);
  30.         }
  31. }
复制代码
修改只是将display声明改为private,结果是Animal构造函数里的this指针的确是子类Dog的,但调用的display方法却是Animal!这似乎乱套了?
关键在于java虚函数的机制,子类重新定义了display方法,用多态的机制解释,当创建的是子类对象那这个display调用的就是子类定义的display。
实际可以理解为子类的方法覆盖(替换)了父类的方法,没有产生新的方法。而声明改成private后,私有成员不能外界(包括子类)访问,所以子类虽然重新定义了display方法,但他没有覆盖(替换)掉原来的display方法,而是产生了一个新的方法。
在java里你声明了一个类一个方法那它的所有代码都固定了,代表java类的那一段字节码是不会变的。
Animal构造函数的代码也是固定的。里面调用的display方法单纯的理解也是一个函数地址(标识、函数名),不管写成this.display()还是就只写成display(),它调用的还是那个函数。在声明为public的情况下display被子类重新定义的方法覆盖(替换)了,所以调用的是子类实现的display。而在声明为private的情况下,编译器是以某种规则产生了新的方法,子类的display有新的函数地址(标识、函数名)。父类Animal的构造函数方法代码又没变化,所以它调用的还是父类那个display。

评分

参与人数 1黑马币 +6 收起 理由
王勃 + 6 赞一个!多谢罗兄指教。V587

查看全部评分

回复 使用道具 举报
12下一页
您需要登录后才可以回帖 登录 | 加入黑马