大家猜猜这个java的运行结果是什么?

ludongxing
ludongxing 02月08日 字数 391

文件名是 Test.java

class A {

int x=5;

public A(){

pk();

}

public void pk() {

System.out.println(x);

}

}

class B extends A{

int x=6;

public void pk() {

System.out.println(x);

}

}

public class Test {

public static void main(String args[]) {

B mb = new B();

}

}

/////////////////////////////////////////////////////

运行结果为啥不是 6 ?

A的pk()不是被B的pk()覆盖了吗?结果不应该是 6 吗?

Java Java技术
14 个回复
here080
hero080 02月09日

ctor里一般不允许虚函数吧。

【 在 ludongxing (ludongxing) 的大作中提到: 】

: 标  题: 大家猜猜这个java的运行结果是什么?

: 发信站: 水木社区 (Sat Feb  8 22:46:21 2020), 站内

: 文件名是 Test.java

: class A {

: int x=5;

: public A(){

: pk();

: }

:     public void pk() {

:     System.out.println(x);

:     }

: }

: class B extends A{

: int x=6;

:     public void pk() {

:     System.out.println(x);

:     }

: }

: public class Test {

: public static void main(String args[]) {

:    B mb = new B();

: }

: }

: /////////////////////////////////////////////////////

: 运行结果为啥不是 6 ?

: A的pk()不是被B的pk()覆盖了吗?结果不应该是 6 吗?

: --

ludongxing
ludongxing 02月09日

这里没有虚函数啊。这就是一个简单的子类继承父类,然后子类

把父类的同名方法pk()给覆盖掉了。

【 在 here080 的大作中提到: 】

: ctor里一般不允许虚函数吧。

olddognewwit
老狗 02月09日

构造B时,因为没有自己的构造,只能调用super的构造,这时还没有执行x=6这个变量赋值。你可以在B内拷贝一份A的构造函数再看看。

huatu992
日画一图 02月09日

怎么不是虚函数。java难道不是所有函数都是虚函数吗?当然构造函数除外。你在构造函数里调用,虚函数指针还是指向基类,所以调了基类的pk

【 在 ludongxing () 的大作中提到: 】

: 这里没有虚函数啊。这就是一个简单的子类继承父类,然后子类

: 把父类的同名方法pk()给覆盖掉了。

: 【 在 here080 的大作中提到: 】

here080
hero080 02月09日

你是怎么理解“虚函数”和“覆盖”这两个东西的?

【 在 ludongxing (ludongxing) 的大作中提到: 】

: 标  题: Re: 大家猜猜这个java的运行结果是什么?

: 发信站: 水木社区 (Sun Feb  9 03:02:22 2020), 站内

: 这里没有虚函数啊。这就是一个简单的子类继承父类,然后子类

: 把父类的同名方法pk()给覆盖掉了。

: 【 在 here080 的大作中提到: 】

: : ctor里一般不允许虚函数吧。

: :

: --

chenjinyuan
心梦如水 02月09日

所以结果不应该是56吗。。。?

o,b没写构造。。。

【 在 olddognewwit 的大作中提到: 】

: 构造B时,因为没有自己的构造,只能调用super的构造,这时还没有执行x=6这个变量赋值。你可以在B内拷贝一份A的构造函数再看看。

pigkeeper
赶猪的星星 02月09日

B没有自己的构造函数,所以调用的A的,所以打印出来就是A的x

【 在 ludongxing 的大作中提到: 】

: 文件名是 Test.java

: class A {

ludongxing
ludongxing 02月09日

但是运行结果输出的是一个零,A的x是5也不是0啊?

【 在 pigkeeper 的大作中提到: 】

: B没有自己的构造函数,所以调用的A的,所以打印出来就是A的x

pigkeeper
赶猪的星星 02月09日

卧槽,还真的是,受教了

做实验下来,应该是顺序的问题

新建B的对象时,先初始化父类A的成员变量,然后调用A的构造函数,但其中的pk方法是子类B重构的pk方法,这个时候子类B的成员变量还没有初始化,所以输出x是0,接下去才会初始化子类B的成员变量,再接着调用B的构造函数,如果有的话

【 在 ludongxing 的大作中提到: 】

: 但是运行结果输出的是一个零,A的x是5也不是0啊?

: 【 在 pigkeeper 的大作中提到: 】

: : B没有自己的构造函数,所以调用的A的,所以打印出来就是A的x

elizabethxxy
谢绝私聊!网上流氓骗子多,小心谨慎! 02月09日

1. 关于类的构造方法、类继承中构造方法的访问:

如果类没有给出构造方法,系统将给出一个默认的无参构造方法供这个类使用。

子类构造方法执行前,都会先执行父类的无参构造方法。因为子类会继承父类的成员方法,父类必须先初始化好了,子类才能继承。在子类的构造方法中,默认第一行有一条语句super(),即必须在构造方法的第一行。如果子类没有写super(),系统会默认加上一条。如果我们写了构造方法,系统就不会给我们提供构造方法了。

在楼主的代码中,类B没有明确写自己的构造方法,那么它的构造方法就是默认构造方法。因为类B是类A的子类,所以在执行类B的构造方法之前,会先执行父类A的无参构造方法。

2. 关于类继承中成员方法的覆盖:

java继承中成员方法的访问特点是就近原则。子类中方法和父类中方法的声明相同时(这叫方法的覆盖override),调用的是子类中的方法。

在楼主的代码中,类A有成员方法pk(),类B也有成员方法pk(),由于类B继承了类A,所以类B是类A的子类。调用类B的构造方法时,会先调用父类A的构造方法。在父类A的构造方法中调用成员方法pk()时,由于成员方法pk()被子类B的成员方法pk()覆盖,这时调用的就是子类B中的成员方法pk()。

3. 关于变量的默认值、初始化和赋值:

类的成员变量有默认值,可以不用初始化就可以使用。给类的成员变量赋值的方法通常有两种:A.setXxx()  B.构造方法。还有一种是直接赋值的方法,如int x = 5;但是这种方法不建议采用。局部变量没有默认值,必须先定义(即初始化)、赋值,最后使用。

在楼主的代码中,类A的成员变量x和类B的成员变量x都没有采用常用的赋值的方法来赋值,所以一开始采用的都是默认值。int数据类型的默认值是0,所以x的值都是0。后期待代码调用到第三种直接赋值的方法时,才给x赋值。

4. 关于成员变量和局部变量:

成员变量的位置在类中方法外。局部变量在方法内或者方法声明上(形式参数)。如果局部变量名和成员变量名称一致,在方法中使用的时候采用的是就近原则,所以用this来区分成员变量和局部变量。方法被哪个对象调用,this就代表谁。

成员变量名称相同时,在子类方法中访问变量,(1)在方法的局部范围找,如果有就使用。(2)在子类的成员范围找,如果有就使用。(3)在父类的成员范围找,如果有就使用。(4)如果还找不到,就报错。

在楼主代码中的x都是类的成员变量,pk都是类的成员方法。类A的成员方法pk内调用变量x,先在成员方法pk内找局部变量x,由于方法pk内没有局部变量x,按照就近原则,就在成员方法pk所属的类A的成员变量中找有没有名字为x的成员变量,找到了,就采用成员变量x。B的pk方法也是类似的思路,采用B的成员变量x。

综上所述,调用的大致路线是:类B的默认构造方法-->类A的构造方法(初始化A的成员变量x=0。在类A的代码之内方法之外,从上到下执行赋值语句,将A的成员变量x赋值为5。由于A的构造方法里调用了pk()方法,先用B的成员方法pk()把A的成员方法pk()覆盖,覆盖后的pk()代码按照就近原则寻找,因为现在的pk()是B的成员方法,所以它就近找到B的成员变量x。B的成员变量x还没有初始化,所以系统采用x的默认值为0。将B的成员变量x现在的值0打印出来。到此,A的构造方法执行完毕。)-->返回到B的构造方法里,先初始化B的成员变量x=0。在类

B的代码之内方法之外,从上到下执行赋值语句,将B的成员变量x赋值为6。再执行B的构造方法里下面的代码(无)。到此,B的构造方法执行完毕。

~~~~~~~~

以上是对楼主代码的分析,下面是一些建议:

1. 不要在父类的构造方法中调用可能被子类覆盖的方法。因为子类初始化会调用父类的构造方法,当父类构造方法调用被子类覆盖的方法时,往往由于子类初始化未完成导致异常。

2. 构造方法最好只用于给对象的数据进行初始化。

3. 构造方法至少写两个:空参构造方法、全参构造方法。

4. 给普通成员变量赋值时,最好采用这两种方法: A.setXxx()   B.构造方法,在这两个方法内给成员变量赋值,不要在类A的代码之内方法之外给成员变量赋值。

~~~~~~~~

全部代码见我的第二个re贴。

over

elizabethxxy
谢绝私聊!网上流氓骗子多,小心谨慎! 02月09日
A.java (867 byte) A.java (481 byte) B.java (839 byte) B.java (271 byte) Test.java (128 byte) Test.java (128 byte)

我的全部代码如下,同时以附件的形式上传了。

//file name: A.java

public class A {

int x=5;

public A(){

System.out.println("调用pk()方法之前,A的成员变量x的值为:" + x);

pk();

System.out.println("调用pk()方法之后,A的成员变量x的值为:" + x);

}

public void pk(){

System.out.println("从类A里调用pk()方法,开始");

System.out.println(x);

System.out.println("从类A里调用pk()方法,结束");

}

}

//file name: B.java

public class B extends A {

int x=6;

public void pk() {

System.out.println("从类B里调用pk()方法,开始");

System.out.println(x);

System.out.println("从类B里调用pk()方法,结束");

}

}

//file name: Test.java

public class Test {

public static void main(String args[]) {

B mb = new B();

}

}

输出结果:

调用pk()方法之前,A的成员变量x的值为:5

从类B里调用pk()方法,开始

0

从类B里调用pk()方法,结束

调用pk()方法之后,A的成员变量x的值为:5

over

【 在 ludongxing 的大作中提到: 】

: 文件名是 Test.java

: class A {

: int x=5;

: ...................

galaxy123
galaxy123 02月09日

厉害! 条理清晰,完美回答了所有疑惑。

【 在 elizabethxxy 的大作中提到: 】

: 1. 关于类的构造方法、类继承中构造方法的访问:

: 如果类没有给出构造方法,系统将给出一个默认的无参构造方法供这个类使用。

: 子类构造方法执行前,都会先执行父类的无参构造方法。因为子类会继承父类的成员方法,父类必须先初始化好了,子类才能继承。在子类的构造方法中,默认第一行有一条语句super(),即必须在构造方法的第一行。如果子类没有写super(),系统会默认加上一条。如果我们写了构造方法,系统就不会给我们提供构造方法了。

: ....................

elizabethxxy
谢绝私聊!网上流氓骗子多,小心谨慎! 02月09日

狂汗

回答网友提出的问题的过程  也是  我自己学习提高的过程。采用以终为始的学习策略,以能够完整地把知识点讲出来的方式  来 检验 自己学习到的知识、查漏补缺,算是挤兑自己、强迫自己老老实实学习、不许偷懒的方式吧。

大家共同学习,共同提高!

【 在 galaxy123 的大作中提到: 】

: 厉害! 条理清晰,完美回答了所有疑惑。

: 【 在 elizabethxxy 的大作中提到: 】

: : 1. 关于类的构造方法、类继承中构造方法的访问:

beishuihan
小北 02月10日

能回复么