一、享元模式的定义与实现
- 定义:运用共享技术来有効地支持大量细粒度对象的复用,通过共享对象减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
- 享元模式中存在以下两种状态
内部状态:即不会随着环境的改变而改变的可共享部分;
外部状态:指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。下面来分析其基本结构和实现方法。
二、享元模式的示例
文本编辑器的享元模式
文档编辑中,文字的属性有字体格式、大小、颜色和字符内容。
- 采用享元模式前
public class Character {//文字
private char c;
private Font font;
private int size;
private int colorRGB;
public Character(char c, Font font, int size, int colorRGB) {
this.c = c;
this.font = font;
this.size = size;
this.colorRGB = colorRGB;
}
}
public class Editor {
private List<Character> chars = new ArrayList<>();
public void appendCharacter(char c, Font font, int size, int colorRGB) {
Character character = new Character(c, font, size, colorRGB);
chars.add(character);
}
}
- 采用享元模式,共享字体格式
public class CharacterStyle { // 字体类
private Font font;
private int size;
private int colorRGB;
public CharacterStyle(Font font, int size, int colorRGB) {
this.font = font;
this.size = size;
this.colorRGB = colorRGB;
}
@Override
public boolean equals(Object o) {
CharacterStyle otherStyle = (CharacterStyle) o;
return font.equals(otherStyle.font)
&& size == otherStyle.size
&& colorRGB == otherStyle.colorRGB;
}
}
public class CharacterStyleFactory { // 采用共享模式
private static final List<CharacterStyle> styles = new ArrayList<>(); // 共享字体类
public static CharacterStyle getStyle(Font font, int size, int colorRGB) {
CharacterStyle newStyle = new CharacterStyle(font, size, colorRGB);
for (CharacterStyle style : styles) {
if (style.equals(newStyle)) {
return style;
}
}
styles.add(newStyle);
return newStyle;
}
}
public class Character {
private char c;
private CharacterStyle style;
public Character(char c, CharacterStyle style) {
this.c = c;
this.style = style;
}
}
public class Editor {
private List<Character> chars = new ArrayList<>();
public void appendCharacter(char c, Font font, int size, int colorRGB) {
Character character = new Character(c, CharacterStyleFactory.getStyle(font, size, colorRGB));
chars.add(character);
}
}
三、JDK中的享元模式
包装器类型应用享元模式,java中的基本类型和包装类型,如下图所示。

基本类型和包装类型.png
所谓的自动装箱,就是自动将基本数据类型转换为包装器类型。所谓的自动拆箱,也就是自动将包装器类型转化为基本数据类型。
Integer i = 56; //自动装箱 底层执行Integer i = Integer.valueOf(59);
int j = i; //自动拆箱 底层执行了:int j = i.intValue();
分析下面代码的执行结果
Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2); // true
System.out.println(i3 == i4); // false
思考:为什么前者为true,后者为false?
解答:Integer类中IntegerCache内部类,缓存-128~127之间的整数。源码如下图所示
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
总结:类似Integer,Long、Float等包装类型也有LongCache、FloatCache等内部类,采用享元模式实现对象复用、节省内存。
说明:String类与包装类型的享元模式有点不相同。由于字符串内容无法在String实例化的时候知道要共享哪些字符,所以没法提前创建好字符。因此,某个字符常量第一次使用的时候,保存到运行时常量池,后续再用到的时候,直接从常量池中获取该字符串。
四、享元模式的总结
- 享元模式的原理:复用对象,节省内存,前提是享元对象是不可变对象。
- 实现思路:当一个系统中存在大量重复对象的时候,可以利用享元模式,将对象设计成享元,在内存中只保留一份实例,供多处代码引用,这样可以减少内存中对象的数量,以起到节省内存的目的。
- 享元模式对比单例、缓存、对象池
享元模式是为了实现对象复用,节省内存。单例模式是为了保证对象全局唯一。缓存是为了提高访问效率,而非复用。池化技术中的“复用”理解为“重复使用”,主要是为了节省时间。