享元模式通過區分內部狀態(Internal State)與外部狀態(External State),實現對象共享:
String a = "hello";
String b = "hello";
System.out.println(a == b); // true:指向同一對象,享元模式實踐
JVM 確保字符串字面量在常量池中唯一,極大節省記憶體。
問題:文章包含成千上萬字符,若每個字符都是獨立對象,記憶體壓力極大。
解決:
char code(內部狀態)。// 1. 享元對象:字母
public class Letter {
private final char code; // 內部狀態:不可變
public Letter(char c) { this.code = c; }
public char getCode() { return code; }
@Override public String toString() { return "Letter@" + Integer.toHexString(hashCode()) + "['" + code + "']"; }
}
// 2. 享元工廠(單例)
public class LetterFactory {
private static final LetterFactory INSTANCE = new LetterFactory();
private final Map pool = new ConcurrentHashMap<>();
private LetterFactory() {}
public static LetterFactory getInstance() { return INSTANCE; }
public Letter getLetter(char c) {
return pool.computeIfAbsent(c, Letter::new); // 雙重檢查鎖定模式隱含
}
public int poolSize() { return pool.size(); }
}
// 3. 非共享享元:文本行(組合字符享元)
public class TextLine {
private final List letters = new ArrayList<>();
private final Map styles = new HashMap<>(); // 外部狀態:位置->樣式
public void addLetter(char c) { letters.add(LetterFactory.getInstance().getLetter(c)); }
public void setStyle(int index, FontStyle style) { styles.put(index, style); }
public void render(Graphics2D g, int x, int y) {
for (int i = 0; i < letters.size(); i++) {
FontStyle fs = styles.getOrDefault(i, FontStyle.DEFAULT);
g.setFont(fs.font); g.setColor(fs.color);
g.drawString(String.valueOf(letters.get(i).getCode()), x, y);
x += fs.fontMetrics.charWidth(letters.get(i).getCode());
}
}
}
// 4. 客戶端測試
public class Test {
public static void main(String[] args) {
LetterFactory factory = LetterFactory.getInstance();
String text = "easiness";
// 模擬構建文本行
TextLine line = new TextLine();
for (char c : text.toCharArray()) line.addLetter(c);
// 設置部分字符樣式(外部狀態)
line.setStyle(0, new FontStyle(new Font("微軟雅黑", Font.BOLD, 24), Color.RED));
line.setStyle(3, new FontStyle(new Font("Consolas", Font.ITALIC, 18), Color.BLUE));
System.out.println("享元池大小: " + factory.poolSize()); // 輸出 5 (e,a,s,i,n)
System.out.println("字符對象: " + line.letters); // 顯示共享對象引用
}
}
享元池大小: 5 字符對象: [Letter@...['e'], Letter@...['a'], Letter@...['s'], Letter@...['i'], Letter@...['n'], Letter@...['e'], Letter@...['s'], Letter@...['s']]
8 個字符位置,僅創建 5 個字符享元對象,e、s 重複複用。
ConcurrentHashMap 或雙重檢查鎖定。