构造函数的理解和程序执行流程

来源:互联网 发布:免费 流程图制作软件 编辑:程序博客网 时间:2024/05/21 10:38


i.构造函数概述:
   构造函数是对象被创建的时候提供的一种特殊的数据结构,这里需要明白的是构造函数本身不是函数。它具有和它所在的类完全一样的名字。一旦定义好一个构造 函数,该对象在被实例化的过程会自动调用它,而构造函数本身没有任何返回类型,不能错误地认为构造函数的返回类型是void,构造函数返回值的类型就是该 类本身。在初始化过程,构造函数的任务就是将一个对象的内部状态进行初始化,从内存结构上讲,一旦一个对象通过构造函数初始化结束过后,我们就通过构造函 数拿到了一个清楚、可用的对象,该对象的数据结构以及存储模型在这个时候是可以被JVM清晰识别的。
  构造函数在源代码级别我们可以认为是一个特殊的方法,构造方法有时候我们又称为一个类的构造子,其特征如下:
  [1]构造函数的方法名必须与类名相同
  [2]构造函数没有返回类型,也不能使用void作为返回类型,在方法名前面不能生命方法类型
  [3]构造函数的作用是为了完成对象初始化,它能够把定义对象时的参数传给对象
  [4]构造函数不能由代码本身像其他方法一样进行调用,只能JVM本身在初始化一个对象的时候自行调用
  [5]一个类可以有多个构造函数,构造函数同样支持重载,重载法则遵循函数的重载法则
  [6]如果没有定义任何构造函数系统会默认一个无参数的构造函数,此函数什么也不做
  ii.JVM类加载器如何加载.class文件:
   从底层看来,JVM本身就是以Java字节码为指令组成的抽象CPU,在服务端或者本地由开发人员先编译出.class文件,然后放在服务器上或者本 地,然后在客户端远程调用或者直接从本地系统进行调用。可以这样理解,当一个Java程序启动的时候,JVM本身的实例就诞生了,而该JVM实例的运行起 点就是我们经常写的程序的入口:
  public static void main(String args[])
   这里有一点需要理解的概念是,虽然我们经常将上边这句代码写入一个public class的类里面,实际上这个程序入口和代码里面包含它的类没有直接的关系,这个程序入口放入某个类里面的目的只是为了JVM能够找到该程序的入口,并 不是说这个函数也属于某个,这点我们可以用最简单的代码来证明,也就是说,即使类不存在,这个函数入口也是有效的,一旦JVM找到了某个类里面的函数入口,就会将该class作为JVM实例运行的起点来对待,这个起点也就是我们平时所说的程序入口。所以当我们用java命令输入:java Helloworld的时候,实际上JVM是在寻找Helloworld.class文件里面是否存在一个函数入口,入口定义如上边这段代码所写。
  JVM实例运行起来过后:main会作为该程序初始线程的起点,也就是说任何其他的线程都必须由该线程来启动。
  JVM内部有两种线程:守护线程和非守护线程【这 里不做这方面详细介绍,只需要理解的是main属于非守护线程,守护线程一般是由JVM自己使用,java程序本身也可以标明自己创建的线程是守护线程。 当程序中所有非守护线程都终止的时候,JVM实例就消失了,然后JVM才会退出。在JVM的安全管理允许的范围内,我们也可以使用 System.exit()来退出,只是这样的退出方式是要受JVM安全管理器的控制的,我们平时写代码一般都是在本机运行,调用的代码都是本地代码,所 以不会存在安全问题,如果.class文件是存在于服务端,在执行该代码的时候需要签名】
  当JVM实例在寻找函数入口过程,会自己启动内部的ClassLoader,也就是平时我们所说的类加载器,类加载器会按照类加载原理把运行环境下所有编译好的类按需要加载进来【*:有些类会延迟加载】。
  然后JVM开始按照main函数里面的代码顺序进行.class文件的执行
  iii.对象初始化流程:
  我们根据一段代码来分析对象初始化流程:
/**
 *基类包含一静态变量、包含一实例变量
 *包含一个静态初始化块以及一个构造子
 */
class Base{
    public static int a = 10;

    public int b = 20;

    static

    {

        System.out.println("Static Init Base " + a);

        //System.out.println("Null Init " + b);

    }

    public Base()

    {

        System.out.println("Init Base " + this.b);

    }

}
/**
 *一级子类和基类包含的内容一样
 **/
class SuperClass extends Base{
    public static int a1 = getSuperStaticNumber();

    public int b1 = getSuperInstanceNumber();

    public SuperClass()

    {

        System.out.println("Init SuperClass" + this.b1);

    }

    static

    {

        System.out.println("Static Init SuperClass" + a1);

    }

    public static int getSuperStaticNumber()
    {

        System.out.println("Static member init");

        return 100;

    }

    public int getSuperInstanceNumber()

    {

        System.out.println("Instance member init");

        return 200;

    }
}
/**
 *二级子类为测试该代码的驱动类
 */
public class SubClass extends SuperClass{
    public static int a2 = getStaticNumber();

    public int b2 = getInstanceNumber();

    public SubClass()

    {

        System.out.println("Init SubClass " + this.b2);

    }

    public static int getStaticNumber()

    {

        System.out.println("Static member init Sub");

        return 1000;

    }

    public int getInstanceNumber()

    {

        System.out.println("Instance member init Sub");

        return 2000;

    }

    public static void main(String args[])

    {

        new SubClass();

    }

    static

    {
        System.out.println("Static Init " + a2);
    }

}
  这段代码会有以下输出:
Static Init Base 10
Static member init
Static Init SuperClass 100
Static member init Sub
Static Init 1000
Init Base 20
Instance member init
Init SuperClass 200
Instance member init Sub
Init SubClass 2000
  [1]对象在初始化过程,JVM会先去搜索该类的顶级父类,直到搜索到我们所定义的SubClass继承树上直接继承于Object类的子类,在这里就是Base类;
  [2]然后JVM会先加载Base类,然后初始化Base类的静态变量a,然后执行Base类的静态初始化块,按照这样第一句话会输出:Static Init Base 10【*:此时该类还未调用构造函数,构造函数是实例化的时候调用的】
  [3]然后JVM按照继承树往下搜索,继续加载Base类的子类,按照静态成员函数->静态成员变量->静态初始化块的顺序往下递归,直到加载完我们使用的对象所在的类。
  [4]类加载完了过后开始对类进行实例化操作,这个过程还是会先搜索到直接继承于Object类的子类,在这里就是Base类;
  [5]JVM会实例化Base类的成员函数,然后实例化成员变量,最后调用Base类的构造函数;
  [6]之后,JVM会递归往继承树下边进行调用,顺序还是遵循:成员函数->成员变量->构造函数;
  [7]最后直到SubClass类的构造函数调用完成
  按照上边书写的逻辑,我们就很清楚了上边源代码的执行结果,而整个JVM初始化某个类的流程就是按照以上逻辑进行
  在构造函数调用过程,有几点是需要我们留意的,这里就不提供代码实例,有兴趣的朋友可以自己去试试
  [1]如果一个类的父类没有无参数构造函数,也就是说父类自定义了一个带参数的构造函数,那么系统不会提供无参数构造函数,此时子类在调用构造函数的时候必须最开始显示调用super(param),因为在构造函数调用之前系统总会先去调用父类的构造函数
  [2]若一个类定义的时候没有提供构造函数,JVM会自动为该类定义一个无参数的构造函数
  [3]一个类在调用构造函数的时候,JVM隐藏了一句代码super(),前提是父类未定义构造函数或者显示定义了无参构造函数;其含义就是调用父类的构造函数,如果父类的无参数构造函数被覆盖的话需要在子类构造函数中显示调用父类带参数的构造函数
   [4]当类中的成员函数遇到变量的时候,会先根据变量名在函数域即局部变量范围内去寻找该变量,如果找不到才会去寻找实例变量或者静态变量,其意思可以 理解为局部变量可以和实例变量或者静态变量同名,而且会在函数调用过程优先使用,这个原因在于在函数范围内,如果调用的变量是实例变量,其中前缀this.被隐藏了。
  以上的流程和法则需要多写点代码来进行检测,因为还会出现疑惑的地方,这个在后边教程慢慢讲解。