Effective Java Item 3 note
Enforce the singleton property with a private constructor or an enum type
什麼是singleton,我自己的理解是在application的生命週期中,只存在這唯一的一份instance的class。
本書上有提到兩種initial singleton的方式
1. instance是public final field的
public class test {
public static final test INSTANCE = new test();
private test() {
}
}
好處是可以很明確就知道這個class是個singleton,書上有提到,現在的JVM都有實現把static factory method inline化,所以在效能上的優勢已經不存在了.
2. instance是public with static factory的
(如果在android studio裡面,可以透過快捷鍵方式產生出來的singleton就是長底下這個樣子)
第一個好處就是如果在不改變API的情況下,我們可以自己掌握這個class不是singleton.第二個好處則為item 27.
但書上表示public field的方式(第一種)比較簡單.(雖然android studio的內建是static factory)
public class test {
private static final test ourInstance = new test();
public static test getInstance() {
return ourInstance;
}
private test() {
}
}
這兩種方式都會在這個class第一次初始化的時候產生出instance,且正常的情況下是不會產生出第二個的.那書上有提到如果有privilege的client可以透過reflection的方式從AccessibleObject.setAccessible去調用private constructor,如果要防止這種使用,可以在constructor的地方確認在已經存在一個instance的情況下要產生第二個instance就丟出exception
像是:
private test() {
if(ourInstance!=null){
throw new IllegalArgumentException("no no no ");
}
}
就可以阻止底下這段想要拿到該singleton的程式碼
test obj1=test.getInstance();
test obj2=test.getInstance();
Class<test> clazz=test.class;
Constructor<test> c= null;
try {
c = clazz.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
c.setAccessible(true);
try {
c.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
不過我有點訝異在android上面setAccessible是可以用的,看起來不用其他權限
(題外話1,有看到一篇blog寫到https://blog.csdn.net/xiangwanpeng/article/details/53220158
這觀點也蠻有趣的
題外話2,從這篇stackoverflow https://stackoverflow.com/questions/13211369/protection-from-reflection-android 的回答應該可以得出我的一個疑問,為什麼android上面可以用setAccessible,這裡面的回答是跟系統的設計有關,一般的JVM通常是跑在一個系統的process上,你沒有辦法掌控別的process跑的是什麼,所以需要這樣的防護,但在android上面的設計,基本上你的process沒有辦法讀取到別的process的內容,所以這在根本上就阻擋掉這樣的讀取,並不用在程式裡面特別去做一些保護,那如果你的城市裡面開了這個洞,也就怪不了別人,自行負責XD,不過也不至於影響到別的process就是了
)
另一個議題就是要怎麼讓singleton可以被serializable呢?(item 77)
先初步看一下Item 77裡面有提到,當一個class 去implement Serializable就不會是singleton了,所以要利用readResolve的method去提供原本的物件,但這種寫法還是會被item 76裡面提到的mutablePeriod攻擊所破解,所以還是建議以第三種方式Enum來建立singleton class,細節看到item 77的時候會再做連結.
3. 是可以用Enum來建立singleton(從java 1.5開始才support),這個寫法跟第一種的public field的方式相近,但更簡潔,也提供serializable的功能,可以防止被多次實例化.
同樣的也可以解決前面提到的reflection的攻擊.( 會出現java.lang.NullPointerException: Attempt to invoke virtual method ‘void java.lang.reflect.Constructor.setAccessible(boolean)’ on a null object reference )
public enum test {
INSTANCE;
public void addTest() {
}
}
對了最後順道一提,千萬不要這樣建立singleton class,這不是thread safe!!!小心建立出來不是singleton(誤
public Singleton getInstance(){
if(s==null)
return new Singleton();
else return s;
}
順道一提2
在查item4的資料的時候,剛好看到一篇建議不要使用singleton的原因,看起來應該是基於OO的概念上,不推薦使用這種global性質的class,然後會很不好寫測試程式.
https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons