1. 引入
享元模式简单的说其实就是: 完成缓冲池的创建,来达到缓冲池对象里面复用的功能;
1.1 源码引入
下面代码执行后答案是什么?
public class FlyweightTest {
public static void main(String[] args) {
Integer a = Integer.valueOf(127);
Integer b = new Integer(127);
System.out.println(a == b);
int c = 127;
System.out.println(a == c);
System.out.println(b == c);
}
}
答案是 false true true
1.1.2 Integer、new Integer() 和 int 比较的面试题
基本概念的区分:
1、Integer 是 int 的包装类,int 则是 java 的一种基本数据类型
2、Integer 变量必须实例化后才能使用,而int变量不需要
3、Integer 实际是对象的引用,当new一个 Integer时,实际上是生成一个指针指向此对象;而 int 则是直接存储数据值
4、Integer的默认值是null,int的默认值是0
1.2 Integer,new Integer() 和 int 的比较解答
两个 new Integer() 变量比较 ,永远是 false
因为new生成的是两个对象,其内存地址不同
Integer i = new Integer(100);
Integer j = new Integer(100);
System.out.print(i == j); //false
Integer变量 和 new Integer() 变量比较 ,永远为 false。
因为 Integer变量 指向的是 java 常量池 中的对象,
而 new Integer() 的变量指向 堆中 新建的对象,两者在内存中的地址不同。
Integer i = new Integer(100);
Integer j = 100;
System.out.print(i == j); //false
两个Integer 变量比较,如果两个变量的值在区间-128到127 之间,则比较结果为true, 如果两个变量的值不在此区间,则比较结果为 false 。
Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true
Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false
分析:
Integer i = 100 在编译时,会翻译成为 Integer i = Integer.valueOf(100),
而 java 对 Integer类型的 valueOf 的定义如下:
public static Integer valueOf(int i){
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high){
return IntegerCache.cache[i + (-IntegerCache.low)];
}
return new Integer(i);
}
java对于-128到127之间的数,会进行缓存。
所以 Integer i = 127 时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了。
int 变量 与 Integer、 new Integer() 比较时,只要两个的值是相等,则为true
因为包装类Integer 和 基本数据类型int 比较时,java会自动拆包装为int ,然后进行比较,实际上就变为两个int变量的比较。
Integer i = new Integer(100); //自动拆箱为 int i=100; 此时,相当于两个int的比较
int j = 100;
System.out.print(i == j); //true
1.2 源码案列总结
Integer、Lang、Byte、String等都用到了享元模式
Integer、Lang、Byte中的valueOf()方法用到了享元模式,String常量池就是享元模式。
具体看一下valueOf()方法的源码实现:
//valueOf()方法的具体实现
public static Integer valueOf(int i) {
//IntegerCache.low=-128,IntegerCache.high=127
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//Integer类中的静态方法,
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;
}
在上面的源代码我们可以看到,如果Integer.valueOf(int i)中传递的i在[-128,127]之间,
就会直接从IntegerCache.cache[i + (-IntegerCache.low)]中取一个出来,如果不在这个范围,
才会去创建一个新的Integer。
看完源码就会发现在valueOf这个方法中它会先判断传进去的值是否在IntegerCache中, 如果不在就创建新的对象,在就直接返回缓存池里的对象。 这个valueOf方法就用到享元模式。
它将-128到127的Integer对象先在缓存池里创建好,等我们需要的时候直接返回即可。
所以在-128到127中的数值我们用valueOf创建会比new更快。
2. 实际案列
假设开始设计五子棋的架构,在分析到棋子那一块的时候,就可以想到了享元模式。
因为棋子虽多,但是除了黑白之分,摆放位置之分,其他都一样,这就非常适用于享元模式;
2.1 第一步,定义一个共享对象通用的接口
棋子对象有一个绘制自己的通用操作
public interface Chess {
//绘制棋子
void draw(int x,int y);
}
2.2 第二步,实现需要共享的对象类
棋子对象分为黑白两类,所以此处我们将颜色设计为对象的内部状态来共享,而不是外部状态,所以就需要黑白两个对象类。如果你把颜色作为外部状态,那么只需要一个对象类即可。
黑棋
import java.awt.*;
//黑棋
public class BlackChess implements Chess {
//内部状态,共享
private final Color color = Color.BLACK;
private final String sharp = "圆形";
public Color getColor() {
return color;
}
@Override
public void draw(int x, int y) {
System.out.println(String.format("%s%s棋子置于(%d,%d)处", sharp, color.getAlpha(), x, y));
}
}
白棋
import java.awt.*;
//白棋
public class WhiteChess implements Chess {
//内部状态,共享
private final Color color = Color.WHITE;
private final String sharp = "圆形";
public Color getColor() {
return color;
}
@Override
public void draw(int x, int y) {
System.out.println(String.format("%s%s棋子置于(%d,%d)处", sharp, color.getAlpha(), x, y));
}
}
2.3 第三步,共享对象工厂
其负责提供共享对象,客户端不应该直接实例化棋子对象,而应该使用此工厂来获取。因为我们分了黑白两类对象,所以这里使用Color为key的map来存储共享对象。
import java.awt.*;
import java.util.HashMap;
import java.util.Map;
public class ChessFactory {
private static final Map<Color, Chess> chessMap = new HashMap<>();
public static Chess getChess(Color color) {
Chess chess = chessMap.get(color);
if (chess == null) {
chess = color == Color.WHITE ? new WhiteChess() : new BlackChess();
chessMap.put(color, chess);
}
return chess;
}
}
2.4 测试
import java.awt.*;
public class FlyweightClient {
public static void main(String[] args) {
//下黑子
Chess backChess1 = ChessFactory.getChess(Color.BLACK);
backChess1.draw(2, 5);
//下白子
Chess whiteChess = ChessFactory.getChess(Color.WHITE);
whiteChess.draw(3, 5);
//下黑子
Chess backChess2 = ChessFactory.getChess(Color.BLACK);
backChess2.draw(2, 6);
System.out.println(String.format("backChess1:%d | backChess2:%d | whiteChess:%d",
backChess1.hashCode(), backChess2.hashCode(), whiteChess.hashCode()));
}
}
输出结果:
从输出可见,backChess1 与 backChess2 是同一个对象,都是黑子,只是棋子的摆放位置不一样,而whiteChess是另一个不同的对象。所以不论棋盘上有多少颗棋子,程序中只会保持最多两个棋子对象,这就极大的节约了内存。
3. 总结
首先一定要区分出内部状态与外部状态,共享对象只持有内部状态,内部状态不可以从客户端设置,而外部状态必须从客户端设置。
合理设计共享对象分类,大部分情况下会设计成一组,而不是一个共享对象。
共享对象要求是不可变对象,从FlyWeightFactory获取到的对象都是一个原始的对象,而不是一个状态不确定的对象。
优点:
极大的降低了程序的内存占用