享元模式(FlyWeight Pattern)及其在java自动拆箱、自动装箱中的运用
本文主要从三个方面着手,第一:简要介绍享元模式。第二:享元模式在基本类型封装类中的运用以Integer为例进行阐述。第三:根据第一、第二的介绍,进而推出java是如何实现自动拆箱与装箱的。
第一:简要介绍享元模式。
享元模式的角色:抽象享元类(FlyWeight)、具体享元类(ConcreteFlyWeight)、享元工厂类(FlyWeightFactory)、客户类(Client,相当于一个测试类)
至于FlyWeight pattern如果直接翻译应译为: 蝇量级模式。从蝇量级我们可以知道共享的对象是非常小的。在Integer中共享的数字范围为[-128,127]。老外是不是从共享的对象太小的角度,所以称它为FlyWeight Pattern呢?。
我国人是从FlyWeight Pattern的用途(FlyWeight Pattern 的用途就是共享对象。元,也就是最小的意思。享元就是共享最小对象。)上进行翻译的。
下面给出各个角色的代码:
FlyWeight的代码如下:
package com.qls.flyWeight2; public interface FlyWeight { //以外蕴状态【external state】为参数的商业方法。这里假设外蕴状态是String类型的。 void operation(String externalState); }
ConcreteFlyWeight的代码如下:
1 package com.qls.flyWeight2; 2 3 public class ConcreteFlyWeight implements FlyWeight { 4 //内蕴状态的私有字段[internal state] 5 private Character internalState; 6 //构造一个以内蕴状态为参数的构造方法 7 public ConcreteFlyWeight(Character internalState) { 8 this.internalState = internalState; 9 } 10 @Override 11 public void operation(String externalState) { 12 // TODO Auto-generated method stub 13 System.out.println("internalState="+internalState+" externalState="+externalState); 14 } 15 16 }
FlyWeightFactory的代码如下:
1 package com.qls.flyWeight2; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 import java.util.Map.Entry; 6 7 public class FlyWeightFactory { 8 Map<Character,FlyWeight> files=new HashMap<Character,FlyWeight>(); 9 //构造方法 10 public FlyWeightFactory() { 11 // TODO Auto-generated constructor stub 12 } 13 //写一个以内蕴状态的工厂方法,以生产FlyWeight对象。 14 public FlyWeight factory(Character internalState){ 15 if(files.containsKey(internalState)){ 16 return files.get(internalState); 17 }else{ 18 FlyWeight flyWeight = new ConcreteFlyWeight(internalState); 19 //把对象保存到Map集合中。 20 files.put(internalState, flyWeight); 21 return flyWeight; 22 } 23 } 24 public void checkFlyWeight(){ 25 //打印出FlyWeight对象,以及享元对象。 26 for(Entry<Character, FlyWeight> en:files.entrySet()){ 27 System.out.println(en.getKey()+"="+en.getValue()); 28 } 29 } 30 }
Client的代码如下:
1 package com.qls.flyWeight2; 2 3 public class Client { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 //创建一个享元工厂对象 8 FlyWeightFactory flyWeightFactory = new FlyWeightFactory(); 9 //向享元工厂对象请求一个内蕴状态为'a'的享元对象 10 FlyWeight factory = flyWeightFactory.factory(new Character('a')); 11 //以参量方式传入一个外蕴状态。 12 factory.operation("ouyangfeng"); 13 //向享元工厂对象请求一个内蕴状态为'b'的享元对象 14 FlyWeight factory2 = flyWeightFactory.factory(new Character('b')); 15 //以参量方式传入一个外蕴状态。 16 factory.operation("sixi"); 17 //向享元工厂对象请求一个内蕴状态为'a'的享元对象 18 FlyWeight factory3 = flyWeightFactory.factory(new Character('a')); 19 //以参量方式传入一个外蕴状态。 20 factory.operation("wallow"); 21 System.out.println(factory==factory3);//返回结果为true。由此可以确定'a'确实做到了享元(共享元数据) 22 /** 23 * 打印出享元对象。以及内蕴状态。目的是证明对相同的内蕴状态只创建一个对象。 24 * 下面语句的输出结果是: 25 * b=com.qls.flyWeight2.ConcreteFlyWeight@21780f30 26 a=com.qls.flyWeight2.ConcreteFlyWeight@512d297a 27 这个输出结果证明了:factory==factory3. 28 */ 29 flyWeightFactory.checkFlyWeight(); 30 } 31 32 }
第二:享元模式在基本类型封装类中的运用以Integer为例进行阐述。
首先概述一下:在Integer类中享元对象是保存在一个IntegerCache.cache[]这个数组里面的这个数组存放的内容是:[-128,127]之间的数字,这个范围中的Integer,对象只创建一次。
所以你要测验:Integer a=-128; Integer b=-128 ; System.out.println(a==b);输出结果为true. 而Integer c=-129; Integer b=-129 ; System.out.println(a==b);输出结果为false.
要想说清楚这个问题只需看一看private static class IntegerCache 、 public static Integer valueOf(int i)、 public int intValue() 这三个类的源码即可。
private static class IntegerCache的源码如下:(注:在这个源码中我添加了,对这个源码的一些解释)
1 private static class IntegerCache { 2 static final int low = -128; 3 static final int high; 4 static final Integer cache[]; 5 //静态代码块保证了,Integer cache[]在IntegerCache加载时就会被赋值。赋值的结果见第22行 6 static { 7 // high value may be configured by property 8 int h = 127; 9 String integerCacheHighPropValue = 10 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); 11 if (integerCacheHighPropValue != null) { 12 int i = parseInt(integerCacheHighPropValue); 13 i = Math.max(i, 127); 14 // Maximum array size is Integer.MAX_VALUE 15 h = Math.min(i, Integer.MAX_VALUE - (-low) -1); 16 } 17 high = h; 18 19 cache = new Integer[(high - low) + 1]; 20 int j = low; 21 for(int k = 0; k < cache.length; k++) 22 cache[k] = new Integer(j++);//这个循环语句等价于Integer[] cache={new Integer(-128),new Integer(-127),new Integer(-126).........new Integer(126),new Integer(127)}共127+128+1=256个数字。 23 }//当我们在Integer a=f;直接从这个cache[]数组中进行搜索如果f在[-128,127]之间(例如Integer a=1)不需要new Integer(1)了,而是在这个cache[]数组中等于1的值直接赋值。反之如果 f不在[-128,127]之间则会new Integer(f);这个源代码见下面的valueOf 24 //即可豁然开朗。同时这也体现了享元模式的运用。在享元模式示例中用HashMap存放对象,对同一个对象只创建一次。这里用的是数组存放对象,对同一个对象也只创建一次。 25 private IntegerCache() {} 26 }
valueOf的源代码如下:
1 public static Integer valueOf(int i) { 2 assert IntegerCache.high >= 127; 3 if (i >= IntegerCache.low && i <= IntegerCache.high) 4 return IntegerCache.cache[i + (-IntegerCache.low)]; 5 return new Integer(i); 6 }
public static Integer valueOf(int i)这个方法用于把int转换为Integer,它是编译器用于自动装箱的核心方法。由编译器根据代码的实际情况,决定是否调用这个方法。一句话在把int变为Integer时,编译器会自动调用这个方法。
intValue()的源代码如下:
1 public int intValue() { 2 return value; 3 }
public int intValue()这个方法用于把Integer转换为int的。
,它是编译器用于自动拆箱的核心方法。由编译器根据代码的实际情况,决定是否调用这个方法。一句话在把Integer变为int时,编译器会自动调用这个方法。
下面这个例子说明在一下三种情况编译器是如何工作的:三种情况是:int 与Integer Integer与Integer int 与int
1 package com.qls.gateway; 2 3 public class Test6 { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 int g=1; 8 int a=1; 9 int f=189; 10 //b与c在[-128,127]这个范围内。 11 Integer b=1; 12 Integer c=1; 13 //d和e不在[-128,-127]这个范围内。 14 Integer d=189; 15 Integer e=189; 16 System.out.println(a==g);//true【这个是int 与int的比较】 17 /** 18 * 从这两个输出语句System.out.println(f==d); 19 System.out.println(a==b);得出一个结论当int 与Integer比较时,编译器会把Integer自动拆箱。 20 证明如下:假设当int 与Integer比较时,编译器会把int自动装箱。那么int f=189.自动装箱过后变为 21 new Integer(189)相当于用创建了一个对象所以与Integer d=189。不可能是同一个对象,那么f==d也就为 22 false了,但是输出结果为true。所以假设不成立。 23 所以当int 与Integer比较时,编译器会把Integer自动拆箱。 24 */ 25 System.out.println(f==d); 26 System.out.println(a==b); 27 /** 28 * Integer与Integer比较时:只需看Integer的范围是否在[-128,127]如果在直接从cache[]数组中取, 29 * 则必然是同一个对象,反之如果不在,则会创建一个对象。 30 */ 31 System.out.println(d==e);//输出false 32 System.out.println(b==c);//输出true. 33 } 34 35 }