一个很新的知识点:
这段java代码啥意思?修改
1.
…
显示全部
举报
添加评论
•
邀请回答
按投票排序
按时间排序
13 个回答
谢邀。
( ̄、 ̄),回答一发,这个0看起来的确很诡异,但是在字节码成面看就一目了然了。
手写贴下我写的代码,和书上的基本没有不同(除了变量名 类名以外)
然后使用:
javap命令看JVM字节码:
首先是new指令,开辟空间,然后进紧接着用bipush 10放入操作数栈中.
然后就开始走invokespecial <init> ,也就是java层面的构造方法。
然后看构造方法的code具体字节码
aload_0 这个相当于this,对你可以这么理解,任何一个实例方法都有一个隐藏参数,就是this。
然后invokespecial #10 // Method AAA/A."<init>":()V 父类构造方法。
这是定义。。。
然后不多说了 aload_0, 然后bipush 把100放入操作数栈中,然后再putfield #13
这个pufiled就是从运行时的常量池中取一个指向成员变量的引用,这个成员变量的值以及其对应的对象都会从操作数栈中弹出。
很明显就是属性的那个赋值了。。。
然后这三个
很明显,100出去后,就是构造方法里传进来的那个 10了。。。。
再然后return了。。。。。
很明显在真正初始化数据的时候,构造方法的运行优先级大于了属性的赋值。
而这段代码正好在构造方法里调用了一个虚方法。根据执行顺序,方法动态动态绑定后,很明显后面的一系列bipush putfield操作都还没有执行!
然而JVM是给int类型默认数值是0.
所以。。。。。
神奇的0就这么被打印出来了。。。
码字不容易,大大们给个赞哈哈,,,,,
最后贴贴上完整的字节码
( ̄、 ̄),回答一发,这个0看起来的确很诡异,但是在字节码成面看就一目了然了。
手写贴下我写的代码,和书上的基本没有不同(除了变量名 类名以外)
public class ACE extends A {
private int x = 100;
public ACE(int x) {
this.x = x;
}
@Override
void 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_STATIC
Code:
stack=2, locals=1, args_size=1
0: new #1 // class AAA/ACE
3: bipush 10
5: invokespecial #33 // Method "<init>":(I)V
8: return
LineNumberTable:
line 40: 0
line 41: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
然后就开始走invokespecial <init> ,也就是java层面的构造方法。
然后看构造方法的code具体字节码
public AAA.ACE(int);
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #10 // Method AAA/A."<init>":()V
4: aload_0
5: bipush 100
7: putfield #13 // Field x:I
10: aload_0
11: iload_1
12: putfield #13 // Field x:I
15: return
然后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_0
11: iload_1
12: putfield #13 // Field x:I
再然后return了。。。。。
0: aload_0
1: invokespecial #10 // Method AAA/A."<init>":()V
4: aload_0
5: bipush 100
7: putfield #13 // Field x:I
很明显在真正初始化数据的时候,构造方法的运行优先级大于了属性的赋值。
而这段代码正好在构造方法里调用了一个虚方法。根据执行顺序,方法动态动态绑定后,很明显后面的一系列bipush putfield操作都还没有执行!
然而JVM是给int类型默认数值是0.
所以。。。。。
神奇的0就这么被打印出来了。。。
码字不容易,大大们给个赞哈哈,,,,,
最后贴贴上完整的字节码
Last modified 2016-8-3; size 622 bytes
MD5 checksum 259caa63726c1e21346d410985d3b020
Compiled from "ACE.java"
public class AAA.ACE extends AAA.A
SourceFile: "ACE.java"
minor version: 0
major version: 52
flags: 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_PRIVATE
public AAA.ACE(int);
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #10 // Method AAA/A."<init>":()V
4: aload_0
5: bipush 100
7: putfield #13 // Field x:I
10: aload_0
11: iload_1
12: putfield #13 // Field x:I
15: return
LineNumberTable:
line 31: 0
line 29: 4
line 32: 10
line 33: 15
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this LAAA/ACE;
0 16 1 x I
void show();
flags:
Code:
stack=2, locals=1, args_size=1
0: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #13 // Field x:I
7: invokevirtual #26 // Method java/io/PrintStream.println:(I)V
10: return
LineNumberTable:
line 37: 0
line 38: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this LAAA/ACE;
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: new #1 // class AAA/ACE
3: bipush 10
5: invokespecial #33 // Method "<init>":(I)V
8: return
LineNumberTable:
line 40: 0
line 41: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
参考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.
重点部分标识为粗体:
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.
谢邀。这题是有点绕。这里的执行顺序是这样的
1,首先定义DemoImpl对象下有哪些属性和方法。于是我们有了属性变量x。注意这时x还没有初始化,值是0。
2,执行Demo的构造器,中间会输出x,值为0。
3,初始化DemoImpl下的属性,此时x值变为100。
4,执行DemoImpl的构造器,中间会将x的值变为30。
1,首先定义DemoImpl对象下有哪些属性和方法。于是我们有了属性变量x。注意这时x还没有初始化,值是0。
2,执行Demo的构造器,中间会输出x,值为0。
3,初始化DemoImpl下的属性,此时x值变为100。
4,执行DemoImpl的构造器,中间会将x的值变为30。
楼上回答都很对,不过书上都写了:“一个类只有执行了构造方法后才可以为类的属性初始化”。
说白了就是变量 声明 与初始化的关系。
变量的声明,告知编译器需要申请什么样大小的内存,及变量的别名:
int i 告知编译器,需要申请一块32位长的内存,这块内存的别名叫做 i
变量的初始化,告知编译器在内存中写入什么内容:
i=100 告知编译器,别名叫做i的地址里写入100这个值
java中JVM编译器对没有进行初始化的int类型内存默认读取为0。
而java中对类的私有成员赋值统一在执行构造函数时候进行,父类加载比子类早,所以父类调用子类实现的抽象方法时候子类并没有进行构造,所以父类拿到的就是一个空的地址,输出0。
个人感觉这是java在实现继承上设计不合理,这样的代码可以动态增删父类的成员,完全打破了类的独立性,要我说应该抛出父类方法未定义的错误才合适。
说白了就是变量 声明 与初始化的关系。
变量的声明,告知编译器需要申请什么样大小的内存,及变量的别名:
int i 告知编译器,需要申请一块32位长的内存,这块内存的别名叫做 i
变量的初始化,告知编译器在内存中写入什么内容:
i=100 告知编译器,别名叫做i的地址里写入100这个值
java中JVM编译器对没有进行初始化的int类型内存默认读取为0。
而java中对类的私有成员赋值统一在执行构造函数时候进行,父类加载比子类早,所以父类调用子类实现的抽象方法时候子类并没有进行构造,所以父类拿到的就是一个空的地址,输出0。
个人感觉这是java在实现继承上设计不合理,这样的代码可以动态增删父类的成员,完全打破了类的独立性,要我说应该抛出父类方法未定义的错误才合适。
首先,java中 int 类型默认值为 0
1. 子类构造方法初始化前先初始化父类构造方法
2. 父类构造方法调用了本类的抽象函数print,但是要被子类print方法覆盖,也就是说此时调用的是子类的print方法
3. 但是,在第一次调用print的时候,x还没有被赋值,仅仅是父类构造器在发挥作用,此时x =0
4. 因此打印出来的只能是0
1. 子类构造方法初始化前先初始化父类构造方法
2. 父类构造方法调用了本类的抽象函数print,但是要被子类print方法覆盖,也就是说此时调用的是子类的print方法
3. 但是,在第一次调用print的时候,x还没有被赋值,仅仅是父类构造器在发挥作用,此时x =0
4. 因此打印出来的只能是0
他想表达两个东西
1:构造函数先调用父类,再调用子类
2:对属性的赋值插在自己类的构造函数前面
所以先调用print x,再初始化x=100
要我说,这是java的bug,先把所有属性赋值,再调用构造函数就能解决。
1:构造函数先调用父类,再调用子类
2:对属性的赋值插在自己类的构造函数前面
所以先调用print x,再初始化x=100
要我说,这是java的bug,先把所有属性赋值,再调用构造函数就能解决。
匿名用户
同初学,我理解是:new一个子类对象必先完成父类的初始化(子类构造方法第一行默认有super() ),也就是完成父类的无参构造函数,同时父类无参构造函数调用了print方法,该方法在子类中重写了,所以此时调用的是子类的print方法,但x并未赋值,所以是0。