第四条:通过私有构造器强化不可实例化的能力
java.lang.Math,java.util.Arrays这种工具类无需实例化,因为毫无意义,但是在缺少显式构造器的情况下,编译器会自动构建一个公有的无参构造器,这些工具类可能会在无意识的情况下被实例化,或是被继承,并且,企图将这种类做成抽象类限制实例化是行不通的,因为继承抽象类的子类仍然可被实例化,同时会造成误解。解决办法是让这种工具类包含一个私有的构造器,这样可以保证类不被实例化,同时也不可被继承,因为子类无法调用父类的构造器。
第五条:优先考虑依赖注入来引用资源
许多类会依赖一个或多个底层资源,同时要求底层资源是可以修改的(同一个类的多个实例),满足该需求的最简单的模式是,当创建一个新的实例时,就将资源传入类的构造器中(这就是依赖注入),实现如下。
public class DependencyDemo { public static void main(String[] args) { new People(new Apple()); } } class People { public People(Fruit fruit) { fruit.eat(); } } interface Fruit { void eat(); } class Apple implements Fruit { @Override public void eat() { System.out.println("eat apple"); } } class Banana implements Fruit { @Override public void eat() { System.out.println("eat banana"); } }
不要使用单例或工具类实现一个需要依赖底层资源的类,因为资源的行为会影响类的行为,也不要直接创建资源,应使用依赖注入的方式。依赖注入的资源对象具有不可变性,因此多个客户端可以共享依赖对象,依赖注入的这种模式同样适用于静态工厂、构造器、构建器。依赖注入的另一个变体是将资源工厂传给构造器,客户端可创建任意子类型。
第六条:避免创建不必要的对象
将String s = new String("abc"),修改为String s = "abc";前者多次执行每次都构建一个新的对象,后者多次执行每次都使用缓冲区同一字符常量。
对于同时提供了构造器和静态工厂方法的不可变类,应优先使用静态工厂方法,避免创建不必要的对象。
有些对象创建成本昂贵,应缓存下来重复使用,如使用String.matches方法匹配正则表达式,其在内部为正则表达式创建Pattern实例,并且使用一次后就会被回收,为了提升性能,将正则表达式编译成一个Pattern实例,同时在创建类的实例时初始化,缓存起来,达到重用目的。
自动装箱也会创建多余对象,在仅做计算无需判空的操作下,应优先使用基本类型而非包装类。
同时,并非任何情况下都要避免创建对象,一些小对象的创建和回收是非常廉价的,无需维护在对象池中,对象池中维护的对象应是非常重量级的,典型如数据库连接池。
第七条:消除过期的对象引用
Java虽然有垃圾回收机制,但仍然有可能发生内存泄漏的情况,如下所示,我们手动实现一个栈。
class Stack { private Object[] elements; private int size = 0; private static final int DEFAULT_INITAL_CAPACITY = 16; public Stack() { this.elements = new Object[DEFAULT_INITAL_CAPACITY]; } public void push(Object ele) { elements[size++] = ele; } public Object pop() { if (size == 0) { throw new EmptyStackException(); } return elements[--size]; } private void ensureCapacity() { if (elements.length == size) { elements = Arrays.copyOf(elements, size * 2 + 1); } } }
在这个例子中,如果一个栈先增长再收缩,弹出的对象将不会被当作垃圾回收,即使程序中不再使用这些对象,它们仍不会被回收,因为栈的内部维护着对这些对象的过期引用(即永远不会被解除的应用)。在本例中,凡事elements中下标小于size部分的引用都是过期应用,这些对象将造成内存泄漏。解决这个问题的方法很简单,只要清空引用即可,pop方法中添加如下处理。
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
Object ele = elements[--size];
elements[size] = null;
return ele;
}
清空引用的另一个好处是,如果这些过期对象又被错误的引用,程序将抛出NPE,可快速发现异常。
还有一个内存泄漏的常见来源就是缓存,可以设置过期时间来避免。
第三个会发生内存泄漏的常见来源是监听器和其他回调,如果没有取消注册或特殊处理将不断堆积,解决办法是只保存它们的弱引用,如将它们保存成WeakHashMap的键。
第八条:避免使用终结方法和清除方法
java不会保证一个程序终止前,清除方法或终结方法被及时的执行(或被执行),所以不应该依赖终结方法或清除方法更新状态。并且,如果在终结过程中抛出未被捕获的异常,终结过程将会停止也不会打印出异常栈信息。
让类实现AutoClouseable可避免使用终止方法或清除方法,配合try-with-resourses可确保程序终止。使用方式及原理可见本博客另一篇文章https://www.cnblogs.com/youtang/p/11441959.html
第九条:try-with-resources优先与try-finally