美文网首页程序员今日看点Java学习笔记
Effective Java Note (对象的创建和销毁)

Effective Java Note (对象的创建和销毁)

作者: dooze | 来源:发表于2016-11-26 21:23 被阅读78次

    Effective Java Note (对象的创建和销毁)

    一、对象的创建和销毁

    1. 考虑使用静态工厂方法替代构造器

    优点

    1. 静态工厂方法可以有一个名称,易于理解与阅读,工厂方法的名称可以凸显出不同构造器的区别
    2. 每次掉用的时候可以不用都创建一个新的对象,而是可以选择复用对象,在一些情况就可以直接使用==判断相等,而不是equals
    3. 可以返回原返回类型的任意子类型。可以参考java的集合框架。使用接口来引用被返回的对象,而不是通过实现类来返回对象引用是一个好习惯。
    4. 在创建参数化类型实例的时候代码更简洁。例如通过类型推倒与泛型:
    public static <K,V> HashMap<K,V> newInstance(){
      return new HashMap<K,V>();
    }
    
    Map<String,List<String>> m = HashMap.newInstance();
    

    缺点

    1. 如果类不含有共有或者受保护的构造器,那么将不能被子类化。(可以通过组合化解)
    2. 与其他的静态方法没有实质差别。一个好的命名这时候就很重要了,让人一看就知道是一个静态方法的作用。

    静态工厂方法和构建器对于有多个可选参数的时候,将增加复杂度与降低可读性

    2. 遇到多个构造器参数时考虑使用构建器

    重叠构造器在少数参数的时候可以,可以方便按需选择构造器,但是参数很多的时候,很容易造成混乱,阅读型也会大大下降。有时候即使参数类型是对的,但是最终结果也可能时错误的。

    此外基于JavaBeans思想可以使用 setXXX,为每个参数设置setter方法,这样每个构造参数都能很容易理解其作用,但是一个很严重的缺点是:构造的过程被分配到了几个调用过程,容易造成不一致状态(尤其时多线程环境下),而要保证一致状态需要付出很多精力去维护。

    构建器一般是一个需要实例化的类的静态内部类,提供了对参数的默认设置,同时对外可以提供链式调用进行构造。例如:

    Car car = new Car.Builder().name("benz").wheels(4).color(Color.BLACK).build();
    

    而且在Builder的每个域中都可以进行约束判断,违反约束条件的情况可以跑出非法异常。Builder很好的结合了setter和重叠构造器的优点,参数数量可以更灵活,阅读性更好

    还可以考虑使用抽象工厂:

    public interface Builder<T>{
      T build();
    }
    

    缺点

    为了构造一个实例需要多生成一个实例(Builder类)。

    3. 使用私有构造器或者枚举类型强化单例(Singleton)

    1. 一个类中之提供了一个私有的无参构造器,防止类外进行实例化。
    public class CEO{
      public static final CEO INSTANCE  = new CEO();
      private CEO(){}
    }
    

    但是,无参私有构造器可以通过反射的setAccessible()修改

    解决这个问题则需要在无参构造器中加入判断,当进行第二个实例化的时候抛出异常。还以提供友好的getInstance()公共静态方法。

    如果该类实现了序列化,那么需要重写readResolve,防止反序列化时创建一个新的实例:

    private Object readResolve(){
      return INSTANCE;
    }
    

    通过枚举实现Singleton,无偿提供序列化机制,绝对防止多次实例化,简洁的同时功能和公有域很相似。

    public enum CEO{
      INSTANCE;
      public void speech(){....}
      public void work(){....}
    }
    

    工具类中加入私有无参构造器,防止实例化是个很好的习惯。

    4. 避免不必要的对象创建

    1. 不要使用 new String("text");,这实际上创建了两个实例。
    2. 能重用对象的时候尽量重用对象,但是需要避免重用对象造成的错误,得不偿失。
    3. 避免不必要的额自动装箱工作,能用原生类型就用原生类型。
    4. 慎重使用对象池,维护起来需要很多工作。

    5. 消除过期的对象的引用

    1. 在一下设计的Stack出栈方法中:
    public Object pop(){
      if(size==0)
        throw new EmptyStackExcetiop();
      return elements[--size];
    }
    

    经过多次push增长后,pop掉的元素依然在Stack中保持有过期引用并由Stack管理着,而这些过期引用后期不在被使用,却不能被垃圾回收器进行回收。导致内存泄露。可以做一下修改:

    public Object pop(){
      if(size==0)
        throw new EmptyStackExcetiop();
        Object o = elements[--size];
        element[size] = null;
      return o;
    }
    

    设计自己管理内存的时候,都需要警惕内存泄露带来的问题

    1. 缓存带来内存泄露可能是缓存引用不在使用但是仍然长时间留在缓存中。
    • 当设计的缓存项的生命周期由该键的外部引用决定时可以使用WeakHashMap
    • 可以使用Timer或者ScheduleThreadPoolExecutor设计一个后台线程进行引用清理工作
    • LinkedHashMap的removeEldsetEntry管理缓存,实现添加新缓存项的同时清除旧的缓存项
    1. 回调与监听导致的内存泄露是因为,不使用的时候没有显示的取消注册,否则不断的注册回调或者监听会不断积累。可以只保存监听的弱引用。

    6. 避免使用终结方法

    1. 避免尽量避免使用finalize()终结方法,因为jvm不能保证何时执行,或者是否执行,执行需要多长时间。
    2. 涉及资源释放的的时候,尽可能的使用显示终结方法,例如流操作中的close()方法,一般使用try..catch..finally,并在finally中调用close()操作。
    3. 但是finalize方法可以作为最后的安全网操作,也就是在不显示终结的时候提供最后的终结调用,长时间释放资源总比不释放资源好。或者是用来释放非关键本地自愿的额时候使用。

    子类覆盖了父类的finalize方法,那么需要显示调用父类的finalize,super.finalize();

    相关文章

      网友评论

        本文标题:Effective Java Note (对象的创建和销毁)

        本文链接:https://www.haomeiwen.com/subject/hnugpttx.html