面向对象的问题

考察知识点:
  • 第一,类的加载机制以及类的初始化过程;
  • 第二,继承的相关知识,其中这里涉及到子类继承父类的时候,同名的变量不会覆盖父类,只是会将父类的同名变量隐藏;
  • 第三,多态性,多态性就是让实现与接口进行分离,在这道题目中,在父类的构造方法中调用了虚函数造成多态
package zx.javase.ctrip;

/**
 * @author Carl_Hugo
 * 当创建子类的时候首先会调用构造函数,如没有写明构造函数,编译器则会自动创建无参的构造函数,
 * 并且在执行前首先调用父类的构造函数。
 * 在父类的无参构造函数中又调用了this指向的callName函数输出baseName
 * 当子类和父类有相同的变量时,子类的变量并不会覆盖父类的同名变量,只会隐藏父类的同名变量
 * 输出baseName时只是调用的子类的baseName,而子类的baseName还未被赋值,则输出null
 * 
 */
public class Base {

    private String baseName="base";

    public Base(){
        callName();
    }

    public void callName(){
        System.out.println(baseName);
    }

    static class Sub extends Base{
        private String baseName="sub";
        public void callName(){
            System.out.println(baseName);
        }
    }

    public static void main(String[] args) {
        Base sub = new Sub();
    }

}

输出: null

1.类加载的机制和程序运行的顺序
我们通过 Debug 能很好的了解程序的运行顺序,因为 new 了一个 Sub 对象,且 Sub 类中没有重写构造函数,因此会调用父类的构造函数,父类 Base 的构造函数中调用了 callName 方法。
baseName 的初始化过程

一个类一旦被加载连接初始化,它就可以随时被使用了,程序可以访问它的静态字段,调用静态方法,或者创建它的实例。

实例化有四种途径:

  • (1)明确使用 new 操作符;
  • (2)调用 Class 或者 Constructor 对象的 newInstance() 方法;
  • (3)调用任何现有对象的 clone() 方法
  • (4)或者通过 objectInputStream 类的 getObject() 方法反序列化。虚拟机创建一个新的实例时,都需要在堆中为保存对象的实例分配内存。所有在对象的类中和它的父类中声明的变量(包括隐藏的实例变量)都要分配内存。一旦虚拟机为新的对象准备好堆内存,它立即把实例变量初始化为默认的初始值。

2.继承
Java保证了一个对象被初始化前其父类也必须被初始化。
有下面机制来保证:

Java强制要求任何类的构造函数中的第一句必须是调用父类构造函数或者是类中定义的其他构造函数。如果没有构造函数,系统添加默认的无参构造函数,如果我们的构造函数中没有显示的调用父类的构造函数,那么编译器自动生成一个父类的无参构造函数

3.多态
父类中的构造函数调用了 callName 方法,在题目中是通过 new Sub() 对象,因此调用的是子类 Sub 类中的 callName 方法,因此当前的 this 是指 Sub 类中的。

根据运行顺序分析整个过程
1.Base b = new Sub();
在main方法中声明父类变量b对于子类的引用,JAVA类加载器将BASE和Sub类加载到JVM,完成了Base类和Sub类的初始化。

2.JVM 为 Base,Sub 的的成员开辟内存空间且值均为 null
在初始化sub类之前,JAVA虚拟机就在堆区开辟内存,并将子类Sub中的baseName和父类中的baseName(已被隐藏)均赋值为null(子类继承父类的时候,同名的变量不会被覆盖,只会被隐藏)
3.调用父类的无参构造
调用Sub的构造函数,因为子类没有重写构造函数,默认调用无参的构造函数,调用了super()
4.callName在子类中被重写,因此调用子类的callName()
调用了父类的构造函数,父类的构造函数调用了callName方法,此时父类的baseName值为base,可是子类重写了baseName方法,且调用了父类Base中的callName是在子类Sub中调用的,因此,当前的this指向的是子类,也就是说实现了子类的callName方法。
5.调用子类的callName,打印baseName
实际上在new Sub()时,实际执行过程为:

public Sub(){
    super();
    baseName = "sub"; 
}

可见,在 baseName = “sub” 执行前,子类的 callName() 已经执行,所以子类的 baseName 为默认值状态 null 。

构造器的初始化顺序大概是:父类静态块 ->子类静态块 ->父类初始化语句 ->父类构造函器 ->子类初始化语句 子类构造器

子类的初始化语句在最后执行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值