Effective Java这本书针对人群是对Java有一定的开发经验,至少对Java的整体结构是了解的,额,我属于比小白再高级点的,提前接触了这本书,有错误或者认识的不到位的欢迎指教,我也会随着时间的推移,不断地完善。
今天要总结的是Effective Java的第二章创建和销毁对象。
第一条:考虑使用静态工厂方法代替构造器
之前的博客有说过,关于创建一个类的实例的方法,最常见的即利用构造器,当然还有利用Java反序列化机制、clone、通过Class对象的newInstance方法以及getInstance,后面两种就属于今天要说的静态工厂方法。首先要明白的一点是静态工厂方法与设计模式中的工厂模式是不一样的,即静态工厂方法并不直接对应设计模式中的工厂方法。静态工厂方法只是一个返回类的实例的静态方法。我们在创建类的实例时就可以利用静态工厂方而不用公有的构造器,这样做有如下优势:
(1)静态工厂方法有名称。
这一点是很好理解的。我们都知道构造器是和类名是一致的,如果一个类中有很多个构造器,我们区分它们就是根据构造器的参数列表上,但是对于用户来说,并不知道构造器的参数列表是什么样的,即用户不知道该调用哪一个构造器。但是,静态工厂方法是有名称的,因此当一个类需要多个构造器时可以选择用静态工厂的方法代替构造器。
(2)静态工厂方法不必在每次调用时都创建一个新的对象。
当利用构造器创建类的实例时,每new一次,程序就会在堆内存中 创建一个新的对象,即每调用一次构造器都会产生一个新的对象。但是静态工厂方法就可以避免这问题。静态工厂方法可以使不可变类使用预先构建好的实例,或者把构建好的实例缓存起来进行重复利用(这个地方是需要再补充的)。Boolean.valueOf就是利用了静态工厂的方法,它首先在源码里创建了两个对象TRUE和FALSE,每次调用这个方法时就不需要再创建TRUE和FALSE这两个对象了。
静态工厂方法能为重复的调用返回相同的对象,这里有一个概念即实例受控的类,实例受控的类是指这种类总能严格的控制在某个时刻哪些实例应该存在。实例受控的类可以保证它是一个单例类或者是不可实例化的类,也可以保证不可变的类不会存在两个相等的实例,即只有在a==b时,才会有a.equals(b)为true。
/*
* 验证静态工厂方法不必在每次调用类时都创建一个新的对象
* 以单例模式为例
*/
public class Singleton {
private static Singleton singleton=null;
public static Singleton getInstance(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
public static void main(String[] args){
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1==s2);
}
}
(3)静态工厂方法可以返回原返回类型的任何子类型对象。
利用Java的多态,使静态工厂方法可以在产生基类的同时注入子类的实现。公有的静态工厂方法所返回的对象的类不仅可以是非公有的而且该类还可以随着每次调用而发生改变,这取决于静态工厂方法的参数值,但是只要是已经声明的返回类型的子类型,都是允许的。而且静态工厂方法返回的对象所属的类,可以不存在,编译时不会出错,但是运行时肯定会出错。
定义一个接口
public interface Food {
public void introduce();
}
Fish和Fruit均实现了这个接口,两个代码如下:
/*
* 每个类中都会有一个默认的无参构造器
* public Fish(){}
* 因此可以不显示的写
*/
public class Fish implements Food{
//public Fish(){
// System.out.println("Fish");
//}
@Override
public void introduce() {
// TODO Auto-generated method stub
System.out.println("i am a fish");
}
}
public class Fruit implements Food{
public Fruit(){
//System.out.println("Fruit");
}
@Override
public void introduce() {
// TODO Auto-generated method stub
System.out.println("i am a fruit");
}
}
编写静态工厂方法如下:
/*
* 验证静态工厂方法可以返回原类型的任何子类型的对象
* Class.forName(类所在的包+类名)
* newInstance只能调用无参构造器
* getFood属于静态工厂方法来创建对象。
*/
public class test1 {
public static Food getFood(String type) {
Food food = null;
try {
food = (Food) Class.forName("test1."+type).newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return food;
}
public static void main(String[] args){
Food food=getFood("Fish");
food.introduce();
}
}
结果如下:
(4)静态工厂方法之服务提供者框架(还不明白,明白了再补充)
(5)静态工厂方法可以使代码简洁。
下面是关于静态工厂方法的缺点(1)类如果不含公有的或者受保护的构造器,就不能被子类化。(2)静态工厂方法和静态方法实际上没有任何的区别。
第二条:遇到多个构造器参数时要考虑使用构建器
静态工厂方法和构造器都不能很好的扩展到大量的可选参数上。
一般程序员会采用重叠构造器的方法,即每个构造器都依次多加一个参数,但是当参数特别多时,代码会很难编写,用户在调用时因为不知道构造器参数列表很有可能会调用错误的构造器。在遇到许多构造器参数时,还有一种解决方案就是用JavaBeans模式,这种模式即调用一个无参构造器创建对象,再调用setter方法来设置每个参数的,如下:
/*
* javaBean模式采用setter方式挨个构造
*/
public class create1 {
private final String name;
private int age;
private String sex;
private double rate;
public create1(String name){
this.name=name;
}
public void setAge(int age){
this.age=age;
}
public void setSex(String sex){
this.sex=sex;
}
public void setRate(double rate){
this.rate=rate;
}
public String toString(){
return "name:"+name+",age:"+age+",sex:"+sex+",rate:"+rate;
}
public static void main(String[] args){
create1 c=new create1("ltt");
c.setAge(12);
c.setRate(23.4);
c.setSex("girl");
System.out.println(c);
}
}
但是这种模式也有非常严重的缺点。因为构造的过程被分到了几个调用中,在构造的过程中JavaBean可能处于不一致的状态,试图使用处于不一致状态的对象将会导致失败。
因此有了今天要介绍的构建器,它属于Builder模式的一种形式,它既能保证像重叠构造器那样的安全性也能保证像JavaBeans模式那么好的可读性。例子如下:
/*
* 使用Builder模式进行创建对象
* 在要构建的类内部创建一个静态内部类 Builder
静态内部类的参数与构建类一致
构建类create2的构造参数是 静态内部类Builder,使用静态内部类的变量一一赋值给构建类
静态内部类Builder提供参数的 setter 方法,并且返回值是当前 Builder 对象
最终提供一个 build 方法构建一个构建类的对象,参数是当前 Builder 对象
*/
public class create2 {
private final String name;
private String sex;
private int age;
private double rate;
public create2(Builder builder) {
this.name=builder.name;
this.age=builder.age;
this.sex=builder.sex;
this.rate=builder.rate;
}
public static class Builder{
private final String name;
private String sex;
private int age;
private double rate;
public Builder(String name){
this.name=name;
}
public Builder setSex(String sex){
this.sex=sex;
return this;
}
public Builder setAge(int age){
this.age=age;
return this;
}
public Builder setRate(double rate){
this.rate=rate;
return this;
}
public create2 build(){
return new create2(this);
}
}
public String toString(){
return "name:"+name+",age:"+age+",sex:"+sex+",rate:"+rate;
}
public static void main(String[] args){
create2 c=new create2.Builder("ltt").setAge(12).setRate(23.4).setSex("girl").build();
System.out.println(c);
}
}
第三条:使用私有构造器或者枚举类型强化Singleton属性
实现Singleton的方法:
(1)保证构造器时私有的,导出公有的静态成员:
public class Singleton{
public static final Singleton Instance=new Singleton();
private Singleton(){}
}
(2)保证构造器是私有的,提供公有的静态工厂方法:
public class Sngleton{
private Singleton(){}
private static final Singleton Instance=new Singleton();
public static Singleton getInstance(){
return Instance;
}
}
要注意的是如果这个单例类要变成可序列化的(Serializable)则必须实现readResolve方法,只有这样才能保证单例类的特性,只创建一个对象。
(3)只编写一个包含单元素的枚举类型:
public enum Singleton{
INSTANCE;
}
第四条:通过私有构造器强化不可实例化的能力
一些工具类是不希望被实例化的,因为实例化没什么意义,但是在缺少显示的构造器的情况下,程序会自动的调用隐式的无参构造器。在这种情况下,我们只能在不希望被实例化的类中显示的加上构造器,而且这个构造器需要是私有的。
第五条:避免创建不必要的对象
最好能重用对象,而不是在每次需要的时候就创建一个相同功能的新对象,如果对象是不可变的,它就始终可以被重用。
除了重用对象外,还可以重用那些已知不会被修改的可变对象。
第六条:消除过期的对象引用
第七条:避免使用终结方法finalize
关于finalize在前面的博客中有一定的介绍,总之就是要尽量的不用。Java中显示的终止方法就是结合try-finally。像FileInputStream、FileOutputStream、Timer和Connection都有各自的终结方法,它们可以和try-finally结合。