首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

減少 Java 臨時物件建立,從設計階段提升效能

     編輯:Java 相關知識

許多 Java 效能問題的種子,早在類設計階段就已埋下。Brian Goetz 指出:大量短命臨時物件會拖垮 GC、增加記憶體壓力,且往往源於介面設計不當。本文整理三大減少物件建立的實戰技巧,助你在寫下第一行程式碼前就避開效能陷阱。

為什麼臨時物件會傷效能?

  • GC 壓力:短命物件填滿 Young Generation,觸發頻繁 Minor GC。
  • 記憶體頻寬:分配、初始化、複製、回收都消耗頻寬。
  • 快取污染:大量臨時物件擠走熱點資料,降低 CPU 快取命中率。

元兇:String 介面設計

經典反面教材 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[],完全避免中間物件。

技巧 1:提供基本型別存取子

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 膨脹,但效能敏感場合值得。

技巧 2:利用可變物件「傳入重用」

為相容舊 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 等不可變類別無法使用。

技巧 3:不可變介面 + 可變內部實作

對外暴露不可變介面(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 加速大整數運算。

不可變 vs 可變:效能權衡

結語

效能優化不應犧牲可讀性與維護性,但設計階段的介面選擇往往決定物件分配量級。記住三原則:

  1. 熱點路徑優先用基本型別/陣列,避免物件包裝。
  2. 必要時提供「傳入重用」多載方法。
  3. 對外不可變、內部可變,兼顧封裝與效能。

在寫下 public String foo(String...) 之前,先問自己:這裡真的需要新物件嗎?

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
特性不可變可變
共享安全性天然執行緒安全,可自由共用引用需防禦性複製或同步,否則易產生副作用
修改成本每次修改需建立新物件就地修改,零分配
適用場景值物件、金鑰、並發共享資料高頻更新的內部狀態、效能熱點