1. 程式人生 > >靜態成員的初始化順序(C#,java)

靜態成員的初始化順序(C#,java)

前幾天去參加了場筆試,裡面考了靜態建構函式,當時沒做出來,現在對靜態成員的初始化做一個總結。
在c#類中的靜態成員有靜態變數、靜態函式和靜態建構函式,而在java中是沒有靜態建構函式的,取而代之的是靜態程式塊。靜態成員一般存放在靜態區,而且是屬於類的,所以我們可以不用例項化物件,直接呼叫靜態函式,比如工具類的方法一般都宣告為靜態函式。c#和java對靜態成員的初始化順序是不一樣的,下面我將分別對他們進行總結。

1.c#中靜態成員的初始化順序

為了更好的說明,我寫了一個測試程式,為了讓變數在初始化時有列印資訊,我用成員函式對他們賦值,程式如下:
  1. class A
  2. {
  3. staticint a = setA
    ();//靜態變數
  4. int a1 = setA1();//非靜態變數
  5. privatestaticint setA1()
  6. {
  7. Console.WriteLine("父類非靜態變數");
  8. return1;
  9. }
  10. publicstaticint setA()
  11. {
  12. Console.WriteLine("父類靜態變數");
  13. return1;
  14. }
  15. public A()//建構函式
  16. {
  17. Console.WriteLine("父類建構函式");
  18. }
  19. static A()//靜態建構函式
  20. {
  21. Console.WriteLine("父類靜態建構函式");
  22. }
  23. }
  24. class B : A
  25. {
  26. staticint b = setB();//靜態變數
  27. int b1 = setB1();//非靜態變數
  28. privatestaticint setB1()
  29. {
  30. Console.WriteLine("子類非靜態變數");
  31. return1;
  32. }
  33. publicstaticint setB()
  34. {
  35. Console.WriteLine("子類靜態變數");
  36. return1;
  37. }
  38. public B()//建構函式
  39. {
  40. Console.WriteLine("子類建構函式");
  41. }
  42. static B()//靜態建構函式
  43. {
  44. Console.WriteLine("子類靜態建構函式");
  45. }
  46. }
  47. classProgram
  48. {
  49. staticvoidMain(string[] args)
  50. {
  51. Console.WriteLine("第一次呼叫。。。");
  52. B b
    =new B();
  53. Console.WriteLine("第二次呼叫。。。");
  54. b =new B();
  55. }
  56. }

在上面我定義了一個父類A和一個子類B,再讓子類例項化兩次,並打印出初始化資訊,結果如下:

  1. 第一次呼叫。。。
  2. 子類靜態變數
  3. 子類靜態建構函式
  4. 子類非靜態變數
  5. 父類靜態變數
  6. 父類靜態建構函式
  7. 父類非靜態變數
  8. 父類建構函式
  9. 子類建構函式
  10. 第二次呼叫。。。
  11. 子類非靜態變數
  12. 父類非靜態變數
  13. 父類建構函式
  14. 子類建構函式

從這裡我們可以看到,靜態變數和靜態建構函式只會在類的第一次例項化時進行初始化,第二次就是正常的初始化了。在正常例項化中,初始化的順序是:成員變數 -> 父類例項化 -> 建構函式。如果有靜態型別的話,就會先初始化靜態型別,於是順序就變成了:靜態變數 -> 靜態建構函式 -> 成員變數 -> 父類例項化 -> 建構函式。在父類例項化中,順序也是這樣的。

1.1.一道筆試題

  1. class A
  2. {
  3. publicstaticint X;
  4. static A()
  5. {
  6. X = B.Y +1;
  7. }
  8. }
  9. class B
  10. {
  11. publicstaticint Y = A.X +1;
  12. static B(){}
  13. staticvoidMain()
  14. {
  15. Console.WriteLine("X={0},Y={1}", A.X, B.Y);
  16. }
  17. }

程式會從B類中的Main()開始執行,所以先初始化靜態變數Y,而Y要呼叫A.X,呼叫A類靜態建構函式A(),此時B.Y未初始化預設為0,所以X=1,再回到B的靜態變數初始化中Y就是2了。初始化完成後,進入Main(),列印X=1,Y=2。意外吧!

2.java中靜態成員的初始化順序

同樣的,只不過靜態建構函式用靜態程式塊代替。
  1. publicclassMain{
  2. publicstaticvoid main(String[] args){
  3. System.out.println("第一次呼叫。。。");
  4. B b =new B();
  5. System.out.println("第二次呼叫。。。");
  6. b =new B();
  7. }
  8. }
  9. class A {
  10. privatestaticint a = setA();
  11. privateint a1 = setA1();
  12. publicstaticint setA(){
  13. System.out.println("父類靜態變數");
  14. return1;
  15. }
  16. privateint setA1(){
  17. System.out.println("父類非靜態變數");
  18. return0;
  19. }
  20. public A(){
  21. System.out.println("父類建構函式");
  22. }
  23. static{
  24. System.out.println("父類靜態程式塊");
  25. }
  26. }
  27. class B extends A {
  28. privatestaticint b = setB();
  29. privateint b1 = setB1();
  30. privatestaticint setB1(){
  31. System.out.println("子類非靜態變數");
  32. return1;
  33. }
  34. publicstaticint setB(){
  35. System.out.println("子類靜態變數");
  36. return1;
  37. }
  38. public B(){
  39. System.out.println("子類建構函式");
  40. }
  41. static{
  42. System.out.println("子類靜態程式塊");
  43. }
  44. }

執行結果如下:

  1. 第一次呼叫。。。
  2. 父類靜態變數
  3. 父類靜態程式塊
  4. 子類靜態變數
  5. 子類靜態程式塊
  6. 父類非靜態變數
  7. 父類建構函式
  8. 子類非靜態變數
  9. 子類建構函式
  10. 第二次呼叫。。。
  11. 父類非靜態變數
  12. 父類建構函式
  13. 子類非靜態變數
  14. 子類建構函式

和c#的一樣,只在第一次例項化時呼叫,但是初始化順序卻不一樣。在java中正常例項化順序是:父類例項化 -> 成員變數 -> 建構函式。加入靜態型別後會先出示話所有的靜態型別(包括父類和子類的),然後才是正常的初始化:父類類靜態型別 -> 子類靜態型別 -> 父類正常例項化 -> 成員變數 -> 建構函式。靜態型別的初始化順序都是先變數後程序塊。

3.總結

c#的初始化順序看起來比較有規律,父類在子類中初始化,先靜態後常規;而java則是先初始化全部靜態型別(先父後子),再父類例項化,最後子類。