Java 父类的构造函数执行要早于子类的实例变量初始化

编程入门 行业动态 更新时间:2024-10-11 13:28:04

Java 父类的构造函数执行要早于<a href=https://www.elefans.com/category/jswz/34/1770793.html style=子类的实例变量初始化"/>

Java 父类的构造函数执行要早于子类的实例变量初始化

一个很新的知识点:


知乎 朱谷粒sir
  • 首页
  • 话题
  • 发现
  • 消息
Java Java 编程 修改

这段java代码啥意思?修改

1. …  显示全部 举报 添加评论  分享   •  邀请回答 按投票排序 按时间排序

13 个回答

流星 专注于任何好玩、稀奇古怪的事物。 17 人赞同 谢邀。
( ̄、 ̄),回答一发,这个0看起来的确很诡异,但是在字节码成面看就一目了然了。
手写贴下我写的代码,和书上的基本没有不同(除了变量名 类名以外)
public class ACE extends A {private int x = 100;public ACE(int x) {this.x = x;}@Overridevoid show() {System.out.println(this.x);}public static void main(String[] args) {new ACE(10);}
}abstract class A {public A() {show();}abstract void show();
}

然后使用:
javap命令看JVM字节码:
 public static void main(java.lang.String[]);flags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: new           #1                  // class AAA/ACE3: bipush        105: invokespecial #33                 // Method "<init>":(I)V8: returnLineNumberTable:line 40: 0line 41: 8LocalVariableTable:Start  Length  Slot  Name   Signature0       9     0  args   [Ljava/lang/String;
首先是new指令,开辟空间,然后进紧接着用bipush 10放入操作数栈中.
然后就开始走invokespecial <init> ,也就是java层面的构造方法。

然后看构造方法的code具体字节码
 public AAA.ACE(int);flags: ACC_PUBLICCode:stack=2, locals=2, args_size=20: aload_01: invokespecial #10                 // Method AAA/A."<init>":()V4: aload_05: bipush        1007: putfield      #13                 // Field x:I10: aload_011: iload_112: putfield      #13                 // Field x:I15: return
aload_0 这个相当于this,对你可以这么理解,任何一个实例方法都有一个隐藏参数,就是this。
然后invokespecial #10 // Method AAA/A."<init>":()V 父类构造方法。
   #7 = Utf8               <init>#8 = Utf8               (I)V#9 = Utf8               Code#10 = Methodref          #3.#11         //  AAA/A."<init>":()V#11 = NameAndType        #7:#12         //  "<init>":()V
这是定义。。。
然后不多说了 aload_0, 然后bipush 把100放入操作数栈中,然后再putfield #13
这个pufiled就是从运行时的常量池中取一个指向成员变量的引用,这个成员变量的值以及其对应的对象都会从操作数栈中弹出。
很明显就是属性的那个赋值了。。。
然后这三个
        10: aload_011: iload_112: putfield      #13                 // Field x:I
很明显,100出去后,就是构造方法里传进来的那个 10了。。。。
再然后return了。。。。。
  0: aload_01: invokespecial #10                 // Method AAA/A."<init>":()V4: aload_05: bipush        1007: putfield      #13                 // Field x:I

很明显在真正初始化数据的时候,构造方法的运行优先级大于了属性的赋值。
而这段代码正好在构造方法里调用了一个虚方法。根据执行顺序,方法动态动态绑定后,很明显后面的一系列bipush putfield操作都还没有执行!
然而JVM是给int类型默认数值是0.
所以。。。。。

神奇的0就这么被打印出来了。。。


码字不容易,大大们给个赞哈哈,,,,,




最后贴贴上完整的字节码
  Last modified 2016-8-3; size 622 bytesMD5 checksum 259caa63726c1e21346d410985d3b020Compiled from "ACE.java"
public class AAA.ACE extends AAA.ASourceFile: "ACE.java"minor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Class              #2             //  AAA/ACE#2 = Utf8               AAA/ACE#3 = Class              #4             //  AAA/A#4 = Utf8               AAA/A#5 = Utf8               x#6 = Utf8               I#7 = Utf8               <init>#8 = Utf8               (I)V#9 = Utf8               Code#10 = Methodref          #3.#11         //  AAA/A."<init>":()V#11 = NameAndType        #7:#12         //  "<init>":()V#12 = Utf8               ()V#13 = Fieldref           #1.#14         //  AAA/ACE.x:I#14 = NameAndType        #5:#6          //  x:I#15 = Utf8               LineNumberTable#16 = Utf8               LocalVariableTable#17 = Utf8               this#18 = Utf8               LAAA/ACE;#19 = Utf8               show#20 = Fieldref           #21.#23        //  java/lang/System.out:Ljava/io/PrintStream;#21 = Class              #22            //  java/lang/System#22 = Utf8               java/lang/System#23 = NameAndType        #24:#25        //  out:Ljava/io/PrintStream;#24 = Utf8               out#25 = Utf8               Ljava/io/PrintStream;#26 = Methodref          #27.#29        //  java/io/PrintStream.println:(I)V#27 = Class              #28            //  java/io/PrintStream#28 = Utf8               java/io/PrintStream#29 = NameAndType        #30:#8         //  println:(I)V#30 = Utf8               println#31 = Utf8               main#32 = Utf8               ([Ljava/lang/String;)V#33 = Methodref          #1.#34         //  AAA/ACE."<init>":(I)V#34 = NameAndType        #7:#8          //  "<init>":(I)V#35 = Utf8               args#36 = Utf8               [Ljava/lang/String;#37 = Utf8               SourceFile#38 = Utf8               ACE.java
{private int x;flags: ACC_PRIVATEpublic AAA.ACE(int);flags: ACC_PUBLICCode:stack=2, locals=2, args_size=20: aload_01: invokespecial #10                 // Method AAA/A."<init>":()V4: aload_05: bipush        1007: putfield      #13                 // Field x:I10: aload_011: iload_112: putfield      #13                 // Field x:I15: returnLineNumberTable:line 31: 0line 29: 4line 32: 10line 33: 15LocalVariableTable:Start  Length  Slot  Name   Signature0      16     0  this   LAAA/ACE;0      16     1     x   Ivoid show();flags:Code:stack=2, locals=1, args_size=10: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;3: aload_04: getfield      #13                 // Field x:I7: invokevirtual #26                 // Method java/io/PrintStream.println:(I)V10: returnLineNumberTable:line 37: 0line 38: 10LocalVariableTable:Start  Length  Slot  Name   Signature0      11     0  this   LAAA/ACE;public static void main(java.lang.String[]);flags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: new           #1                  // class AAA/ACE3: bipush        105: invokespecial #33                 // Method "<init>":(I)V8: returnLineNumberTable:line 40: 0line 41: 8LocalVariableTable:Start  Length  Slot  Name   Signature0       9     0  args   [Ljava/lang/String;
}
发布于 昨天 17:22  16 条评论  感谢  分享   收藏  •  没有帮助  •  举报  •  禁止转载 7cat 敲键盘的铲屎官~ 4 人赞同 参考JVM 规范,父类的构造函数执行要早于子类的实例变量初始化,详细参考此链接  Chapter 12. Execution 。
重点部分标识为粗体:

Next,  if C is a class rather than an interface, and its superclass SC has not yet been initialized, then recursively perform this entire procedure for SC. If necessary, verify and prepare SC first. If the initialization of SC completes abruptly because of a thrown exception, then acquire LC, label the Class object for C as erroneous, notify all waiting threads, release LC, and complete abruptly, throwing the same exception that resulted from initializing SC

Next, determine whether assertions are enabled ( §14.10) for C by querying its defining class loader.

Next , execute either the class variable initializers and static initializers of the class, or the field initializers of the interface, in textual order, as though they were a single block. 编辑于 12:05  1 条评论  感谢  分享   收藏  •  没有帮助  •  举报  •  作者保留权利 小耿 生物狗转职程序猿 5 人赞同 谢邀。这题是有点绕。这里的执行顺序是这样的

1,首先定义DemoImpl对象下有哪些属性和方法。于是我们有了属性变量x。注意这时x还没有初始化,值是0。

2,执行Demo的构造器,中间会输出x,值为0。

3,初始化DemoImpl下的属性,此时x值变为100。

4,执行DemoImpl的构造器,中间会将x的值变为30。 发布于 昨天 09:16  5 条评论  感谢  分享   收藏  •  没有帮助  •  举报  •  作者保留权利 Vincent Lee 秋水时至 7cat说的那个就是。
Java面试题:类加载过程和子类重写父类方法的调用 瞎写的,看看就好。 编辑于 21:46  添加评论  感谢  分享   收藏  •  没有帮助  •  举报  •  作者保留权利 Man Lee 反智,随缘 楼上回答都很对,不过书上都写了:“一个类只有执行了构造方法后才可以为类的属性初始化”。

说白了就是变量 声明 与初始化的关系。
变量的声明,告知编译器需要申请什么样大小的内存,及变量的别名:
int i 告知编译器,需要申请一块32位长的内存,这块内存的别名叫做 i

变量的初始化,告知编译器在内存中写入什么内容:
i=100 告知编译器,别名叫做i的地址里写入100这个值

java中JVM编译器对没有进行初始化的int类型内存默认读取为0。
而java中对类的私有成员赋值统一在执行构造函数时候进行,父类加载比子类早,所以父类调用子类实现的抽象方法时候子类并没有进行构造,所以父类拿到的就是一个空的地址,输出0。

个人感觉这是java在实现继承上设计不合理,这样的代码可以动态增删父类的成员,完全打破了类的独立性,要我说应该抛出父类方法未定义的错误才合适。 发布于 11:35  添加评论  感谢  分享   收藏  •  没有帮助  •  举报  •  作者保留权利 此人已死 专注机器学习 首先,java中 int 类型默认值为 0
1. 子类构造方法初始化前先初始化父类构造方法
2. 父类构造方法调用了本类的抽象函数print,但是要被子类print方法覆盖,也就是说此时调用的是子类的print方法
3. 但是,在第一次调用print的时候,x还没有被赋值,仅仅是父类构造器在发挥作用,此时x =0
4. 因此打印出来的只能是0 发布于 06:34  添加评论  感谢  分享   收藏  •  没有帮助  •  举报  •  作者保留权利 士大夫 无产阶级 1 人赞同 他想表达两个东西
1:构造函数先调用父类,再调用子类
2:对属性的赋值插在自己类的构造函数前面
所以先调用print x,再初始化x=100

要我说,这是java的bug,先把所有属性赋值,再调用构造函数就能解决。 编辑于 00:37  添加评论  感谢  分享   收藏  •  没有帮助  •  举报  •  作者保留权利 Dean.Lee 过不去的坎永远只是暂时的… 谢邀!对java木有研究,骚瑞! 发布于 昨天 15:13  添加评论  感谢  分享   收藏  •  没有帮助  •  举报  •  作者保留权利 千里冰封 Jetbrains大主教/高一/双性恋/代码/电子… 1 人赞同 我靠,为啥没写super也调用了父类构造?

回去试试,感觉和我想的不一样。它的描述我看懂了,但我总觉得是错的。。。


忘了说,谢邀 编辑于 昨天 02:02  2 条评论  感谢  分享   收藏  •  没有帮助  •  举报  •  作者保留权利 不相信 科密都是狗/dog 主要意思就是:1.实例化对象的时候,会先调用父类的构造方法2.父类的方法执行时,其实执行的是子类的方法(如果子类有,而且实例化的是子类)。 发布于 12:47  添加评论  感谢  分享   收藏  •  没有帮助  •  举报  •  作者保留权利 匿名用户 同初学,我理解是:new一个子类对象必先完成父类的初始化(子类构造方法第一行默认有super() ),也就是完成父类的无参构造函数,同时父类无参构造函数调用了print方法,该方法在子类中重写了,所以此时调用的是子类的print方法,但x并未赋值,所以是0。 发布于 昨天 21:30  添加评论  感谢  分享   收藏  •  没有帮助  •  举报  •  作者保留权利 爱吃西红柿的洋芋 不爱写代码的西红柿 执行子类构造方法会先执行父类构造方法,而父类构造方法中调用了被重写的print,此时又回到了子类的print方法中,然而此时子类的初始化还未完成,但空间已分配,x在被分配空间时,默认值是0。 发布于 昨天 18:57  1 条评论  感谢  分享   收藏  •  没有帮助  •  举报  •  作者保留权利 打杂小二 西邮/帮倒忙/念书念得不好 抽象方法必须实例化后才能调用 否则为默认值 发布于 昨天 06:49  2 条评论  感谢  分享   收藏  •  没有帮助  •  举报  •  作者保留权利 朱谷粒sir,一个穿着西装的和尚。 添加话题经验,提升回答可信度

写回答…

发布回答   设置 24 人关注该问题

问题状态

最近活动于  昨天 01:14  •  查看问题日志 被浏览  5094 次,相关话题关注者  116116 人
  • 刘看山 
  • 知乎指南 
  • 建议反馈 
  • 移动应用 
  • 加入知乎 
  • 知乎协议 
  • 联系我们
© 2016 知乎

更多推荐

Java 父类的构造函数执行要早于子类的实例变量初始化

本文发布于:2024-03-04 21:34:12,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1710479.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:子类   初始化   变量   函数   实例

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!