許多 Java 效能問題的種子,早在類設計階段就已埋下。Brian Goetz 指出:大量短命臨時物件會拖垮 GC、增加記憶體壓力,且往往源於介面設計不當。本文整理三大減少物件建立的實戰技巧,助你在寫下第一行程式碼前就避開效能陷阱。
經典反面教材 BadRegExpMatcher:
public class BadRegExpMatcher {
public BadRegExpMatcher(String regExp);
public String match(String inputText); // 回傳新 String
}
每次呼叫 match() 至少產生兩個 String 物件(參數 + 回傳值),高頻呼叫時效能崩盤。
改良版 BetterRegExpMatcher 以基本型別與陣列取代 String:
class BetterRegExpMatcher {
public int match(String inputText);
public int match(char[] inputText);
public int match(char[] inputText, int offset, int length);
public int getMatchLength();
public String getMatchText(); // 僅在需要時建立 String
}
呼叫端可重用 char[],完全避免中間物件。
Swing 早期版本回傳 Rectangle、Point 等小物件,導致大量臨時分配。解法:新增 getX()、getY()、getWidth()、getHeight() 等 int 存取子。
// 呼叫端不再需要建立 Rectangle
int x = component.getX();
int y = component.getY();
int w = component.getWidth();
int h = component.getHeight();
此優化讓 Swing 許多操作快近兩倍。代價是 API 膨脹,但效能敏感場合值得。
為相容舊 API,提供 getBounds(Rectangle dest) 讓呼叫端重用同一 Rectangle 實例:
public Rectangle getBounds(Rectangle returnVal) {
returnVal.x = myBounds.x;
returnVal.y = myBounds.y;
returnVal.width = myBounds.width;
returnVal.height = myBounds.height;
return returnVal;
}
呼叫端在迴圈中只建立一次 Rectangle,反覆重用。注意:此技巧僅適用可變物件,String 等不可變類別無法使用。
對外暴露不可變介面(Point),內部持有可變子類別(MutablePoint):
public class Point {
protected int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
public final int getX() { return x; }
public final int getY() { return y; }
}
public class MutablePoint extends Point {
public final void setX(int x) { this.x = x; }
public final void setY(int y) { this.y = y; }
}
public class Shape {
private MutablePoint myLocation = new MutablePoint(0, 0);
public Point getLocation() { return myLocation; } // 安全回傳不可變視圖
}
Point,無法修改內部狀態(編譯期保護)。MutablePoint,可高效就地更新座標,零分配。java.math.BigInteger 即採此模式,內部用 MutableBigInteger 加速大整數運算。| 特性 | 不可變 | 可變 |
|---|---|---|
| 共享安全性 | 天然執行緒安全,可自由共用引用 | 需防禦性複製或同步,否則易產生副作用 |
| 修改成本 | 每次修改需建立新物件 | 就地修改,零分配 |
| 適用場景 | 值物件、金鑰、並發共享資料 | 高頻更新的內部狀態、效能熱點 |