天天看点

Effective Java读书笔记、感悟——1.创建和销毁对象

    每天看点Effective Java,挺好的,至今觉着Java小菜,多学基础知识,喜欢因为做喜欢的事忘记时间的感觉。

    不多吐槽了。直奔主题,这里只是笔记和一些感触,选取了一些我熟悉的类和方法举个例子,因为很久了做Android比较多,我可能会选择一些Android的类库说明android相关的设计理念,对于常用到的就不再举例,一时想不到有些,也不必要非要找出实例,自己做时候注意就好了。如果细致了解建议看原文,原文中提到了一些例子,除了很重要的、以前忽略的这里都不再重复。

一:考虑静态工厂方法代替构造器 ->静态工厂方法,不全同设计模式中的工厂模式。 ->静态工厂方法可以解决构造函数只能参数类型顺序不同的问题,比如可以两组相同的序列可以用方法名表示不同的含义。 ->可以返回子类型,类似工厂模式了。到这里可以屏蔽子类,如针对不同参数返回不同子类优化性能,如果后来发展无法满足需求了,大可增加或删除子类,而构造方法不行。 ->对于编码阶段禁忌第一反应是构造方法。 ->静态工厂方法缺点,无可继承构造方法时无法子类化,还有在java doc中和其他方法没有区别,而构造方法会单独列出。

二:遇到多个构造器参数时考虑用构造器 ->构建器builder。这里很有代表性的就是Android中Dialog的Builder,详见API,提供了先传入参数后构造Dialog的方法。 ->多个参数的解决方案中: 重叠构造器包含了用户不期望的默认值。Java bean非线程安全,可能处于不一致状态,且阻止了把类做成不可变的可能。 ->构建器使用抽象工厂解决了两者,且可以在对象域(解释下,产生对象的方法)进行约束检查,违反抛出illegalstateexception,或在每个setter进行。 ->Java中传统的抽象工厂实现是Class对象,用newInstance方法充当build方法的一部分,这种用法隐含着许多问题。因为newInstance方法总是企图调用类的无参数构造器,这个构造器甚至可能根本不存在。如果类没有可以访问的无参构造器不会出现编译错误,但是client code中必须catchInstantiationException or IllegalAccessException和一切因无参数构造器带来的问题,即使无throws子句。Class newInstantce破坏了编译时的异常检查,而Builder可以弥补。 ->缺点:需要多建立一个构造器,代码显得冗长,一般用于多余4个参数调用,但如果你将来有添加参数的可能,那么请用构造器,因为同时存在这些就比较不协调了,对于API的设计来讲是非常不合理的。

三:用私有构造器或枚举类型强化Singleton属性 Singleton属性一般采取的两种方式: 1. 静态final instance 2. 私有final instance,提供静态getInstance方法返回,我们一般采取这种方式,因为它可以保证在API不便的情况下选定是否为Singleton或修改为其他形式。 这里提到这两种方式主要要说明两个没注意的问题:反射和序列化 ->反射:这种方式反射都可以访问到构造方法,可以对构造方法做修改,当产生第二个实例抛出异常。 ->序列化:仅仅普通方法实现接口是不够的,以为为了保证Singleton,必须要声明所有实例域都是瞬时的,并提供一个readResolve方法。否则存在的问题是每次反序列化一个序列化实例,都会创建一个新的实例。 private Object readResolve(){       return instance; }

书中提到了1.5增加枚举后的第三种方法, public enum Elvis{      INSTANCE;

     methods...... } 此方法直接解决以上两个问题。Android很重要的就是工具库编写的DB工具类使用了Singleton生成Android自身的dbhandler来处理,虽然不序列化,但是反射是个很严重的问题,毕竟是工具包,所以快点重构吧亲。同样,也许你在HttpHandler中也需要用到。如果没有制作这两个包,建议使用,因为可重用性非常高,也需要解决很多问题,几乎每个项目都使用,同样,保证包的使用的稳定性,不多说了,下一条。

四:通过私有构造器强化不可实例化的能力 ->对只有静态方法和域的类,主要用于把基本类型的值或数据类型上的相关方法组织起来(Math,Arrays),可以通过Collections的方法把实现特定接口的对象上的惊天方法组织起来,可以利用这种类把final类上的方法组织起来,以取代扩展该类的做法。 ->此工具类是不希望被实例化的,实例化对他么有任何意义。然后我们如果不提供构造器,jvm会自己提供,那还是会被实例化,那么我们只要在类中提供一个私有的构造器就可以了,并添加注释说明。 这样带来的问题是不能子类化,因为子类要求要求super父类的构造函数。

五:避免创建不必要的对象 ->若一个方法频繁调用且每次生成相同的内部实例,可以作为static,如Map的keyset。 ->维护自己的对象池来避免重复创建对象不是很好的做法,除非对象是非常重量级的,Object pool也增加了内存占用。

六:消除过期的对象引用 存在过期引用会导致无法GC,但清空对象引用应该是一种例外,而不是一种规范行为。 一下三种情况要考虑会发生内存泄露: 1.类自己申请内存管理 2.缓存,易忘记管理,如WeakHashMap可以自动处理没有被外部引用的缓存项。一般利用后台线程定时清理,也可以类似LinkedHashMap使用removeEldestEntry在添加条目时清理。对于复杂的缓存,必须直接使用java.lang.ref 3.监听器和其他回调,回调此时可以做成弱引用。

七:避免使用终结方法 这里先说一下finalize的执行方式,再来说effective java中学到的东西。JVM采用跟搜索算法进行GC对象选择,这里不细说,JVM发现需要回收的对象(当然直观的来看就是没有引用的对象了)会做一次标记并进行一次筛选,筛选的条件就是此对象是否要执行finalize方法(对象未覆盖或finalize已被JVM执行过一次都被视为不需要执行),需要执行的放在F-Queue中,并稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行,但执行仅指虚拟机会触发这个方法,但不承诺会等待此方法运行结束,原因很简单,不可能因为等待这一个进程而使得其他要回收的资源一直等待,这样会导致GCC崩溃。当然一般也没有人会去这样做,毕竟教材没有这样教的,这里effective java中提到了两个用途很重要去掌握,回收资源一般作为一种显示的stop方法。finalize方法的作用: 1. 当用户没有调用stop,这时候在finilize中回收资源总比不回收强,相当于补充一个安全网。(Android中最典型的是MediaPlayer,如果不回收资源将会带来很大的问题,可以查看Android确实是这样做的,它调用的方法如下: protected void finalize() { native_finalize(); }  具体含义不是这里应该继续讨论的问题,但是需要关注的是如果去做,这里MediaPlayer做的不是realse一样的内容,实际需要考虑finalize执行的问题,它不能耗时太长且不保证执行) 2. 普通对象通过本地方法委托给一个本地对象(本地对等体),因为本地对等体不是普通对象,gC不知道它,当本地对等体不拥有关键资源时finilize是最合适的工具。 需要注意的是,子类如果不super父类的finalize那么不会执行父类的代码,为了防止子类恶意不调可以使用放入一个匿名类,创建一个终结方法守卫者,即当他finalize时会释放外面的资源,与子类调用与否无关。但是此方法不推荐使用,因为方法的本意只是为了让c++程序员更快习惯java的编码而做的妥协,并不是java的析构函数