标签:
今天刚开始读effective java,中文版的读起来很拗口,但感觉收获很多。
另外,这本书的内容是针对Java 1.5和1.6的。
在这里整理一下第2章:创建和销毁对象 的内容。
这一条针对的情景是要获得类的实例时。一般说来,想要获得类的实例,都是通过构造函数(书里叫做构造器)。
最常见的构造函数是这样的,没有返回参数,名字和类名相同。
public class A{ public A(int a){ //构造函数内容 ... } }
而所谓的静态工厂,是一个静态的函数,名字和类名不同,返回值是本类的对象。示例是将boolean基本类型值转化成Boolean对象引用:
public static Boolean valueOf(boolean b){ return b ? Boolean.TRUE : Boolean.FALSE; }
类可以通过它的静态工厂方法来访问它的客户端(也就是获得对象的实例),而不是通过构造器,那么我们为什么要使用静态工厂呢?它的优势是什么?
如果构造器的参数不能正确描述返回的对象,那么静态工厂会更容易使用,代码也会更容易阅读。
比如说一个类A,有两个参数不同的构造函数,分别做两件事情A,B。代码如下:
public class A{ public A(int a,String s){ //do A } public A(String s,int a){ // do B } }
这是对构造函数的重载,针对不同的参数做不同的动作,但是用户在调用的时候并不知道哪个构造器做哪个活动,他们可能把参数的顺序写错,调用了自己不想用的那个构造器。而静态工厂方法就不受限制了,可以根据要做的行为给方法起不同的名字,比如doA(),doB(),这样可以避免用户的错误调用,也加强了代码的可读性。
这是静态方法的特点了,用static声明一个方法的时候,这个方法可以直接用类名调用,而不需要用对象调用。
比如方法methodA是静态方法,属于类A,那么可以这样调用:A.methodA().而不用A a = new A();a.methodA();
静态方法能够为重复的调用返回相同对象,这样有助于类严格控制在某个时刻哪些实例应该存在。这种类叫做实例受控类。使用实例受控类有几个原因:
1.可以确保它是singleton(单例)的,或者是不可实例化的。
2.可以使不可变的类可以确保不会存在两个相等的实例,即当且仅当a==b时才有a.equals(b)为true。这样用户就可以用==操作符来替代equals方法,提升性能。
构造器只会返回本类的对象,而静态工厂方法可以返回本类以及本类的子类对象,这个是多态性的一个体现。使用这种静态工厂方法时,用户可以通过接口来引用被返回的对象,而不是通过它的实现类,这是一种良好的习惯。
举个例子,比如说对集合类,List l = new ArrayList<String>().List是接口,而ArrayList是实现类。我们可以通过接口List来引用,而没必要用实现类ArrayList来引用。这样有一个好处,当程序以后的实现类改变时,只需要在这一处改变实现类声明就可以了。
同时,还有一条:静态工厂方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不必存在。比如说:(这里我不知道理解的对不对)
public class A{ public static B method(){ //静态工厂方法 } } public class B extends A{ //B类的实现 }
这种灵活的静态工厂方法构成了服务提供者框架的基础。什么叫“服务提供者”框架呢?书里是这么定义的:
它是指这样一个系统:多个服务提供者实现一个服务,系统为客户端提供多个实现,并把他们从多个实现中解耦出来。
服务提供者框架中有3个重要的组件:1,服务接口。这是提供者实现的。2,提供者注册API,这是系统用来注册实现的,让客户端访问他们。3.服务访问API,是客户端用来获取服务的实例的。
我的理解是:这3个组件构成了一个分层的结构。从上到下分别是3,2,1。用户见到的是3服务访问API,用户调用了3之后,3会调用2提供者注册API,得知服务实现的信息(比如提供者,名称之类的),2再调用不同提供者提供的服务接口1,完成服务的实现。用户只知道结果(得到了一个service),但是不必知道是哪个提供者提供的,也不必知道调用了哪个具体的服务实现接口。
用代码说明:
Map<String,List<String>> m = new HashMap<String,List<String>>(); //这里声明了两次<String,List<String>> //下面是用静态工厂方法的,只声明了一次 Map<String,List<String>> m = HashMap.newInstance();
当然,静态工厂方法也有缺点。
缺点一:类如果有私有的构造器,就不能被子类化
缺点二:它们与其他的静态方法实际上没有任何区别。你没有办法从名字看出来,一个静态方法到底是静态工厂方法还是仅仅是一个普通的静态方法。遵守惯用名称可以弥补这一劣势,常用的名称有:valueOf,of,getInstance,newInstance,getType,newType。
总的来说,这本书的作者推荐我们首先考虑静态工厂方法,而不是共有的构造器。
考虑这一的场景:我们需要一个类来表示包装食品外面的营养成分标签。这些标签中有几个域是必须的:每份的含量,每罐的含量,卡路里。还有超过20个可选域:总脂肪量,饱和脂肪量,转化脂肪,胆固醇,钠等等。对于这样的类,应该用哪种构造器或者静态工厂方法来编写呢?
常用的是提供重载构造器,通过重载构造函数中的参数来实现。但是当有很多参数的时候,客户端代码会非常难写。
还有第二种方法,JavaBean模式,即调用一个无参构造器来创建对象,然后用setter方法来设置每个必要的参数。但是这个方法有严重的缺点:可能会使JavaBean处于不一致的状态;线程安全难以保证。
作者提出了第三种方法:构建器。不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或静态工厂),得到一个builder对象,然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成不可变的对象。这个builder是它构建的类的静态成员类。
听上去有点复杂,上代码:
//Builder Pattern 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; public static class Builder{ //必要参数 private final int servingSize; private final int servings; //可选参数 private final int calories=0; private final int fat=0; private final int sodium=0; private final int carbohydrate=0; 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;} public NutritionFacts build(){ return newNutritionFacts(this);} } private NutritionFacts(Builder builder){ servingSize = builder.servingSize; ... } }
客户端代码:
NutritionFacts cocaCola = new NutritionFacts.Builder(240,8).calories(100).sodium(35).carbohydrate(27).build();
builder模式模拟了具名的可选参数。
当然,Builder模式更冗长,所以它只适合用于多个参数,且数目不定,有可选参数时使用。
singleton是指仅仅被实例化一次的类。实现方法有两种:
第一种,公有静态成员是个final域。
public class Elvis{ public static final Elvis INSTANCE = new Elvis(); private Elvis(){...} public void otherMethods(){...} }
第二种,公有的成员是个静态工厂方法。
public class Elvis{ private static final Elvis INSTANCE = new Elvis(); private Elvis(){...} public static Elvis getInstance(){return INSTANCE;} public otherMethods(){...} }
用上面这两种方法实现的singleton在反序列化时会出现问题,每次反序列化时都会创建一个新的实例。
从Java 1.5后,有第三种方法,编写一个含有单个元素的枚举类型:
public enum Elvis{ INSTANCE; public void otherMethod(){...} }
这种方法更加简洁,而且无偿的提供了序列化机制,防止多次实例化。所以单元素的枚举类型已经成为实现singleton的最佳方法。
有一些工具类,可能只包含静态方法和静态域,我们不希望它们被实例化(比如?),但是在缺少构造函数的情况下,编译器会自动提供一个公有的无参的构造函数。同时,把它们声明为抽象类企图强制该类不可被实例化,这也是行不通的。抽象类可以有子类,子类也可以实例化,甚至声明会误导用户,以为这个类是专门为了继承而设计的。
我们只需要让类包含私有构造器就可以达到这个目的。当然,这样也有一个副作用,会使得这个类并不能被子类化。因为所有构造器都会显示或隐式调用父类构造器。
考虑:String s = new String("abdcle");这个语句每次被执行的时候都会创建一个新的String实例,但是这些动作都是不必要的,如果这句在一个循环中,会创建很多个不必要的String实例。改为 String s = "abdcle"更好。(因为java有常量缓冲池,字符串常量只需要创建一次)
另一个例子:
public static void main(String[] args){ Long sum = 0l; for(long i = 0 ; i < Integer.MAX_VALUE; i ++){ sum += i; } System.out.println(sum); }
这个例子在功能上没有问题,但是要慢很多。因为sum被声明为Long类型,造成了程序构造了231个多余的Long实例。所以说:优先使用基本类型而不是装箱类型,要当心无意识的自动装箱。
另外,小对象的创建和回收代价是很廉价的,所以用维护自己的对象池来避免创建对象并不是很好的做法,除非对象是重量级的(比如数据库的连接池,因为创建一个数据库连接的代价很昂贵,因此重用这些连接有意义。)
考虑一个实现栈的例子:
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) throws new EmptyStacException(); return elements[--size]; } private void ensureCapacity(){ .... } }
这样会发生内存泄露。因为栈中元素弹出时,被弹出的元素并不会被当做垃圾回收,即使使用栈的程序不再引用这些对象,它们也不会被回收。因为栈内部维护了对这些对象的过期引用。
如果一个对象引用被无意识的保留了,那么,GC不仅不会处理这个对象,而且也不会处理被这个对象所引用的其他对象。
修复这个问题很简单,在栈的pop方法中,添加一句: element[size] = null;即可。
内存泄露的另一个来源是缓存,有两种情况:第一种是缓存中的元素已经在内存中被清除了,那么这个元素是无效的,应该被清除。第二种是缓存中的元素很久没有被访问到,这时候应该替换掉这个元素。
第三个来源是监听器和其他回调(这个我没接触过,不写了)
finalize方法的缺点在于,不能保证会被及时的执行,因为GC的启动时间并不确定。甚至没有办法保证会被执行。所以不能用来释放资源,更新持久状态等等。这部分内容应该由try-finally来完成。
如果确实需要终止方法,只需要提供一个显示的终止方法,如InputStream,OutStream,sql.Connection上的close()等。
值得注意的是,终结方法链并不会被自动执行。也就是说,如果子类覆盖了终结方法,那么在子类的终结方法中必须手工调用父类的终结方法,否则不会执行父类的终结方法。
标签:
原文地址:http://www.cnblogs.com/cangyikeguiyuan/p/4384448.html