這個坑是關於單例模式 getInstance時候的static final順序,而且問提十分隱蔽。在實際的項目,在QA環境下由於判斷條件為false,無法發現此問題;而到生產上之后,判斷條件為true,拋了空指針異常。
情況1:判斷條件為false
1 package com.paypal.dinyu; 2 3 /** 4 * Created by dinyu on 26/07/2017. 5 */ 6 public class Bpp { 7 private static final Bpp INSTANCE = new Bpp(); 8 private static final Cpp CPP = Cpp.getInstance(); 9 10 private Bpp() { 11 init(); 12 } 13 14 public static Bpp getInstance() { 15 return INSTANCE; 16 } 17 18 private void init() { 19 if (false) { 20 System.out.println("CPP.toString: " + CPP.toString()); 21 } 22 } 23 24 public static void main(String[] args) { 25 Bpp.getInstance(); 26 } 27 } 28 29 class Cpp { 30 private static final Cpp INSTANCE = new Cpp(); 31 32 private Cpp() { 33 34 } 35 36 public static Cpp getInstance() { 37 return INSTANCE; 38 } 39 }
Output:
Process finished with exit code 0
情況2:判斷條件為true
1 package com.paypal.dinyu; 2 3 /** 4 * Created by dinyu on 26/07/2017. 5 */ 6 public class Bpp { 7 private static final Bpp INSTANCE = new Bpp(); 8 private static final Cpp CPP = Cpp.getInstance(); 9 10 private Bpp() { 11 init(); 12 } 13 14 public static Bpp getInstance() { 15 return INSTANCE; 16 } 17 18 private void init() { 19 if (true) { 20 System.out.println("CPP.toString: " + CPP.toString()); 21 } 22 } 23 24 public static void main(String[] args) { 25 Bpp.getInstance(); 26 } 27 } 28 29 class Cpp { 30 private static final Cpp INSTANCE = new Cpp(); 31 32 private Cpp() { 33 34 } 35 36 public static Cpp getInstance() { 37 return INSTANCE; 38 } 39 }
Output:
Exception in thread "main" java.lang.ExceptionInInitializerError
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:123)
Caused by: java.lang.NullPointerException
at com.paypal.dinyu.Bpp.init(Bpp.java:20)
at com.paypal.dinyu.Bpp.<init>(Bpp.java:11)
at com.paypal.dinyu.Bpp.<clinit>(Bpp.java:7)
... 3 more
Process finished with exit code 1
出現以上情況的原因,是因為在Bpp.getInstace()時,在Bpp中new了自己,而在new的過程中又需要用到Cpp的Instance,但是Cpp的static塊在Bpp的new之后,也就是在Bpp new自己的時候,Cpp還是null,所以會拋出NPE;但是在情況1中,由於判斷條件為false,並沒有跑到使用CPP的地方,所以不會拋出NPE。
正確做法:把new Bpp放到所有static塊的最后
1 package com.paypal.dinyu; 2 3 /** 4 * Created by dinyu on 26/07/2017. 5 */ 6 public class Bpp { 7 private static final Cpp CPP = Cpp.getInstance(); 8 private static final Bpp INSTANCE = new Bpp(); 9 10 private Bpp() { 11 init(); 12 } 13 14 public static Bpp getInstance() { 15 return INSTANCE; 16 } 17 18 private void init() { 19 if (true) { 20 System.out.println("CPP.toString: " + CPP.toString()); 21 } 22 } 23 24 public static void main(String[] args) { 25 Bpp.getInstance(); 26 } 27 } 28 29 class Cpp { 30 private static final Cpp INSTANCE = new Cpp(); 31 32 private Cpp() { 33 34 } 35 36 public static Cpp getInstance() { 37 return INSTANCE; 38 } 39 }
輸出結果:
CPP.toString: com.paypal.dinyu.Cpp@63947c6b
Process finished with exit code 0