chapter2
Chapter2
Tip1 静态工厂方法代替构造器
公有的静态方法,只是一个返回类实例的静态方法。
静态工厂方法的优势:
优势一:
有名称,如果构造器本身没有正确的描述被返回的对象,具有适当名称的静态工厂方法会更加适用。由于一个类仅能提供一个构造器,程序员可能会调用错误的构造器。用静态方法代替构造器,可以避免构造器的错误使用。
优势二:
不必每次都返回新的对象,直接返回已经创建好的对象就行,适合单例模式、不可实例化、不可变的类,这样可以确保不生成两个相等的实例。
优势三:
(有点没理解好)可以返回原返回类型的任何子类型的对象。
服务提供者框架(Service Provider Framework)
多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现(通过重写服务类中的那个关键函数)。感觉上好像就是一个多态的使用而已,还是没理解到位?
下面是一个实现的例子:
这个例子大概就是,有好多service provider,它们可以提供不同的service。最一般的想法就是,一个provider类里面弄一个doService的方法,但这么做显然不太好,会有许多重复的代码。于是需要一个Service接口,里面至少要有一个doService方法,其他Provider再具体生成的时候都需要去继承这个接口来实现它们自己的doService方法。此外还需要有个Services的类来提供一些工具函数(API)让provider对Service的调用过程变得更方便。Services里面基本全是一些静态方法。比如生成新的Provider实例(这里就用到了上面提到的用静态方法代替构造器),根据已有的Provider实例名来返回实例,把一个新生成的实例注册到Service的管理器中(用一个map 保存实例名字字符串和实例对象的映射关系)
源码如下:
package chapter2_serviceprovider;
public interface Service {
//some methos to do
public void doService();
}
package chapter2_serviceprovider;
public interface Provider {
Service newService();
}
package chapter2_serviceprovider;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
// 1 provide a map to store the relationship of name and provider instance
// 2 provide a registration api to excute the puts method of map
// 3 provide Service access api to return the instance of provider from map
public class Services {
private Services(){}
//providers is a map to store the
//relation of the service name and the provider
private static final Map<String,Provider>providers=
new ConcurrentHashMap<String,Provider>();
public static final String DEFAULT_PROVIDER_NAME="<def>";
//provider registraion api
public static void registerDefaultProvider(Provider p){
registerProvider(DEFAULT_PROVIDER_NAME,p);
}
public static void registerProvider(String name,Provider p){
providers.put(name, p);
}
//Service access api
public static Service newInstance(){
return newInstance(DEFAULT_PROVIDER_NAME);
}
public static Service newInstance(String name){
Provider p=providers.get(name);
if(p==null){
throw new IllegalArgumentException(
"No provider registered with name:"+name);
}
return p.newService();
}
}
package chapter2_serviceprovider;
public class testServiceProvider {
//create different providers
//every provider should realize the newService in their own way
private static Provider DEFAULT_PROVIDER = new Provider() {
public Service newService() {
return new Service() {
public void doService() {
System.out.println("do the service in the default way") ;
}
};
}
};
private static Provider SERVICE_WAYA = new Provider() {
public Service newService() {
return new Service() {
public void doService() {
System.out.println("do the service in waya") ;
}
};
}
};
private static Provider SERVICE_WAYB = new Provider() {
public Service newService() {
return new Service() {
public void doService() {
System.out.println("do the service in wayb") ;
}
};
}
};
public static void main(String[] args) {
//regist the created Providers
//namely create the maping betwwen the provider and its key name
Services.registerDefaultProvider(DEFAULT_PROVIDER);
Services.registerProvider("servicea",SERVICE_WAYA);
Services.registerProvider("serviceb",SERVICE_WAYB);
// create the newinstance
Service s1=Services.newInstance();
Service s2=Services.newInstance("servicea");
Service s3=Services.newInstance("serviceb");
//invoke the service
s1.doService();
s2.doService();
s3.doService();
}
}
优势四
创建参数化实例的时候,可以更加简洁,这里的例子用的是集合中的例子
如果想要通过泛型创建一个Map比如
Map<String,List
这样可能显得很繁琐,尤其是使用迭代器循环的时候,可能里里外外要好多层。
如果HashMap提供一个类似的静态工厂方法(貌似目前还没有类似的方法)
Public static<K,V>HashMap<K,V>newinstance(){
return new HashMap<K,V>()
}
这样直接通过HashMap.newinstance()来生成新的实例,会方便许多。
在自己编写类似的工具类的时候,可以借鉴相关的思路,来简化程序
缺点一
光看有点是不对的,如果该类不含有共有的或者受保护的构造器,就不能被实例化。比如说Collections类,它里面全都是static方法和私有的构造器,因此不能被实例化。鼓励使用复合的继承的方式对其进行操作
缺点二
与其他静态方法实际上没有什么本质的区别。
总结
静态方法和共有构造器各有用处,静态方法通常情况下更合适。因此我们的第一反应应该是使用静态工厂,而非是使用共有的构造器。
Tip2 遇到多个构造器参数时,要考虑使用构造器
这部分主要介绍的是Builder模式
具体内容:
下面是一个实际的例子
package chapter2_buildermodel;
//using builder to create the instance of NutritionFacts
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
//initialize the parameters by a builder
private NutritionFacts(Builder builder){
servingSize=builder.servingSize;
servings=builder.servings;
calories=builder.calories;
fat=builder.fat;
sodium=builder.sodium;
carbohydrate=builder.carbohydrate;
}
public void info(){
System.out.println("the servingSize is "+servingSize);
System.out.println("the servings is "+servings);
System.out.println("the calories is "+calories);
System.out.println("the fat is "+fat);
System.out.println("the sodium is "+sodium);
System.out.println("the carbohydrate is "+carbohydrate);
}
public static class Builder{
//required parameters
private final int servingSize;
private final int servings;
//optionl parameters (shuld provide a default value)
private int calories=0;
private int fat=0;
private int carbohydrate=0;
private int sodium=0;
//every Builder method return this builder instance
public Builder(int servingSize,int servings){
this.servingSize=servingSize;
this.servings=servings;
}
public Builder calories(int val){
calories=val; return this;
}
public Builder fat(int val){
fat=val; return this;
}
public Builder carbohydrate(int val){
carbohydrate=val; return this;
}
public Builder sodium(int val){
sodium=val; return this;
}
//return this initialized instance by the build method at last
public NutritionFacts build(){
//invoke the private constructor of NutritionFacts
//this constructor use a builder instance to initaialze the parameter
return new NutritionFacts(this);
}
}
public static void main(String[] args) {
//create the NutritionFacts instance by the Builder model
NutritionFacts cocaCola=new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
cocaCola.info();
}
}
这样处理起来,比那种使用多个构造方法的形式确实灵活好多,但是还是比较麻烦。在一些类似的脚本语言中,可能对于类似问题的处理会好一些,比如早python中,直接在函数声明的时候就能指定默认的参数。传参的时候也能用显式的方式指定到底是给哪个变量传递的。
总结
如果类的构造器或者静态工厂方法有多个参数,或者以后可能会拓展出多个参数出来,Builder模式是很推荐的选择,对参数的检验也变得更加灵活,更易于编写维护。
当然缺点就是代码可能会显得冗长,只有在多参数的时候才适合使用。
书上提供的另外两种方式是设置多个构造器,或者是使用JavaBean的方式,具体参考书上内容。
Tip3 用私有构造器或者枚举类型强化Singleton属性
单例模式(singleton)似乎是必须要掌握的一个设计模式了,好多时候只需要生成一个单独的实例,具体的可以参考之前整理的这个(http://www.cnblogs.com/Goden/p/3956803.html)再补充一点,因为反射方式的存在,可以通过反射的方式调用私有的构造器生成新的实例,于是为了防止有人利用这一点恶意破坏程度,可以再构造函数中添加保护机制,如果实例被第二次创建的时候,就会抛出一个异常。
还有就是单例的序列化的问题
之前那个是比较常见的一个单例实现,通过枚举的方式实现单例,看起来似乎更加简洁,并且单元素的枚举类型已成为实现singleton的最佳方法。(枚举这里相关内容具体参考第6章)
枚举就是单例的泛型化,这个说的也是很好,应该加强理解。在一个枚举类中,定义的每个instance都是单例的。
Tip4 私有构造器强化类的不可实例化的属性
有些类是不希望被实例化的,比如某些工具类,如java.lang.Math,或者java.util.Arrays,再或者java.util.Collections等等,对于这种类型的类,通常会提供给一个私有的构造器,来防止这个类被实例化。通常在这种私有的构造器上加一个注释,就是明确的指出,这个类不希望被实例化,比如// supperss default constructor for noninstamtiability
Tip5 避免创建不必要的对象
这个比较容易理解,就是在方法中尽量减少 Object o=new Object()这种语句,这样的话,每次方法被调用都会创建一个新的对象出来,到底是效率低了好多。
根据前面的几条,能使用静态方法的,最好还是使用静态方法。
下面这个例子,是好方法和差方法的对比:
package chapter2_objectReuse;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
//check if the birth date betwwen baby boomer
public class Personold {
private final Date birthDate=null;
//other fields, methods, and constructor omitted
//Bad Way (the Calendar instance is created every time when the method is invoked):
public boolean isBabyBoomer(){
Calendar gmtCal=Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY,1,0,0,0);
Date boomStart=gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY,1,0,0,0);
Date boomEnd=gmtCal.getTime();
return birthDate.compareTo(boomStart)>=0&&birthDate.compareTo(boomEnd)<0;
}
}
package chapter2_objectReuse;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
public class Personnew {
private final Date birthDate=null;
//other fields, methods, and constructor omitted
//appoint the start time and the end time
private static final Date BOOM_START=null;
private static final Date BOOM_END=null;
// a better way to initialize the parameter
static{
Calendar gmtCal=Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY,1,0,0,0);
Date BOOM_START=gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY,1,0,0,0);
Date BOOM_END=gmtCal.getTime();
}
public boolean isBabyBoomer(){
return birthDate.compareTo(BOOM_START)>=0&&birthDate.compareTo(BOOM_END)<0;
}
}
第一个例子中,每次都要新生成一个Calemdar类,影响效率,后面的一个实现中,通过static块的方式,将参数都初始化好存了下来,这样比较好。
后面还说了适配器的情形(有点没看懂)还有自动装箱的情况,优先使用基本类型而不是自动装箱类型,比如计算long的时候写成了Long,这样的话,实例就会被自动装箱,生成许多不必要的实例,影响开销。
现在有点晕了,后面又说,这种避免对象创建的策略也不是绝对的,小对象的创建和回收都是比较廉价的,如果通过创建附加的对象能够提升程序的清晰性,这也是推荐的,看来还是具体情况具体分析了。
Tip6 消除过期引用
这一个tip主要是讨论一个内存泄露的问题。 内存泄露可以由过期引用引起,所谓过期引用就是指永远不会再被接触的引用。 比如下面的这个例子。 package chapter2_overduereference;import java.util.Arrays;
import java.util.EmptyStackException;
public class Stack {
private Object[]elements;
private int size=0;
private static final int DEFAULT_INITIAL_CAPACITY=16;
public Stack(){
elements=new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e){
ensureCapacity();
elements[size++]=e;
}
public Object pop(){
if(size==0)
throw new EmptyStackException();
return elements[--size];
}
//ensure space for one more elements
//roughly doubling the capacity each time the array need grow
private void ensureCapacity(){
if(elements.length==size)
elements=Arrays.copyOf(elements, 2*size+1);
}
}
看起来代码也没什么错误,关键是在pop函数的地方,如果先入栈好多,再出栈,那么先前的引用就没有被释放掉,因为对于垃圾回收器来说,前面已经不用的数组区域和后面正在使用的数组区域是没有区别的,因此要用手工的方式使得不用的引用指向null才能保证被垃圾回收期器回收(与垃圾回收器的机制有关?)。这主要是因为stack是自己管理内存的。
好的做法应该是这样:
public Object pop(){
if(size==0)
throw new EmptyStackException();
Object result= elements[--size];
elements[size]=null;
return result;
}
事实上在官方的Stack实现中,也是这样做的:
在pop方法中,调用了一个removeElementAt方法来去掉指定位置的元素,在这个方法的最后,也把去掉的那个元素的引用赋成了null。
还有两种内存泄露的来源,由于没有实例,理解的不是太好。
缓存
监听器和其他回调
内存泄露一般不容易被发现,应该提前就预测到内存泄露可能会发生的地方。
Tip 7 避免使用终结方法
这个tip还是蛮重要的,算是有一点亲身体会。特别是使用finally来关闭连接状态的时候,无论是socket或者是是数据库的连接,并且还是那种多个线程的环境下。 这个后面介绍的部分还是有点没看懂。除非是作为安群网,或者是为了终止非关键的本地资源,否则请不熬使用终结方法。在一些很少见的情况下,既然使用了终结方法,就要牢记调用super.finalize方法。(这一部分不太了解)如果用终结方法作为安全网,要记得记录终结方法的非法用法。最后,如果需要把终结方法与共有的非final类关联起来,请考虑使用终结方法的守卫者,以确保即使使子类的终结方法未能调用super.finalize该终结方法也会被执行。