建立不必要的物件的情況
使用 new String()
字串 a、b、c 都具有「hi」字串。但是,這三個字串所參考的地址都不同,因此會發生針對相同資料分配不同記憶體的浪費。
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fa53d14aa-6abf-40f0-8441-435647d172fa%2FUntitled.png?table=block&id=f168859c-367c-4924-a4c6-5e04788fab67&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/1146;" width="2000" height="1146"></span>
因此,宣告字串時,不應使用 new 關鍵字,而應使用文字宣告。
上述程式碼只使用一個執行個體。更進一步地說,如果使用這種方式,則保證在相同的 JVM 中使用「hi」字串文字的所有程式碼都會重複使用相同的物件。這是由於 Java 常數池的特性。
使用 new Boolean()
上述程式碼透過接收字串作為參數的建構函式來建立 Boolean 執行個體。Boolean 只有 true 或 false,每次建立執行個體都是記憶體浪費。因此,最好使用靜態工廠方法 Boolean.valueOf()。
使用 String.matches()
如果建立成本很高,最好快取並重複使用,但我們並非總是能知道我們建立的物件的成本。例如,如果想要撰寫一個方法來檢查給定的字串是否為有效的羅馬數字,那麼使用正則表達式是最簡單的方法。
但是,String.matches() 是一個效能上有問題的方法。這個方法在內部建立的正則表達式 Pattern 執行個體會被使用一次後就丟棄,很快就會成為垃圾回收的目標,如果該正則表達式重複使用的頻率很高,則建立和丟棄相同 Pattern 執行個體的成本就會增加。因此,最好事先快取 Pattern 執行個體,並在稍後每次呼叫 isRomanNumeral() 方法時重複使用這個執行個體。
注意事項
在上述所有範例中,快取不必要的物件時,都將其設為不可變物件。這是因為這樣才能安全地重複使用。但是,在某些情況下,重複使用不可變物件會與直覺相違背。
Adapter(檢視)是一種物件,它將實際作業委派給後端物件,而自身則充當第二個介面。Adapter 只需要管理後端物件,因此,每個後端物件只需要建立一個 Adapter 即可。
例如,Map 介面的 keySet() 方法會傳回包含 Map 物件中所有金鑰的 Set 檢視。使用者可能會認為每次呼叫 keySet() 方法時都會建立一個新的 Set 執行個體,但實際上,如果查看 JDK 的實作內容,會發現每次都傳回相同的可變 Set 執行個體。
這是因為即使傳回的 Set 執行個體是可變的,但其執行的功能都是相同的,而且所有 Set 執行個體都代表 Map 執行個體。因此,即使 keySet() 建立多個檢視物件也沒有關係,但也沒有必要也沒有好處。
因此,如果修改 names1 執行個體,names2 執行個體也會受到影響。
但是,我個人認為 keySet() 方法的傳回值應該使用防禦性複製來每次都傳回新的物件。如果使用 keySet() 方法接收的 Set 執行個體也在其他地方使用,並且存在更改此執行個體狀態的程式碼,那麼就無法確定目前正在使用的 Set 執行個體和 Map 執行個體的值。
此外,除非是在過度使用 keySet() 的環境中,否則 Set 介面每次建立都不會對效能造成致命影響。我認為,最好將 Set 介面設為不可變物件,以確保其穩定性和可維護性。
自動裝箱
自動裝箱是一種技術,當程式設計師混合使用基本類型和包裝器類型時,它會自動在它們之間進行相互轉換。但是,自動裝箱只會模糊基本類型和包裝器類型的區別,而不會完全消除它。
邏輯上沒有問題,但效能非常低效。其原因在於 sum 的類型和 for 迴圈內 i 的類型。
sum 的類型是 Long 類型,i 是 long 類型。也就是說,long 類型的 i 在迴圈中每次加到 sum 時,都會建立一個新的 Long 執行個體。結果是,應該使用基本類型而不是包裝器類型,並且要注意避免無意中使用自動裝箱。
不要誤解的部分
不要誤解避免建立不必要的物件的意思,以為只是因為建立物件的成本很高所以才需要避免。
特別是在當今的 JVM 中,建立和回收不必要的小物件並不太令人擔憂。因此,除非是資料庫連線等成本非常高的物件,否則不要建立自訂物件池。
更進一步地說,在需要防禦性複製的情況下重複使用物件所造成的損害遠大於重複建立不需要的物件所造成的損害。重複建立的副作用只會影響程式碼形式和效能,但如果防禦性複製失敗,則會直接導致錯誤和安全問題。
评论0