对象实例化过程
Posted yuancoco
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对象实例化过程相关的知识,希望对你有一定的参考价值。
一个实例对象的创建包括:类初始化和实例初始化
1. 一个类要创建实例需要先加载并初始化该类,main方法所在的类需要先加载和初始化
2. 一个子类要初始化需要先初始化父类
3. 一个类初始化就是执行<clinit>()方法
<clinit>方法由静态变量显示赋值代码和静态代码块组成
类静态变量显示赋值代码和静态代码块代码从上到下顺序执行
<clinit>方法只执行一次
4.实例初始化就是执行<init>方法
<init>方法由非静态实例变量显示赋值代码和非静态代码块,对应构造器代码组成。
非静态实例变量显示赋值代码和非静态代码块从上到下顺序执行,而对应构造器代码最后执行
每次创建实例对象,调用对应的构造器,执行的就是对应的<init>方法
<init>方法的首行是super()或super(实参列表),即对应父类的<init>方法
类加载过程(类初始化):
1、类加载就是执行Java程序编译之后在字节码文件中生成的clinit()方法(称之为类构造器),clinit()方法由静态变量和静态代码块组成。
2、子类的加载首先需要先加载父类,如果父类为接口。则不会调用父类的clinit方法。一个类中可以没有clinit方法。
3、clinit方法中的执行顺序为:父类静态变量初始化,父类静态代码块,子类静态变量初始化,子类静态代码块。
4、clinit()方法只执行一次。
对象实例化过程(实例初始化):
1、对象实例化过程就是执行Java程序编译之后在字节码文件中生成的init()方法(称之为实例构造器),init()方法由非静态变量、非静态代码块以及对应的构造器组成。
2、init()方法可以重载多个,有几个构造器就有几个init()方法,每次创建实例,调用哪一个构造器,就会调用相应的init()方法。
3、init()方法中的执行顺序为:父类变量初始化,父类代码块,父类构造器,子类变量初始化,子类代码块,子类构造器。
clinit()方法优先于init()方法执行,所以整个顺序就是:
父类静态变量初始化,父类静态代码块,子类静态变量初始化,子类静态代码块,父类非静态变量初始化,父类非静态代码块,父类构造器,子类非静态变量初始化,子类非静态代码块,子类构造器。
类加载时只会加载静态代码块和静态变量。只有创建对象的实例时: 变量和初始化代码块和构造函数才会执行(创建对象实例会先触发类加载)
类加载:执行<clinit>
创建对象实例:执行<<init>,但是创建类实例之前会先类加载执行<clinit>。
类初始化:执行<clinit>方法,<clinit>方法由静态变量显示赋值代码和静态代码块组成,<clinit>方法只执行一次
实例初始化:执行<init>方法,<init>方法由非静态实例变量显示赋值代码和非静态代码块,对应构造器代码组成。
触发类加载:
1. 为一个类型创建一个新的对象实例时(比如new、反射、序列化)
只有这个会触发 变量和初始化代码块和构造函数执行,这个是实例初始化,其他6个都是类初始化
2. 调用一个类型的静态方法时(即在字节码中执行invokestatic指令)
3. 调用一个类型或接口的静态字段,或者对这些静态字段执行赋值操作时(即在字节码中,执行getstatic或者putstatic指令),
不过用final修饰的静态字段已经赋值了(String和基本类型,不包含包装类型)的除外,它被初始化为一个编译时常量表达式
4. 调用JavaAPI中的反射方法时(比如调用java.lang.Class中的方法(Class.forName),或者java.lang.reflect包中其他类的方法)
5. 初始化一个类的派生类时(Java虚拟机规范明确要求初始化一个类时,它的超类必须提前完成初始化操作,接口例外)
6. JVM启动包含main方法的启动类时。
7. 对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。
加载顺序: 静态变量和静态初始化块是依照他们在类中的定义顺序进行初始化的。同样,变量和初始化块也遵循这个规律。
1. 父类--静态变量
2. 父类--静态初始化块
3. 子类--静态变量
4. 子类--静态初始化块
5. 父类--变量
6. 父类--初始化块
7. 父类--构造器
8. 子类--变量
9. 子类--初始化块
10. 子类--构造器
多态
方法重写@Override
哪些方法不可以被重写
1、final方法
2、静态方法
3、private等子类中不可见的方法
对象的多态性
1、子类如果重写了父类的方法,通过子类对象调用的一定是子类重写过的代码
2、非静态方法默认的调用对象是this
3、this对象在构造器或者说<init>方法中就是正在创建的对象,所以通过子类实例化时触发的父类加载调用的非静态方法,
调用对象this是子类对象,调用的方法是子类重写的方法。
子类实例化时如果有父类,会触发父类加载 在初始化过程中如果父类方法被子类重写了,会调用子类的方法。
通过父类的引用类型变量指向子类类型对象,访问成员变量时是访问的父类的成员变量。
父类代码:
/** * 父类的初始化<clinit> * 1、j=method() * 2、父类的静态代码块 * * 父类的实例方法: * 1、super()最前 * 2、i = test(); * 3、父类的非静态代码块 * 4、父类的无参构造(最后) * * * 非静态方法前面其实有一个默认的对象this * this在构造器或者<init>,它表示的是正在创建的对象,因为这里是在创建Son对象,所以 * test()执行的是子类重写的代码(面向对象多态) * 这里 i=test()执行的是子类重写的test()方法 * @author yuan * @date 2019/11/23 */ public class Father { /** * i=test()执行的是子类重写的test,因为是子类实例化触发的父类加载 */ private int i = test(); private static int j = method(); static { System.out.print("(1)"); } public Father() { System.out.print("(2)"); } { System.out.print("(3)"); } public int test() { System.out.print("(4)"); return 1; } public static int method(){ System.out.print("(5)"); return 1; } public static void main(String[] args) throws Exception { new Father().test(); } }
子类代码:
/** * 子类的初始化<clinit> * 1、j=method() * 2、父类的静态代码块 * * 先初始化父类:(5),(1) * 初始化子类:(10),(6) * * 子类的实例方法<init>: * 1、super()最前,super代表父类初始化,不是创建父类实例,是加载父类 * 2、i = test(); * 3、子类的非静态代码块 * 4、子类的无参构造(最后) * * 因为创建了两个Son对象,因此实例方法<init>执行了两次,但是<clinit>方法只会执行一次 * * @author yuan * @date 2019/11/23 */ public class Son extends Father { private int i = test(); private static int j = method(); static { System.out.print("(6)"); } public Son() { //super(); 写或不写都存在,在子类构造器中一定会调用父类的构造器 System.out.print("(7)"); } { System.out.print("(8)"); } @Override public int test() { System.out.print("(9)"); return 1; } public static int method() { System.out.print("(10)"); return 1; } public static void main(String[] args) throws Exception { Son s = new Son(); System.out.println(); Son son = new Son(); } }
输出:
(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)
以上是关于对象实例化过程的主要内容,如果未能解决你的问题,请参考以下文章