EffectiveJava学习笔记(二)

  第四条:通过私有构造器强化不可实例化的能力

  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

  https://www.cnblogs.com/youtang/p/11441959.html

原文地址:https://www.cnblogs.com/youtang/p/11617919.html