熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

逐步深入剖析java類的構造方式

2022-06-13   來源: Java核心技術 

  概要本文通過查看一個精心構造的類結構的運行輸出和使用javap工具查看實際生成的java字節碼(bytecode)向java程序員展示了一個類在運行時是如何構造生成的
  
  關鍵字 java 構造 javap 字節碼 bytecode
  按照java規范一個類實例的構造過程是遵循以下順序的
  
  如果構造方法(constructor也有翻譯為構造器和構造函數的)是有參數的則進行參數綁定
  
  內存分配將非靜態成員賦予初始值(原始類型的成員的值為規定值例如int型為float型為fboolean型為false對象類型的初始值為null)靜態成員是屬於類對象而非類實例所以類實例的生成不進行靜態成員的構造或者初始化後面將講述靜態成員的生成時間
  
  如果構造方法中存在this()調用(可以是其它帶參數的this()調用)則執行之執行完畢後進入第步繼續執行如果沒有this調用則進行下一步
  
  執行顯式的super()調用(可以是其它帶參數的super()調用)或者隱式的super()調用(缺省構造方法)此步驟又進入一個父類的構造過程並一直上推至Object對象的構造
  
  執行類申明中的成員賦值和初始化塊
  
  執行構造方法中的其它語句
  
  現在來看看精心構造的一個實例
  
  class Parent
  {
  int pm;
  int pm=;
  int pm=pmethod();
  {
  Systemoutprintln(Parents instance initialize block);
  }
  public static int spm=;
  static
  {
  Systemoutprintln(Parents static initialize block);
  }
  
  Parent()
  {
  Systemoutprintln(Parents default constructor);
  }
  static void staticmethod()
  {
  Systemoutprintln(Parents staticmethod);
  }
  int pmethod()
  {
  Systemoutprintln(Parents method);
  return ;
  }
  }
  
  class Child extends Parent
  {
  int cm;
  int cm=;
  int cm=cmethod();
  Other co;
  public static int scm=;
  {
  Systemoutprintln(Childs instance initialize block);
  }
  static
  {
  Systemoutprintln(Childs static initialize block);
  }
  
  Child()
  {
  co=new Other();
  Systemoutprintln(Childs default constructor);
  }
  Child(int m)
  {
  this();
  cm=m;
  Systemoutprintln(Childs selfdefine constructor);
  }
  static void staticmethod()
  {
  Systemoutprintln(Childs staticmethod);
  }
  
  int cmethod()
  {
  Systemoutprintln(Childs method);
  return ;
  }
  
  }
  
  class Other
  {
  int om;
  Other() {
  Systemoutprintln(Others default constructor);
  }
  
  }
  public class InitializationTest
  {
  public static void main(String args[])
  {
  Child c;
  Systemoutprintln(program start);
  Systemoutprintln(Childscm);
  c= new Child();
  Systemoutprintln(program end);
  }
  }
  
  進入此文件所在的目錄然後
  編譯此文件javac InitializationTestjava
  運行此程序java ?classpath InitializationTest
  得到的結果是
  
  program start
  Parents static initialize block
  Childs static initialize block
  
  Parents method
  Parents instance initialize block
  Parents default constructor
  Childs method
  Childs instance initialize block
  Others default constructor
  Childs default constructor
  Childs selfdefine constructor
  program end
  
  如果沒有看過上面的關於類的構造的說明很容易讓人誤解為類的構造順序是如下的結果(忽略參數綁定內存分配和非靜態成員的缺省值賦值)
  
  完成父類的非靜態成員初始化賦值以及執行初始化塊(這個的先後順序取決於源文件中的書寫順序可以將初始化塊置於成員聲明前那麼先執行的將是初始化塊將上面的代碼稍稍變動一下就可以驗證這一點
  
  調用父類的構造方法完成父類構造
  
  完成非靜態成員的初始化賦值以及執行初始化塊
  
  調用構造方法完成對象的構造執行構造方法體中的其它內容
  
  如果根據以上java規范中給出的順序也可以合理的解釋程序的輸出結果那麼如何親眼看到是規范中的順序而不是以上根據程序的輸出推斷的順序呢?
  下面就使用JDK自帶的javap工具看看實際的順序這個工具是一個根據編譯後的字節碼生成一份字節碼的助記符格式的文檔的工具就像根據機器碼生成匯編代碼那樣
  反編譯javap c classpath Child
  輸出的結果是(已經經過標記交替使用黑體和斜體表示要講解的每一塊)
  
  Compiled from InitializationTestjava
  class Child extends Parent {
  int cm;
  int cm;
  int cm;
  Other co;
  public static int scm;
  static {};
  Child();
  Child(int);
  int cmethod();
  static void staticmethod();
  }
  
  Method static {}
   bipush
   putstatic #
   getstatic #
   ldc #
   invokevirtual #
   return
  
  Method Child()
   aload_
   invokespecial #
   aload_
   bipush
   putfield #
   aload_
   aload_
   invokevirtual #
   putfield #
   getstatic #
   ldc #
   invokevirtual #
   aload_
   new #
   dup
   invokespecial #
   putfield #
   getstatic #
   ldc #
   invokevirtual #
   return
  
  Method Child(int)
   aload_
   invokespecial #
   aload_
   iload_
   putfield #
   getstatic #
   ldc #
   invokevirtual #
   return
  
  Method int cmethod()
   getstatic #
   ldc #
   invokevirtual #
   iconst_
   ireturn
  
  Method void staticmethod()
   getstatic #
   ldc #
   invokevirtual #
   return
  
  請仔細浏覽一下這個輸出並和源代碼比較一下
  
  下面解釋如何根據這個輸出得到類實例的實際的構造順序在開始說明前先解釋一下輸出的語句的格式語句中最前面的一個數字是指令的偏移值這個我們在此可以不管第二項是指令助記符可以從字面上大致看出指令的意思
  
  例如 getstatic 指令將一個靜態成員壓入一個稱為操作數堆棧(後續的指令就可以引用這個數據結構中的成員)的數據結構而 invokevirtual 指令是調用java虛擬機方法第三項是操作數(#號後面跟一個數字實際上是類的成員的標記)有些指令沒有這一項因為有些指令如同匯編指令中的某些指令一樣是不需要操作數的(可能是操作數是隱含的或者根本就不需要)這是java中的一個特色
  
  如果你直接檢查字節碼你會看到成員信息沒有直接嵌入指令而是像所有由java類使用的常量那樣存儲在一個共享池中將成員信息存儲在一個常量池中可以減小字節碼指令的大小因為指令只需要存儲常量池中的一個索引而不是整個常量
  
  需要說明的是常量池中的項目的順序是和編譯器相關的因此在你的環境中看到的可能和我上面給出的輸出不完全一樣第四項是對前面的操作數的說明實際的字節碼中也是沒有的根據這個你能很清楚的得到實際上使用的是哪個成員或者調用的是哪個方法這也是javap為我們提供的便利
  
  說完上面這些你現在應該很容易看懂上面的結果和下面將要敘述的
From:http://tw.wingwit.com/Article/program/Java/hx/201311/25958.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.