《Effective Java》阅读笔记之对象创建及销毁

写在前面

本文及后面的几篇笔记都是读《Effective Java》的随笔。内容里面有书里面重点的部分,所以大家别见怪就好。如果各位发现啥问题,希望能不吝赐教,虽然是自己的笔记,也希望能得到大家的指教。

关于对象的创建,《Design patterns》里面有五种设计模式支撑,不能说设计模式就是最终导向,只能说是特定情况下的全局考量。那么在《Effective Java》(以下用EJ表示)里面的处理方式,可以跟这些设计模式相得益彰,因为GOF讲的是大局,而Bloch讲的是实践。必要时,两个方面都会谈谈。

一、静态工厂方法

优点:

1.可以有更能说明具体作用的方法名(与构造函数相比)

   从维护代码的角度讲,这的确是个优点。至少可以提供更加简洁明了的api支持。但是,如果一个类已经能够说明清楚用途,大可不必因为这个原因来做此重构。

2.不必每次都创建一个新对象(与构造函数相比)

   从接触java开始,我们就不停地在利用构造函数创建对象,new来new去,再加上jvm的垃圾回收机制,使得我们java团员们根本不会考虑一味的对象创建会对内存和效率产生多大影响。通过静态工厂方法,我们可以重复使用一些对象。这些对象的特点:常用属性不变(field或method)。

   在java的api里面,就有:

   public static Boolean valueOf(boolean b) {

        return (b ? TRUE : FALSE);

    }

   这就可以重复使用public static final Boolean TRUE = new Boolean(true);或者public static final Boolean FALSE = new Boolean(false);

   在这种情况下,我们的代码里面应该都是领域(用有业务含义的词来说明)的语言表达。结合第一点,我们可以让我们的代码变得更加简洁有效。

3.可以返回子类的对象(这点明显优于构造函数,但是否有这个必要,还需考虑)

   一般情况下,我想可以不考虑这个实现。但是考虑到EJ提到的服务提供框架,这的确是非常高级的一种处理。

  在此也大体说明一下这种service provider framework!在EJ中对这种框架的组成部分有比较详细的说明(不禁感叹学习的重要性了)。这种框架主要包括三个接口:服务接口(各种服务提供类必须实现的统一服务接口);服务注册接口(便于各个具体服务提供实现,注册到框架中);服务访问接口(这是服务使用端【或者称为客户端】需要的)。

  例子的话,也就是JDBC里面的:Connection是服务接口,DriverManager.registerDriver是服务注册接口,DriverManager.getConnection是服务访问接口。

4.降低冗长的泛型定义(在定义一些集合类时,的确比较繁琐,尤其是我们的泛型比较复杂时)

  对于这点,我也模仿EJ里面的方式,写了几个方法:

  public static final <T> ArrayList<T> newArrayList(int initialCapacity){

        return new ArrayList<T>(initialCapacity);

    }

  在使用的时候,真的少些很多type reference(就是泛型的类型定义)。

缺点:

1.如果提供了静态工厂方法,那么这个类就应该禁用构造函数创建,同时也就不能被继承。

2.无法区分静态工厂方法跟其他静态方法。

二、用构造器替换参数较多的构造函数

这种情况时候存在,比方说:

class Person {

 public Person(int id,int age,int weight,int hight){

  this.id = id;

  this.age = age;

  this.weight = weight;

  this.hight = hight;

 }

//nothing more ....

}

在这个类里面我们需要四个变量,这还是少的。即便少,有时也会眼迷一下,搞错位置,就出问题了:Person p = new Person(123344,23,76,178);哪个可怜鬼要是碰到了这个类,算是比较幸运的。再来四个变量,就疯了。一般在这种情况下,考虑通过setter的方式来避免同一类型的参数错误:

class Person {

  public Person(){}

  public void setId(int id){this.id = id;}

  //其他几个变量都有对应的setter方法

}

创建对象:

Person p = new Person();

p.setId(123344);

p.setAge(23);

...

尽管这种方式比构造函数的参数的情况好很多,但还是要写大片的代码。所以,就在此引入builder。

接下来是EJ里面的实现方式:

class Person{

    private int id;

    private int age;

    private int weight;

....

 

 private Persion(Builder builder){

    this.id = builder.id;

    this.age = builder.age;

    ......

 }  

 public static class Builder{

    private int id;

    private int age;

    private int weight;

....

  public Builder(int id){this.id = id;} 

  public Builder age(int age){this.age = age; return this;}//这个效果的确比较方便,能出现面条式的赋值方式

  public Builder weight(int weight){this.weight = weight;return this;}

  .......

  public Person build(){

      return new Person(this);

  }

 }

}

各位看到这里,可能也会跟我一样,会有中看到懒婆娘的裹脚啦。但是这种方式在创建对象时,效果还是相当可观的:Person p = new  Person.Builder(123344).age(23).weight(76).build();比前面好了很多。当然我也在想,EJ的作者为啥非要在Person里面再创建一个builder,而不是直接在Person里面用

这种赋值方式呢?毕竟可以减少,变量的复制,因为在builder里面有跟Person一样的域列表。个人看法,EJ作者是想把责任明确,如果是Person里面就需要一个用于创建实例对象的方法,但是那样的话,责任就不是很明确了,就是对象自己创建自己,还不如使用构造函数算了。

上面的这种方式,的确很值得借鉴,个人觉得适用的情况要明确。

三、强制属性单例

首先,单例在某些情景中是违反设计模式的,比如比较重要的资源:IO,数据库链接等。凡事不可一概而论,单例也有很多优点,可以重用对象。我们该如何加入单例属性呢?

第一点要注意的就是构造函数应该是private的。当然,用单例属性时不一定非要使得宿主类是不可被构造的,这种情况仅仅是保证宿主类就要是单例的。这个问题在单例模式(GOF)中有详细说明。这个单例属性应该是静态的,而且最好是在定义时初始化,否则,就要考虑double-check问题。还是直接来一个类说明一下比较好:

class Person{

  private static final Person INSTANCE = new Person();

  public static Person getInstance(){ return INSTANCE;}//静态工厂方法

}

这种方式中,通过静态的final变量定义方式,可以保证其永远都是同一个对象。

在序列化的时候,因为static和transient变量不会被写到流中。所以,经过序列化之后,总是会使得静态变量再次创建。那么我们所谓的单例也就不能保证了。

但是通过enum可以避免在序列化(从参考资料中,查看enum的序列化处理)中像static那样的负面影响,可以比较保险的实现单例。

public enum PersonEnum{

   INSTANCE;

   public void doAction(){...}

}

四、如何实现非实例化的对象创建及避免多余的对象创建?

通过private构造函数就可以避免不能实例化的对象创建了,当然EJ中也分析了抽象类的一些缺点(引导用户去继承呀等等)。

而避免多余的对象创建,其例子是String s = new String("test");这里就创建了两个string对象。那么对于其他的一些情况,我们要再深入些:

1.java的autobox功能。尽量避免这种转换,以减少对象创建的频率。

2.仔细分析方法调用,区分变化和非变化部分。例如:一个方法调用中,总是会创建一些固定的对象,可以考虑让这些个对象的创建过程在实例或者类层次上完成。以避免在多次的方法调用过程中重复对象创建。

五、避免过时的对象引用及finalizer

一般的变量赋值可能不会出现过时对象引用问题。在用到引用数组或者缓存等数据管理时,要时刻关注这种引用,是否会造成对象引用过时。对于数组(集合也算在其中)情况,可以在使用完之后,赋值成null。而在缓存中,可以使用weakreference的方式来处理。

对于finalize,个人觉得就是记住:一定不要相信它来做任何资源处理。当然,如果你用了,而且说这东西很好,我只能说,那是个奇迹。

六、参考资料

java序列化的高级认识 http://www.ibm.com/developerworks/cn/java/j-lo-serial/

三种enum的序列化http://www.vineetmanohar.com/2010/01/3-ways-to-serialize-java-enums/

weakreference相关的 http://www.cnblogs.com/ericchen/archive/2011/05/27/2059060.html

你可能感兴趣的:(Effective Java)