前几天去参加了场笔试,里面考了静态构造函数,当时没做出来,现在对静态成员的初始化做一个总结。
在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中正常实例化顺序是:父类实例化 -> 成员变量 -> 构造函数。加入静态类型后会先出示话所有的静态类型(包括父类和子类的),然后才是正常的初始化:父类类静态类型 -> 子类静态类型 -> 父类正常实例化 -> 成员变量 -> 构造函数。静态类型的初始化顺序都是先变量后程序块。