请教大家一个 java 多态在对象创建中的问题。

2016-02-15 16:38:08 +08:00
 palmers

问题是这样的,先看一段代码 :

public class A {

    public int num = 1 ;

    public A() {
        fun();
        System.out.println("A constuctor.");
    }

    public void fun() {
        System.out.println("A func." + num);
    }
}

类 C:

public class C extends A {

    public int nu = 100 ;
    public static int no = 101 ;

    public C() {
        System.out.println("C constuctor.");
    }

    @Override
    public void fun() {
        System.out.println("C func." + nu + " --- " + no);
    }
}

以上代码中,父类 A 在构造方法中调用了一个子类覆写的方法, 经过测试,在实例化 C 类对象时可以正常调用 C 类中 fun 方法 (覆写父类方法),只是 C 类成员属性没有初始化完成。
我想不明白,在 C 类没有构造完成的时候怎么可以调用对象方法呢? 目前我看不懂 JVM 原理,请知道这块的朋友帮我详细解释下,非常感谢!


测试代码就一行:

C c = new C() ;
3227 次点击
所在节点    程序员
21 条回复
fwrq41251
2016-02-15 16:42:26 +08:00
最好把测试代码贴一下
palmers
2016-02-15 16:45:20 +08:00
@fwrq41251 测试代码就一行 实例化 C 类对象 C c = new C() ;
allenforrest
2016-02-15 17:01:37 +08:00
没看懂你的问题。。。
构造器里为啥不能调用成员方法?
fwrq41251
2016-02-15 17:04:29 +08:00
java 里面构造方法会隐式的调用父类的无参构造方法(不管你写不写 super();这一句其实都一样).
静态成员或者静态块初始化和执行的时机是第一次使用到它们的时候.
没有直接回答 LZ 的问题,希望有所帮助..
okeydokey
2016-02-15 17:06:01 +08:00
类型信息在 jvm 启动阶段就已经写到方法区了,你 new 不 new 对象都可以调用,楼主最好搞清楚 java 内存模型,
raysonx
2016-02-15 17:08:00 +08:00
基类的构造函数是在派生类的构造函数之前调用的。基类的构造函数返回前,子类的构造函数还没有调用,成员无法初始化。
lusyoe
2016-02-15 17:10:55 +08:00
楼主的意思应该是实例还没生成怎么能调用到实例中的方法的。。不过有个误区不是类的构造函数执行完才生成对象的实例啊,在执行构造函数之前就有申请空间、静态初始化等一系列的操作,应该是在申请好空间后差不多实例就已经生成了,有了实例怎么就不能调用实例中的方法呢?你可以 Debug 一下看看
dullwit
2016-02-15 17:13:39 +08:00
Java 对象的实现化,是按照成员变量的声明顺序进行初始化,最后才是构造函数。当然在继承的状态下,是按照祖先链依次从基类进行初始化。
pelloz
2016-02-15 17:16:50 +08:00
楼主,你在将 class C 改成这样就能理解了:
public class C extends A {

public int nu = 100 ;
public static int no = 101 ;

public C() {
System.out.println("C constuctor.");
}

@Override
public void fun() {
nu = nu + 1000;//修改 nu 的值,你猜会怎样输出?
System.out.println("C func." + nu + " --- " + no);
}
}

你在实例化 C 的时候,隐式调用 A 的构造函数, A 的构造函数调用了自己的 fun()方法,但是被子类覆盖后实际调用的是 C 的 fun 方法。但是 C 的 nu 还没有初始化, int 类型默认为 0 ,即使你在 fun()方法中修改 nu ,也会在接下来的 C 的初始化中被改回来。你认为 C 类没有构造完成,但实际上 C 已经被加载到内存并且各个域已经被赋了默认的初始值(不是你指定的初始值哦),当 C 的父类构造完以后才会使用你给的初始值完成实例化。以上基于个人理解,我也没有学到 JVM 原理....
asj
2016-02-15 17:25:07 +08:00
其实 Java 规范里有个几乎一模一样的例子:
http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.5

Unlike C++, the Java programming language does not specify altered rules for method dispatch during the creation of a new class instance. If methods are invoked that are overridden in subclasses in the object being initialized, then these overriding methods are used, even before the new object is completely initialized.

Example 12.5-2 和你的例子基本上是一样的。
raysonx
2016-02-15 17:26:49 +08:00
@pelloz +1 好例子。
Java 会在分配内存之后(但在初始化之前)对内存用 0 填充。
补充:如果换成 C++的话,成员初始你之前是之前内存的垃圾数据,你会得到一个很古怪的值。 C++编译器在 Release 编译模式下不会对分配的内容做填充。
palmers
2016-02-15 17:44:00 +08:00
@asj 非常感谢!
KentY
2016-02-15 17:45:36 +08:00
当你 C c = new C();的时候, 一个 C object 已经有了, 只是所有 fields (非 static) 都是默认值, 所有 object reference fields 都是 null, method references 也已经创建了. 即使你声明 fields 时候有赋值语句, 也不被执行. 当这个准备工作完成以后, 才调用 constructor, 以及 superclass.constructor(). 所以, 到了这步是可以找到相关的 method reference 的.
palmers
2016-02-15 17:46:07 +08:00
@pelloz 嗯嗯 你解释的很对,只是我一直以为,对象没有构造完成,对象方法也不能使用,通过大家的回复,我这个结论大多是错了。 非常感谢!
palmers
2016-02-15 17:48:06 +08:00
@pelloz `即使你在 fun()方法中修改 nu ,也会在接下来的 C 的初始化中被改回来。` 这句话我觉得使用引用类型更有说服力。
mfaner
2016-02-15 17:57:15 +08:00
这种在 NetBeans 会出警告的: Overridable Method Call in Constructor
zonghua
2016-02-15 18:27:54 +08:00
@pelloz 不明白,为什么默认调用 A 的构造方法,然后调用的又是 C 重写的 fun 方法。不是 A 去调用吗?
pelloz
2016-02-15 19:21:53 +08:00
@zonghua ...请复习下 java 的多态概念和 java 默认构造方法的隐式调用。
zonghua
2016-02-16 00:00:41 +08:00
@pelloz 复习不了,感觉这种问来一次就一次
palmers
2016-02-16 09:02:25 +08:00
@mfaner 恩 根据 think in java 描述,不推荐与初始化无关的操作

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/256646

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX