V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
palmers
V2EX  ›  程序员

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

  •  1
     
  •   palmers · 2016-02-15 16:38:08 +08:00 · 3188 次点击
    这是一个创建于 3189 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    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() ;
    
    第 1 条附言  ·  2016-02-15 17:53:15 +08:00

    非常感谢大家的回复,我大概知道了,谢谢! 就不一一回复大家啦! Thanks @All

    21 条回复    2016-02-16 09:15:43 +08:00
    fwrq41251
        1
    fwrq41251  
       2016-02-15 16:42:26 +08:00
    最好把测试代码贴一下
    palmers
        2
    palmers  
    OP
       2016-02-15 16:45:20 +08:00
    @fwrq41251 测试代码就一行 实例化 C 类对象 C c = new C() ;
    allenforrest
        3
    allenforrest  
       2016-02-15 17:01:37 +08:00
    没看懂你的问题。。。
    构造器里为啥不能调用成员方法?
    fwrq41251
        4
    fwrq41251  
       2016-02-15 17:04:29 +08:00
    java 里面构造方法会隐式的调用父类的无参构造方法(不管你写不写 super();这一句其实都一样).
    静态成员或者静态块初始化和执行的时机是第一次使用到它们的时候.
    没有直接回答 LZ 的问题,希望有所帮助..
    okeydokey
        5
    okeydokey  
       2016-02-15 17:06:01 +08:00
    类型信息在 jvm 启动阶段就已经写到方法区了,你 new 不 new 对象都可以调用,楼主最好搞清楚 java 内存模型,
    raysonx
        6
    raysonx  
       2016-02-15 17:08:00 +08:00
    基类的构造函数是在派生类的构造函数之前调用的。基类的构造函数返回前,子类的构造函数还没有调用,成员无法初始化。
    lusyoe
        7
    lusyoe  
       2016-02-15 17:10:55 +08:00
    楼主的意思应该是实例还没生成怎么能调用到实例中的方法的。。不过有个误区不是类的构造函数执行完才生成对象的实例啊,在执行构造函数之前就有申请空间、静态初始化等一系列的操作,应该是在申请好空间后差不多实例就已经生成了,有了实例怎么就不能调用实例中的方法呢?你可以 Debug 一下看看
    dullwit
        8
    dullwit  
       2016-02-15 17:13:39 +08:00
    Java 对象的实现化,是按照成员变量的声明顺序进行初始化,最后才是构造函数。当然在继承的状态下,是按照祖先链依次从基类进行初始化。
    pelloz
        9
    pelloz  
       2016-02-15 17:16:50 +08:00   ❤️ 1
    楼主,你在将 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
        10
    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
        11
    raysonx  
       2016-02-15 17:26:49 +08:00
    @pelloz +1 好例子。
    Java 会在分配内存之后(但在初始化之前)对内存用 0 填充。
    补充:如果换成 C++的话,成员初始你之前是之前内存的垃圾数据,你会得到一个很古怪的值。 C++编译器在 Release 编译模式下不会对分配的内容做填充。
    palmers
        12
    palmers  
    OP
       2016-02-15 17:44:00 +08:00
    @asj 非常感谢!
    KentY
        13
    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
        14
    palmers  
    OP
       2016-02-15 17:46:07 +08:00
    @pelloz 嗯嗯 你解释的很对,只是我一直以为,对象没有构造完成,对象方法也不能使用,通过大家的回复,我这个结论大多是错了。 非常感谢!
    palmers
        15
    palmers  
    OP
       2016-02-15 17:48:06 +08:00
    @pelloz `即使你在 fun()方法中修改 nu ,也会在接下来的 C 的初始化中被改回来。` 这句话我觉得使用引用类型更有说服力。
    mfaner
        16
    mfaner  
       2016-02-15 17:57:15 +08:00
    这种在 NetBeans 会出警告的: Overridable Method Call in Constructor
    zonghua
        17
    zonghua  
       2016-02-15 18:27:54 +08:00 via iPhone
    @pelloz 不明白,为什么默认调用 A 的构造方法,然后调用的又是 C 重写的 fun 方法。不是 A 去调用吗?
    pelloz
        18
    pelloz  
       2016-02-15 19:21:53 +08:00
    @zonghua ...请复习下 java 的多态概念和 java 默认构造方法的隐式调用。
    zonghua
        19
    zonghua  
       2016-02-16 00:00:41 +08:00 via iPhone
    @pelloz 复习不了,感觉这种问来一次就一次
    palmers
        20
    palmers  
    OP
       2016-02-16 09:02:25 +08:00
    @mfaner 恩 根据 think in java 描述,不推荐与初始化无关的操作
    palmers
        21
    palmers  
    OP
       2016-02-16 09:15:43 +08:00
    @zonghua 这里 C 继承 A 初始化 C 之前需要先初始化 A, 因为 C 默认显式继承了 A 的部分信息,如果 A 没有初始化,则 A 的初始化信息 C 也就拿不到,严格上 C 是不能使用的。所以 A 需要得到初始化。 所以在默认情况下 C 初始化之前会隐式调用父类默认构造方法,保证父类(基类)能在子类初始化之前得到初始化。

    关于在 A 构造方法中调用了子类方法,其实是因为多态特性, JVM 发现 fun 方法被子类覆写,则调用了子类 fun 方法,实现方法多态,这个我觉得是因为当前类型信息为 C 所以才会调用 fun 的。

    以上是我的理解,你可以参考,但不一定正确。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   979 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 21:17 · PVG 05:17 · LAX 13:17 · JFK 16:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.