看完java8相关的东西之后,被林江老司机丢来了一本effective java第三版,正好遇到2018年的春节前后,开始看之。
Item 1 consider staticfactory methods instead of constructors
java8的java.util.time中已经大量使用了静态工厂方法来生成对象。具体实现方式是构造器私有,通过静态工厂方法来调用构造器,从而生成对象。当时看到这种方式还稍微想了一下,毕竟自已以前写的代码中,除了单例以外,还是以使用构造器为主。这一条Item可以说完美的解决了一些疑惑。p.s.这里的静态工厂方法不是设计模式中的工厂设计模式。
advantages:
- 相比构造其,静态工厂方法拥有更好的代码可读性:不同的构造器重载只能根据参数区分,静态工厂方法的函数名可以传递更多信息,从而使代码具有更好的可读性
- 和构造器不同,静态工厂方法不是每次都必须生成一个新的对象。这点比较好理解,常见于单例的写法。
- 静态工厂方法可以返回“返回类型”的子类(多态性)
- 和上一条很像但侧重点不同:根据入参的值的不同,可以返回不同的类型
- 静态工厂方法可以返回一个当下并不一定存在的对象。如返回实现了某个接口的匿名类,lambda之类的。
disadvantages:
- 由于构造器私有,导致该类无法拥有子类
- 静态工厂方法在API文档中不像构造器那样显眼(蜜汁不好找?)
Item 2 consider a builderwhen faced with many constructor parameters
这条主要针对类中有多个成员,且大量成员可以optional。
常见的针对这种场景的解决方案有3个:
- telescoping constructorpattern 根据需要,重载一堆构造函数。缺点也非常明显:代码可读性差。
- java bean一个构造函数,和一堆getter/setter:不是线程安全的,不能构造不可变类,多线程安全会有影响。
- builder在类内实现一个静态内部类,内部类的build方法调用外层的构造函数生成对象。
Item 3 Enforce thesingleton property with a private constructor or an enum type
针对单例模式:
- 如果仅仅实现了一个私有的构造函数,通过反射可以破坏单例。
- 如果实现了一个死有的构造函数,并实现了返回单例成员的静态方法,可以通过反序列话破坏单例。为防止通过反序列化破坏单例,需要将单例实例域声明为transient,并提供readResolve方法。
- 可以用单元素的枚举类型实现单例。
Item 4 Enforcenoninstantiability with a private constructor
不可实例化类构造器私有化。
将不可实例化的类型声明为抽象类是不够的,该类仍然可以被继承;通过提供一个死话的构造函数可以确保。
Item 5 prefer dependencyinjection to handing resources
(Spring,你懂的)
Item 6 Avoid creatingunecessary objects
- 尽可能使用静态工厂方法而不是构造函数,见Item1
- 重用不可变对象,以及明确知道不会改变的可变对象。 e.g.String对象为final不可变对象,可以重用。正则对象可变,但一般场景下不会改变,可以作重复使用。
- 对于可变对象,考虑适配器模式
- 自动装箱也会创建无意义的对象。优先使用基本类型,而不是装箱类型,谨慎无意义的装拆箱。
Item 7 Eliminate obsoleteobject references
应当警惕内存泄漏的问题,一旦元素被释放掉,该元素中包含的任何对应引用就应该被清空。
常见的内存泄漏的情况:
- 外部引用集合始终存在,持有多个对象。清空对象时,只重置了引用个数,而没有清空实际引用。元素不再使用应置为null
- 另一种常见的内存泄漏来自于缓存。可以使用WeakHashMap,弱引用的情况下GC。或者明确生命周期,周期结束后清除缓存
- 第三个常见来源是监听器和其他回调。要确保回调被GC,可以使用WeakHashMap仅保存他们的弱引用。
Item 8 Avoid finalizers andcleaners
finalizers是不可预知的,具有危险性,且往往不必要。在Java9 中被标记为过时的(deprecated),然而仍能使用。Java9中,提供cleaners替代finalizers。cleaners比finalizers的危险性要小,但仍然是不可预知且往往没有必要的。
p.s.个人是基本没有使用过finalizers。这里给出的建议也是尽可能不使用。
disadvantage:
- java不同于C++,C++有destructors来回收资源,java有GC机制。对于一些需要手动关闭的资源或对象,可以使用try-with-resources/try-finally
- finalizer和cleaners不确保会被即刻调用。
如果在finalizer和cleaners中做了具有时效性的工作,可能会产生非预期的效果。
比如在finalizer中关闭文件。用于打开文件的描述符是非常有限的资源,由于finalizer不是敏捷的,可能一直没有执行关闭文件的操作,从而导致大量描述符被占用。
finalizer执行的时机依赖于具体JVM的实现。finalizer所属的线程优先级非常低,在多线程并发的状况下,可能finalizer迟迟没有被调用,从而导致导致finalizer的操作不被执行。如果在finalizer实现了对于对象的回收,则可能导致回收对象的延迟。
cleaner比finalizer稍微好一些,cleaner的线程可以被调用方控制。
- java的语言特性不仅不保证finalizer会被及时执行,且不保证它会被执行。
因此建议:不要在finalizer或cleaner做更新持久化状态的操作。若他们没有被执行,或将导致状态数据不一致的状况。
System.gc,System.runFinalization会提升finalizer被执行的概率,但仍无法保证它一定被执行。
System.runFinalizersOnExit和Runtime.runFinalizersOnExit虽然会确保finalizer被执行,但这两个方法有重大缺陷,已经被废弃使用。
- finalizer中的异常会被忽略,由此可能导致数据不一直的问题。cleaners则不存在该问题,可以通过控制其线程来解决该问题。
- finalizer会抑制有效GC,因此使用finalizer和cleaner可能导致严重的性能问题。
- 警惕finalizerattack。如:子类覆盖了父类的finalize方法。对象释放时,仅调用了子类的finalize方法,而父类的finalize方法并没有被调用,导致父类对象未被回收。
advantage:
- 作为关闭资源的安全网
- 针对nativepeer,可以有效的释放对象。
Item 9 prefertry-with-resources to try-finally
- try-with-resource可以有效避免资源对象没有关闭。
- try-with-resource使代码整洁
- try-with-resource可以有效追踪一场的stacktrace。try-finally在遇到相同的异常时,会suppressed。