静态成员的初始化顺序

2012年10月20日 分类:学习笔记C#Java

前几天去参加了场笔试,里面考了静态构造函数,当时没做出来,现在对静态成员的初始化做一个总结。
在c#类中的静态成员有静态变量、静态函数和静态构造函数,而在java中是没有静态构造函数的,取而代之的是静态程序块。静态成员一般存放在静态区,而且是属于类的,所以我们可以不用实例化对象,直接调用静态函数,比如工具类的方法一般都声明为静态函数。c#和java对静态成员的初始化顺序是不一样的,下面我将分别对他们进行总结。

1.c#中静态成员的初始化顺序

为了更好的说明,我写了一个测试程序,为了让变量在初始化时有打印信息,我用成员函数对他们赋值,程序如下:

class A
{
    static int a = setA();//静态变量
    int a1 = setA1();//非静态变量

    private static int setA1()
    {
        Console.WriteLine("父类非静态变量");
        return 1;
    }

    public static int setA()
    {
        Console.WriteLine("父类静态变量");
        return 1;
    }

    public A()//构造函数
    {
        Console.WriteLine("父类构造函数");
    }

    static A()//静态构造函数
    {
        Console.WriteLine("父类静态构造函数");
    }
}

class B : A
{
    static int b = setB();//静态变量
    int b1 = setB1();//非静态变量

    private static int setB1()
    {
        Console.WriteLine("子类非静态变量");
        return 1;
    }

    public static int setB()
    {
        Console.WriteLine("子类静态变量");
        return 1;
    }

    public B()//构造函数
    {
        Console.WriteLine("子类构造函数");
    }

    static B()//静态构造函数
    {
        Console.WriteLine("子类静态构造函数");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("第一次调用。。。");
        B b = new B();
        Console.WriteLine("第二次调用。。。");
        b = new B();
    }
}

在上面我定义了一个父类A和一个子类B,再让子类实例化两次,并打印出初始化信息,结果如下:

第一次调用。。。
子类静态变量
子类静态构造函数
子类非静态变量
父类静态变量
父类静态构造函数
父类非静态变量
父类构造函数
子类构造函数
第二次调用。。。
子类非静态变量
父类非静态变量
父类构造函数
子类构造函数

从这里我们可以看到,静态变量和静态构造函数只会在类的第一次实例化时进行初始化,第二次就是正常的初始化了。在正常实例化中,初始化的顺序是:成员变量 -> 父类实例化 -> 构造函数。如果有静态类型的话,就会先初始化静态类型,于是顺序就变成了:静态变量 -> 静态构造函数 -> 成员变量 -> 父类实例化 -> 构造函数。在父类实例化中,顺序也是这样的。

1.1.一道笔试题

class A
{
    public static int X;
    static A()
    {
        X = B.Y + 1;
    }
}
class B
{
    public static int Y = A.X + 1;
    static B() { }
    static void Main()
    {
        Console.WriteLine("X={0},Y={1}", A.X, B.Y);
    }
}

程序会从B类中的Main()开始执行,所以先初始化静态变量Y,而Y要调用A.X,调用A类静态构造函数A(),此时B.Y未初始化默认为0,所以X=1,再回到B的静态变量初始化中Y就是2了。初始化完成后,进入Main(),打印X=1,Y=2。意外吧!

2.java中静态成员的初始化顺序

同样的,只不过静态构造函数用静态程序块代替。

public class Main {

 public static void main(String[] args) {
 	System.out.println("第一次调用。。。");
 	B b = new B();
 	System.out.println("第二次调用。。。");
 	b = new B();
 }

}

class A {
 private static int a = setA();
 private int a1 = setA1();

 public static int setA() {
 	System.out.println("父类静态变量");
 	return 1;
 }

 private int setA1() {
 	System.out.println("父类非静态变量");
 	return 0;
 }

 public A() {
 	System.out.println("父类构造函数");
 }

 static {
 	System.out.println("父类静态程序块");
 }
}

class B extends A {
 private static int b = setB();
 private int b1 = setB1();

 private static int setB1() {
 	System.out.println("子类非静态变量");
 	return 1;
 }

 public static int setB() {
 	System.out.println("子类静态变量");
 	return 1;
 }

 public B() {
 	System.out.println("子类构造函数");
 }

 static {
 	System.out.println("子类静态程序块");
 }
}

运行结果如下:

第一次调用。。。
父类静态变量
父类静态程序块
子类静态变量
子类静态程序块
父类非静态变量
父类构造函数
子类非静态变量
子类构造函数
第二次调用。。。
父类非静态变量
父类构造函数
子类非静态变量
子类构造函数

和c#的一样,只在第一次实例化时调用,但是初始化顺序却不一样。在java中正常实例化顺序是:父类实例化 -> 成员变量 -> 构造函数。加入静态类型后会先出示话所有的静态类型(包括父类和子类的),然后才是正常的初始化:父类类静态类型 -> 子类静态类型 -> 父类正常实例化 -> 成员变量 -> 构造函数。静态类型的初始化顺序都是先变量后程序块。

3.总结

c#的初始化顺序看起来比较有规律,父类在子类中初始化,先静态后常规;而java则是先初始化全部静态类型(先父后子),再父类实例化,最后子类。

作者:wuyuan 本文来自Wuyuan's Blog 转载请注明,谢谢! 文章地址: https://wuyuans.com/2012/10/static-initialization