未涉及虛擬機
引子
記得上次中秋一哥們寫個需求,沒寫完。他中秋過后還請一天假,有點錯,打電話叫我幫他繼續搞。
由於測試支撐,叫到我加班了。第二天過來看,打開頁面直接報錯,再次點擊報錯就不一樣了。前次報錯是有代碼行的,第二次直接頁面說類發現什么的錯。
看了下代碼,類似如下:
1 package san; 2 3 import java.io.FileNotFoundException; 4 import java.util.logging.Level; 5 import java.util.logging.Logger; 6 7 import javax.xml.bind.JAXBContext; 8 import javax.xml.bind.annotation.XmlElement; 9 import javax.xml.bind.annotation.XmlRootElement; 10 11 //每個類有一個Log4j的靜態日志成員 12 //這里是單例 13 public class ClassWithStatic { 14 private static ClassWithStatic instance = new ClassWithStatic(); 15 private static Logger logger = Logger.getLogger(ClassWithStatic.class.getName()); 16 17 private ClassWithStatic() { 18 JAXBContext jc; 19 try { 20 //do something that maybe throw IOExption; 21 throw new FileNotFoundException(); 22 } catch (Exception e) { 23 logger.log(Level.ALL, "xxx", e); 24 } 25 } 26 27 /** 28 * @return the instance 29 */ 30 public static ClassWithStatic getInstance() { 31 return instance; 32 } 33 34 public void doSomeThing() { 35 System.out.println("doSomeThing"); 36 } 37 38 public static void main(String[] args) { 39 ClassWithStatic.getInstance().doSomeThing(); 40 } 41 } 42 43 @XmlRootElement(name = "Scenes") 44 class Scenes{ 45 @XmlElement(name = "id", required = true) 46 protected String id; 47 48 /** 49 * @return the id 50 */ 51 public String getId() { 52 return id; 53 } 54 55 /** 56 * @param id the id to set 57 */ 58 public void setId(String id) { 59 this.id = id; 60 } 61 62 }
報錯
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.NullPointerException
at san.ClassWithStatic.<init>(ClassWithStatic.java:21)
at san.ClassWithStatic.<clinit>(ClassWithStatic.java:12)
這是和靜態成員初始化順序有關。
基礎知識如下:
Java代碼中一個類初始化順序:static變量 -- 其他成員變量 -- 構造函數 三者的調用先后順序:
初始化父類Static --> 子類的Static (如果是類實例化,接下來還會: 初始化父類的其他成員變量->父類構造方法->子類其他成員變量->子類的構造方法)。
系統默認值的給予比通過等號的賦予先執行。
一個類中的static變量或成員變量的初始化順序,是按照聲明的順序初始化的。
測試類
1 public class ClassWithStatic extends Super{ 2 private static int iTest0 = 0; 3 private static ClassWithStatic instance = new ClassWithStatic(); 4 private static int iTest1; 5 private static int iTest2 = 0; 6 static { 7 System.out.println(ClassWithStatic.class.getName() + " : static{}"); 8 iTest0++; 9 iTest1++; 10 iTest2++; 11 } 12 13 public ClassWithStatic() { 14 System.out.println(this.getClass().getName() + " : Constuctor."); 15 iTest0++; 16 iTest1++; 17 iTest2++; 18 } 19 20 /** 21 * @return the instance 22 */ 23 public static ClassWithStatic getInstance() { 24 return instance; 25 } 26 27 public void doSomeThing() { 28 System.out.println("iTest0 = " + iTest0); 29 System.out.println("iTest1 = " + iTest1); 30 System.out.println("iTest2 = " + iTest2); 31 } 32 33 public static void main(String[] args) { 34 //private static void main(String[] args) 35 //Error: Main method not found in class san.ClassWithStatic, please define the main method as: 36 // public static void main(String[] args) 37 // or a JavaFX application class must extend javafx.application.Application 38 System.out.println("public static void main(String[] args)"); 39 ClassWithStatic.getInstance().doSomeThing(); 40 41 } 42 } 43 44 class Super { 45 private static Super instance = new Super(); 46 private static int iTest1; 47 private int iTest2 = 0; 48 static { 49 System.out.println(Super.class.getName() + " : static{}"); 50 iTest1++; 51 } 52 53 public Super() { 54 System.out.println(this.getClass().getName() + " : Constuctor."); 55 iTest2++; 56 } 57 }
結果:
san.Super : Constuctor.
san.Super : static{}
san.ClassWithStatic : Constuctor.
san.ClassWithStatic : Constuctor.
san.ClassWithStatic : static{}
public static void main(String[] args)
iTest0 = 2
iTest1 = 2
iTest2 = 1
這里兩遍子類構造,為了區分,改一下構造函數里的打印語句代碼。
1 public class ClassWithStatic extends Super{ 2 private static int iTest0 = 0; 3 private static ClassWithStatic instance = new ClassWithStatic(); 4 private static int iTest1; 5 private static int iTest2 = 0; 6 static { 7 System.out.println(ClassWithStatic.class.getName() + " : static{}"); 8 iTest0++; 9 iTest1++; 10 iTest2++; 11 } 12 13 public ClassWithStatic() { 14 System.out.println(ClassWithStatic.class.getName() + " : Constuctor with this = " + this); 15 iTest0++; 16 iTest1++; 17 iTest2++; 18 } 19 20 /** 21 * @return the instance 22 */ 23 public static ClassWithStatic getInstance() { 24 return instance; 25 } 26 27 public void doSomeThing() { 28 System.out.println("iTest0 = " + iTest0); 29 System.out.println("iTest1 = " + iTest1); 30 System.out.println("iTest2 = " + iTest2); 31 } 32 33 public static void main(String[] args) { 34 //private static void main(String[] args) 35 //Error: Main method not found in class san.ClassWithStatic, please define the main method as: 36 // public static void main(String[] args) 37 // or a JavaFX application class must extend javafx.application.Application 38 System.out.println("public static void main(String[] args)"); 39 ClassWithStatic.getInstance().doSomeThing(); 40 41 } 42 } 43 44 class Super { 45 private static Super instance = new Super(); 46 private static int iTest1; 47 private int iTest2 = 0; 48 static { 49 System.out.println(Super.class.getName() + " : static{}"); 50 iTest1++; 51 } 52 53 public Super() { 54 System.out.println(Super.class.getName() + " : Constuctor with this = " + this); 55 iTest2++; 56 } 57 }
結果:
san.Super : Constuctor with this = san.Super@15db9742
san.Super : static{}
san.Super : Constuctor with this = san.ClassWithStatic@6d06d69c
san.ClassWithStatic : Constuctor with this = san.ClassWithStatic@6d06d69c
san.ClassWithStatic : static{}
public static void main(String[] args)
iTest0 = 2
iTest1 = 2
iTest2 = 1
public class ClassWithStatic extends Super { protected static int iTest0 = Super.iTest0 + 1; private static ClassWithStatic instance = new ClassWithStatic(); protected static int iTest1; private static int iTest2 = 0; static { System.out.println(ClassWithStatic.class.getName() + " : static{}"); iTest1++; iTest2++; } public ClassWithStatic() { System.out.println(ClassWithStatic.class.getName() + " : Constuctor with this = " + this); iTest1++; iTest2++; } /** * @return the instance */ public static ClassWithStatic getInstance() { return instance; } public void doSomeThing() { System.out.println("ClassWithStatic.iTest0 = " + iTest0); System.out.println("ClassWithStatic.iTest1 = " + iTest1); System.out.println("ClassWithStatic.iTest2 = " + iTest2); } public static void main(String[] args) { //private static void main(String[] args) //Error: Main method not found in class san.ClassWithStatic, please define the main method as: // public static void main(String[] args) // or a JavaFX application class must extend javafx.application.Application System.out.println("public static void main(String[] args)"); ClassWithStatic.getInstance().doSomeThing(); System.out.println("Super.iTest0 = " + Super.iTest0); System.out.println(Const.constanceString);//對類的靜態變量進行讀取、賦值操作的。static,final且值確定是常量,是編譯時確定的,調用的時候直接用,不會加載對應的類 System.out.println("------------------------"); Const.doStaticSomeThing(); } } class Super { protected static int iTest0; private static Super instance = new Super(); protected static int iTest1 = 0; static { System.out.println(Super.class.getName() + " : static{}"); iTest0 = ClassWithStatic.iTest0 + 1;//1 } public Super() { System.out.println(Super.class.getName() + " : Constuctor with this = " + this + ", iTest0 = " + iTest0); iTest1++; } } class Const { public static final String constanceString = "Const.constanceString"; static { System.out.println(Const.class.getName() + " : static{}"); } public static void doStaticSomeThing() { System.out.println(Const.class.getName() + " : doStaticSomeThing();"); } }
san.Super : Constuctor with this = san.Super@15db9742, iTest0 = 0
san.Super : static{}
san.Super : Constuctor with this = san.ClassWithStatic@6d06d69c, iTest0 = 1
san.ClassWithStatic : Constuctor with this = san.ClassWithStatic@6d06d69c
san.ClassWithStatic : static{}
public static void main(String[] args)
ClassWithStatic.iTest0 = 2
ClassWithStatic.iTest1 = 2
ClassWithStatic.iTest2 = 1
Super.iTest0 = 1
Const.constanceString
------------------------
san.Const : static{}
san.Const : doStaticSomeThing();
0、<init>與<clinit>的區別
其實:
在編譯生成class文件時,會自動產生兩個方法,一個是類的初始化方法<clinit>, 另一個是實例的初始化方法<init>
<clinit>:在jvm第一次加載class文件時調用,包括靜態變量初始化語句和靜態塊的執行
<init>:在實例創建出來的時候調用,包括調用new操作符;調用Class或java.lang.reflect.Constructor對象的newInstance()方法;調用任何現有對象的clone()方法;通過java.io.ObjectInputStream類的getObject()方法反序列化。
簡要說明下final、static、static final修飾的字段賦值的區別:
- static修飾的字段在類加載過程中的准備階段被初始化為0或null等默認值,而后在初始化階段(觸發類構造器<clinit>)才會被賦予代碼中設定的值,如果沒有設定值,那么它的值就為默認值。
- final修飾的字段在運行時被初始化(可以直接賦值,也可以在實例構造器中賦值),一旦賦值便不可更改;
- static final修飾的字段在Javac時生成ConstantValue屬性,在類加載的准備階段根據ConstantValue的值為該字段賦值,它沒有默認值,必須顯式地賦值,否則Javac時會報錯。可以理解為在編譯期即把結果放入了常量池中。
1、類的加載過程
類加載的時機就很簡單了:在用到的時候就加載(和系統內存管理差不多,一個進程都是寫時復制CopyOnWrite)。下來看一下類加載的過程:
加載->驗證->准備->解析->初始化->使用->卸載
所有的Java虛擬機實現必須在每個類或接口被Java程序 “首次主動使用”時才初始化他們。
有必要提一點的是:准備和初始化
准備
准備階段是正式為類變量分配內存並設置類變量初始值的階段,這些內存都將在方法區中分配。對於該階段有以下幾點需要注意:
1、這時候進行內存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨着對象一塊分配在Java堆中。
2、這里所設置的初始值通常情況下是數據類型默認的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。
假設一個類變量的定義為:
public static int value = 3;
那么變量value在准備階段過后的初始值為0,而不是3,因為這時候尚未開始執行任何Java方法,而把value賦值為3的putstatic指令是在程序編譯后,存放於類構造器<clinit>()方法之中的,所以把value賦值為3的動作將在初始化階段才會執行。
詳細查看:【深入Java虛擬機】之四:類加載機制 http://blog.csdn.net/ns_code/article/details/17881581
2、類的使用方式
Java 程序對類的使用方式可分為兩種 :
主動 |
創建類的實例 |
遇到 |
Test a = new Test(); |
訪問某個類或接口的非編譯期靜態變量, |
讀寫某個類的靜態變量 |
||
調用類的靜態方法 |
調用某個類的靜態方法 Test.doSomething(); |
||
反射 |
Class.forName("xxxx") |
||
初始化一個類的子類(不是對父類的主動使用就初始化子類, |
Child.class、Parent.class,初始化Child時, |
||
Java虛擬機啟動時被標明為啟動類的類 |
就是main方法那個所在類 |
||
被動 |
除了以上六種情況,其他使用Java類的方式都被看作是對 |
||
•主動使用(六種) – 創建類的實例 -------Test a = new Test(); – 訪問某個類或接口的非編譯期靜態變量,或者對該非編譯期靜態變量賦值 -------讀寫某個類的靜態變量 int b = a.staticVariable;或a.staticVariable=b; – 調用類的靜態方法 -------調用某個類的靜態方法 Test.doSomething(); – 反射(如 Class.forName (“ com.shengsiyuan.Test ”) ) -------比如Class.forName("xxxx"); – 初始化一個類的子類(不是對父類的主動使用就初始化子類,這樣的話生成一個Object類,那豈不是每個類都要初始化) -------Child.class、Parent.class,初始化Child時,就是對Parent的主動使用,先初始化父類 – Java虛擬機啟動時被標明為啟動類的類( Java Test ) -------就是main方法那個所在類
•被動使用
除了以上六種情況,其他使用Java類的方式都被看作是對類的被動使用,都不會導致類的初始化
前3個可以歸一類:遇到new、getstatic、putstatic、invokestatic這四條字節碼指令時,如果類還沒有進行過初始化,則需要先觸發其初始化。
主要說下開始:當jvm啟動時,用戶需要指定一個要執行的主類(包含static void main(String[] args)的那個類),則jvm會先去初始化這個類。
友鏈:【深入Java虛擬機】之三:類初始化 http://blog.csdn.net/ns_code/article/details/17845821
3、類的加載來源
• 類的加載指的是將類的 .
class
文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然后在堆區創建一個 java.lang.Class 對象,用來封裝類在方法區內的數據結構
• 加載 .
class
文件的方式
– 從本地系統中直接加載
– 通過網絡下載 .
class
文件(URLClassLoader)
– 從 zip、jar 等歸檔文件中加載 .
class
文件
– 從專有數據庫中提取 .
class
文件
– 將 Java源文件動態編譯為 .
class
文件
4、重載之泛型參數不同可以嗎
泛型編譯后是一樣的類型。
java5后新特性方便了書寫,字面誤導,需要深刻了解一下class。
//返回值參數重載嗎 public class Overload { public int method(List<String> ls){ return 0; } /** * Method method(List<Integer>) has the same erasure method(List<E>) as another method in type Demo.Overload */ public boolean method(List<Integer> li){ return false; } /** * 會有問題嗎,編譯后是什么樣的呢。。。 */ public int method(Integer s){ return s; } public int method(String i){ return 0; } }
重載條件:
在Java語言中,要重載一個方法,除了要與原方法具有相同的簡單名稱外,還要求必須擁有一個與原方法不同的(不包含返回值的)特征簽名,特征簽名就是一個方法中各個參數在常量池中的字段符號引用的集合,也就是因為返回值不會包含在特征簽名之中,因此Java語言里無法僅僅依靠返回值的不同來對一個已有方法進行重載。
如果進行過Android的native編程,便會非常熟悉這種簽名,因為Android源碼中可以看到。
用方法描述符描述方法時,按照先參數后返回值的順序描述,參數要按照嚴格的順序放在一組小括號內,如方法 int getIndex(String name,char[] tgc,int start,int end,char target)的描述符為“(Ljava/lang/String[CIIC)I”。
重載調用選擇:
另外,根據參數選擇重載方法時,是靜態時需要確定的,即編譯后就確定。
參考:http://blog.csdn.net/ns_code/article/details/17965867
public class Demo { /** * I am human * I am human */ public static void main(String[] args){ Demo sp = new Demo(); Human man = sp.new Man(); Human woman = sp.new Woman(); sp.say(man); sp.say(woman); } class Human{ } class Man extends Human{ } class Woman extends Human{ } public void say(Human hum){ System.out.println("I am human"); } public void say(Man hum){ System.out.println("I am man"); } public void say(Woman hum){ System.out.println("I am woman"); } }
5、參考:
http://www.cnblogs.com/tianchi/archive/2012/11/11/2761631.html
http://www.cnblogs.com/o-andy-o/archive/2013/06/06/3120298.html
http://blog.csdn.net/ns_code/article/details/17675609