Effective Java Item 5 note
Avoid creating unnecessary objects
最好能重用對象而不是在每次需要的時候就創建一個相同功能的新對象.
有個極端的錯誤例子,不過應該是不會這樣寫。
String s = new String(“test”);
這行每次被執行的時候都會產生一個新的String object,如果需要大量跑這段程式碼的話,就會多出一堆不必要的String object,而修改後的版本如下:
String s = “test”;
這在Java的規範裡面保證在同一台VM中跑的code,只要他們包含相同的string內容,這個object就會被重用.這是String的不變性(immutable),JVM會將這些String放到String pool裏,別的地方有用到一樣的就會reference它,這想法應該跟底下這種情況要用StringBuilder才不會產生一堆String object有一樣意思.(另外 String Pool裡面也不受Garbage Collector管理)
String test = ‘0’;
for ( int I = 0 ; I < 10 ; I++) {
test += I;
}
如果一個immutable class同時提供了static factory 跟constructor的實例化方式,那最好先優先選用static factory,以避免產生不必要的object。
還有,也可以重用已知不會被修改的可變對象也可以提高效能,書本上的例子放在github( https://bit.ly/2J4RUF0 )上,書上提到跑在作者機器上的兩種寫法可以差距250倍,我自己測試的結果差距到600倍
如果書上提到的static init的方式讓你覺得怪怪的,是有提到說可以用lazily initializing(item 71) 的方式等到function使用到的時候再initialize就好了,就可以不用在一開始就初始化,結果該function並沒有被使用到,但作者不建議這樣做,因為會讓實作更複雜(初步看一下看起來要加上要使用的物件的check,如果沒有該物件要產生出來,產生的function為了避免重複產生物件所以要用synchronized鎖住.)效能也不會有static init那麼好。
此外,前面提到的兩個例子都是初始化之後就不會再改變的object,那如果初始化後會再改變的object呢?
書上提到有兩個例子:
一個是adapter,如果adapter作為一個物件的代理接口,那在這種只有接口後面的物件會改變的情況下,adapter是也不用產生多個實例出來的。
還有Map的keySet method會return該map的全部key值的set,但也是跟adapter一樣的意思,對於返回的object在功能上是相同的,所以也可以不用每次都產生多個實例出來。
在Java 1.5之後,有提供autoboxing的功能,允許primitive type跟Boxed Primitive Type混用,那也要小心如果在大量運算的情況下,不要這兩個type混用。
// 要避免這樣的情況
Long sum = 0L;
for ( long I = 0 ; I < Integer.MAX_VALUE; I++) {
sum += I;
}
當然不是這條就代表100%的產生物件的代價很高,所以要盡可能的避免。相反的,對於一些如果constructor裡面並沒有做太多事情的物件來說,物件的產生跟回收都是非常廉價的,如果這樣可以讓程式碼看起來清楚簡潔的話,那麼也是可以去放心產生物件沒關係。也不一定要對每個物件都去維護自己的object pool,這樣程式碼會變複雜,且也增加內存佔用。但如果產生的物件的成本很高的情況下,應該就要使用object pool來減低產生物件成本的消耗,像是在database裡面很容易看到的connection pool,android如果你要大量背景操作的情況下會使用到的thread pool,等等。
Item 39也有提到另一種情況,總之看起來的感覺就是視情況而定,把重用物件而付出的代價跟產生重複物件的代價比較一下,就可以選擇你想要用怎樣的模式去管理物件。