Java类及其类成员的初始化(二)—类成员的初始化
1. 类成员
类成员只有
成员变量,
构造器,
初始化块,
方法,
内部类5种成员。
注:其中内部类指的是成员内部类,不包括局部内部类和匿名内部类,这里主要研究前三种成员的初始化顺序。
2. 类成员的初始化
在进行类成员的初始化之前,若该类没有初始化过,会先进行类的初始化,详情参见:
Java类及其类成员的初始化(一)—类的初始化
根据上篇结论,静态变量和静态初始化块的初始化在类初始化时就已经完成,且只执行一次。剩下的构造器,实例变量,非静态初始化块都和对象有关,在创建对象时候才会进行初始化,以下进行测试证明。
代码:
package calculate;
/**
* 父类
*/
class SuperClass{
public SuperClass()
{
System.out.println("父类无参构造器");
}
public SuperClass(int a)
{
System.out.println("父类有参构造器");
}
{
System.out.println("父类普通初始化块");
}
static
{
System.out.println("父类静态初始化块");
}
/** 静态成员变量 */
public static String fieldSuperA = printStaticField();
/** 实例变量 */
public String fieldSuperB = printCommonField();
/**
**打印静态成员变量
*/
public static String printStaticField()
{
System.out.println("父类静态成员变量A初始化");
return "我是父类静态成员变量";
}
/**
**打印实例变量
*/
String printCommonField()
{
System.out.println("父类实例变量B初始化");
return "我是父类实例变量";
}
}
/**
* 子类
*/
class SubClass extends SuperClass{
/** 静态成员变量 */
public static String fieldSubA = printStaticField();
/** 实例变量 */
public String fieldSubB = printCommonField();
public SubClass(){
System.out.println("子类的无参构造器");
}
public SubClass(int a){
System.out.println("子类的有参构造器");
}
{
System.out.println("子类普通初始化块");
}
static{
System.out.println("子类静态初始化块");
}
/**
**打印静态成员变量
*/
public static String printStaticField(){
System.out.println("子类静态成员变量A初始化");
return "我是子类静态成员变量";
}
/**
**打印实例变量
*/
String printCommonField(){
System.out.println("子类实例变量B初始化");
return "我是子类实例变量";
}
}
/**
* 类成员初始化顺序测试类
*/
public class MemeberInitializationTest {
public static void main(String[] args) {
//访问子类的静态变量
System.out.println("----------访问子类的静态变量----------");
System.out.println(SubClass.fieldSubA);
//调用有参构造器创建对象
System.out.println("----------调用子类有参构造器创建子类对象----------");
new SubClass(1);
//调用有参构造器创建对象
System.out.println("----------调用子类无参构造器创建子类对象----------");
new SubClass();
}
}
运行结果:
现象与分析:
现象1:程序访问了子类的静态变量(未创建实例)不会引起非静态成员(实例变量,普通初始化块,构造器)(下同)的初始化。
分析1: 静态成员属于类本身,而非静态成员属于类的实例。在没有创建实例的情况下,类的初始化只会引起静态成员的初始化。
现象2:程序访问了一次子类的静态变量,然后又创建了2个子类对象,静态成员(静态成员变量和静态初始化块)(下同)只初始化了一次,非静态成员初始化了两次。
分析2:类的静态成员只伴随着类的初始化时进行一次初始化,创建对象时实际是类实例的初始化,而不是类的初始化。若创建对象之前类未被初始化,则先进行类的初始化。
现象3:同一个类,静态成员,非静态成员初始化的优先级。(数字越小,优先级越高,同级优先级之间根据代码顺序进行初始化)
静态成员变量 | 静态初始化块 | 实例变量 | 普通初始化块 | 构造器 | |
优先级 | 1 | 1 | 2 | 2 | 3 |
现象4:初始化子类之前先进行了父类的初始化。
分析4:若初始化类有父类,则先进行父类的初始化。同一个类中类成员的初始化顺序参照现象3。
现象5:子类使用有参构造器创建对象调用了父类的无参构造器。
分析5:不管子类使用的是无参构造器还是有参构造器最终都会调用父类的无参构造器。
现象6:子类重写了父类的静态和实例方法,但在父类和父类实例的初始化过程中,父类调用的是重写前的静态方法和重写后的实例方法。
准备知识:程序中把一个方法调用和方法主体关联起来称作绑定。根据绑定时间的先后可分为"前期绑定"和"后期绑定"(也称作"动态绑定")。Java中的static方法和final方法(private方法属于final方法)属于"前期绑定",这是默认的绑定方式。 除此以外的其他所有方法均为"动态绑定",含义是在运行时根据对象的实际类型进行绑定,而Java中所有方法都是通过动态绑定来实现多态。
准备知识:程序中把一个方法调用和方法主体关联起来称作绑定。根据绑定时间的先后可分为"前期绑定"和"后期绑定"(也称作"动态绑定")。Java中的static方法和final方法(private方法属于final方法)属于"前期绑定",这是默认的绑定方式。 除此以外的其他所有方法均为"动态绑定",含义是在运行时根据对象的实际类型进行绑定,而Java中所有方法都是通过动态绑定来实现多态。
分析6:根据准备知识得知父类调用的静态方法是前期绑定的,是其自身的方法。而这里的实例方法不是final方法,是动态绑定的,用到的方法就应该是那个被覆盖后的方法。如果此处父类的实例方法用private或者final修饰,则属于前期绑定,调用的是父类本身的实例方法。
总结:
一般来说类成员初始化的顺序如下:
1. 判断是否进行过类及其父类的初始化,若没有则执行类的初始化(静态成员的初始化),从父类开始,若父类还有父类,从最顶层开始初始化。
2. 类实例的初始化(非静态成员的初始化),从最顶层父类的实例进行初始化(先进行普通初始化块和实例变量的初始化(根据代码顺序),最后执行构造器的初始化),依次往下。