一道常见的基础测试题
public class Test {
public static void main(String[] args) {
String a = "china";
String b = "china"; //字面量形式创建
String c = new String("china"); //创建对象形式
System.out.println(a==b);
System.out.println(a==c);
}
}
Java中的字符串常量存储在常量池中,一个字符串常量在常量池中只有一个拷贝。
字面量的体现形式String b = "china",简单理解为去字符串常量池中拿对象的引用。
创建对象的体现形式 String c = new String("china"),简单理解为直接在堆内存空间中创建新的对象。
String类型就包含了Flyweight Pattern享元模式。
简单的代码示例
以查询火车票价为例。
假如每张车票信息都是一个对象,当有很多人在查询一个车票信息时,系统就会重复创建这个车票信息返回给每个人,这样会不停的创建和销毁对象,引发频繁的GC,影响效率。
从城市 A 到城市 B的车辆是有限的,车上的铺位分为硬座,硬卧,软卧。将这些可以公用的对象缓存起来,在用户查询时优先使用缓存,如果没有缓存则重新创建。
首先创建一个 Ticket 接口,该接口定义展示车票信息的函数,具体代码如下。
public interface Ticket {
void showTicketInfo(String bunk);
}
具体的一个实现类是 TrainTicket。
public class TrainTicket implements Ticket {
private String from; //出发地
private String to; //目的地
private int price;
private String bunk; //铺位
public TrainTicket(String from, String to) {
this.from = from;
this.to = to;
}
@Override
public void showTicketInfo(String type) {
price = new Random().nextInt(300);
this.type=type;
System.out.println("购买 从"+from+"到"+to+"的"+bunk+"火车票,价格:"+price);
}
}
使用享元模式很简单,只需要简单的创建一个 TicketFactory。
public class TicketFactory {
private static Map sTicketMap = new ConcurrentHashMap();
public static Ticket getTicket(String from,String to){
String key = from+"-"+to;
if (sTicketMap.containsKey(key)){
System.out.println("使用缓存==> " + key);
return sTicketMap.get(key);
}else {
System.out.println("创建对象==> "+key);
Ticket ticket = new sTicketMap(from, to);
sTicketMap.put(key,ticket);
return ticket;
}
}
}
调用者测试
public class Test {
public static void main(String[] args) {
TicketFactory.getTicket("深圳", "长沙").showInfo("硬座");
TicketFactory.getTicket("深圳", "长沙").showInfo("硬卧");
TicketFactory.getTicket("深圳", "长沙").showInfo("软卧");
}
}
享元模式的结构
享元模式的主要角色由享元工厂,抽象享元,具体享元类和主函数几部分组成。其中
- 享元工厂:用于创建具体享元类,维护相同的享元对象。它保证相同的享元对象可以被系统共享。即,其内部使用了类似单例模式的方法,当请求对象已经存在时,直接返回对象,不存在时,在创建对象。
- 抽象享元:定义需要共享的对象业务接口。享元类被创建出来总是为了实现某些特定的业务逻辑,而抽象享元便定义这些逻辑的语义行为。
- 具体享元类:实现抽象享元类的接口,完成某一具体逻辑。
- 客户端:使用享元模式的组件,通过享元工厂取得享元对象。
UML结构图
虚线箭头指向依赖;
实线箭头指向关联;
虚线三角指向接口;
实线三角指向父类;
空心菱形能分离而独立存在,是聚合;
实心菱形精密关联不可分,是组合;
参考 UML类图与类的关系详解
优点
- 节省重复对象的开销,因为维护的相同的对象只会被创建一次。
- 创建对象减少,系统内存需求也减少,GC压力降低。
经典示例:Handler中Message的应用
mHandler.obtainMessage();
进一步查看Handler源码,obtainMessage如下
public final Message obtainMessage(){
return Message.obtain(this);
}
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
sPool指向的是一个链表,其实这个链表是存储我们用过的Message的,当我们obtain一个Message的时候,会去取链表中的第一个,并把sPool指向下一个,同时把取到的置空。
/*Message的回收方法*/
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
/*将用过的Message放入链表中*/
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
Message的享元模式不是使用的传统的map方式,而是自己构建一个链表。但灵活使用享元模式思想才是重点。