下面这个程序运行的结果让人不理解。
运行结果为:
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();
}}
运行结果为:
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();
}}
2) 在Parent的构造方法中调用eat()时,由于eat()已被Son重写,因此实际调用的是Son.eat()
如你所说那么eatID为什么第一次打印0呢?~
因为存在两个eatID,一个是Parent的等于0,一个是Son的等于1
调用的虽然是Son.eat(),但该方法中取到的eatID却是Parent的eatID
证明4L的说话是正确的
但在子类自动调用父类的构造方法并在调用eat()方法时,此时所处在父类的
“环境”中,故使用了父类的eatID,不知道是不是这样,期待大牛赐教。
public static void main(String[] args){
Son p=new Son();
}
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大神
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
*/
4.Object()
3.Parent()调用super()
2.Son()调用super()
1.main()调用new Son()
/**
*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();
}}
哦可能结果跟那个无关虽然LZ的题意是Parent p = new Son();
那我就大概说一下好了,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
有一点延伸思考
如果两个eat方法都换成private,是什么结果
都换成protected,又是什么结果上机前先推算一下看对不对
---------------------------------------------
Parent p=new Son();这一句造成了多态机制的产生;
分析一下:
第一个输出结果是创建son实例先调用Parent的构造器;顺序执行输出Parent is creating..
第二个输出结果是顺序调用eat()方法,首先子类的重写方法覆盖了父类的方法,多态发生,输出Son is eat:,p这个引用是不会访问到私有的子类属性(多态机制),更不会访问到本类私用属性(访问权限),按加载的顺序,子类的属性初始化类变量自动初始化,int型初始化即为0;所以此时输出结果为0;
第三个不说了;
第四个输出结果是重写的方法被调用,并且子类实例化(加载顺序),属性初始化并且赋值,输出结果为1。
---------------------------------------------
学java只有一年 轻喷
在上面的代码中你把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。
这里的理解有点问题如
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里是通过变量管理表的索引管理的,也就是说把属性编译为一个索引号),方法对属性的操作就是对这个相对地址的操作。
我运行了一下代码,代码的运行顺序是看明白了:
顺序:
但理解不知道是不是还有偏差:
1、首先eat()方法确实被重载了,所以两次调用的都是子类的eat();
2、第一次调用子类的eat()方法,由于是父类构造函数调用的,所以子类变量还没有赋值,但进行了初始化。
3、第二次调用子类的eat()方法,由于是调用的子类自身构造函数,之前子类变量已经赋值了。
4、加载顺序25楼发的代码已经说的很清楚了。不知道自己理解的是否有问题,请大神们指点。有两个地方原理不太明白:
1、在运行构造函数前会初始化所有变量(未赋值)。--这个是否有什么条件,还是所有的都这样?
2、在进入该类构造函数前,才会给变量赋值。--这个是怎么回事,不会根据是否调用构造函数来赋值吧?
1 没有任何条件,所有都是这样。参看10L的反编译,这个步骤由new指令来完成。然后由invokespecial来完成构造函数的调用。2 这个就看你的定义语句是怎么写的,上面说了,private int eatID = 1;相当于两条语句,即private int eatID; {eatID=0;} ,{}这个叫做初始化块,在构造函数之前调用,如果前面加上static,即static{},这个叫静态初始化块,在类加载的时候调用。
看了大家的解释后,有种豁然开朗的感觉
原来一个int eatID=1;有这么多门道,看来我学的还不深
以后得多到论坛学习
这就会出现Parent is creating..
2、你new的是Son,此时Parent的方法eat会被覆盖,且此时Son的private int eatID仅进行了声明(默认值0),而其赋值的son非静态语句块eatID=2,会在创建类时,在构造器执行前执行,所以还没执行eatID=2。
这就会出现Son is eat: 0
1) 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零;
2) 调用基类的构造方法;
3) 按照声明的顺序调用成员的初始化方法;
4) 调用导出类的构造器主体。
在第二部的时候,调用了基类的构造方法,里面有eat()方法,是子类的eat();但是这个时候还没有给eatID赋值(因为那是第三步做的),所以就输出了默认值0(这个0跟父类的初始值无关,甚至父类中没有这个字段,输出还是0)。
就是这样,所以不要在构造函数中调用可被重写的方法,容易造成隐含的错误。