0
点赞
收藏
分享

微信扫一扫

effective Java 学习笔记(第二章)

yongxinz 2022-02-23 阅读 95

使用私有构造方法执行非实例化

有时候写一些工具类什么的,里面都是静态方法和静态成员变量,此时根本不需要实例化这个类,就可以正常使用这个类。
怎么保证这种类不会被实例化呢?当类中没有显示的构造函数时,会默认生成一个构造方法。我们可以通过显示构造函数私有化的方式,避免这个类被实例化。由于方法是私有的,类外是没办法调用的。

public class CalculateUtil {
    private CalculateUtil(){
        throw new AssertionError();
    }
}

不过构造方法私有化等同于也禁止了子类实例化,因为子类实例化时都会显示或隐式的调用父类构造。

避免创建不必要的对象

有些对象不需要反复创建时就可以不创建,比如常量等。还有一些创建昂贵的对象,建议将其缓存起来以便重复使用。
比如,需要一个方法来判断字符串是否符合某种正则表达式:

static boolean isRomanNumeral(String s) {
    return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
            + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}

String的matches方法内部会为正则表达式创建一个Pattern实例,并且只使用它一次。创建Pattern实例是昂贵的,因为它需要将正则表达式编译成有限状态机。
为了提高性能,可以初始化时缓存一个Pattern实例,在方法中直接使用该实例:

public class CalculateUtil {
    private static final Pattern PATTERN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})"
                                            + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
        static boolean isRomanNumeral(String s) {
            return PATTERN.matcher(s).matches();
    }
}

自动装箱也会创建不必要的对象。有线使用基本类型而不是装箱基本类型,也要注意无意识的自动装箱。

消除过期的对象引用

java中也会存在内存泄漏,虽然有垃圾回收机制,但有些情况下,对对象的引用不会被回收。

  1. 无意的对象保留,会导致内存泄漏。应及时清空对象引用,即element = null
  2. 缓存中的泄漏。一旦将对象引用放入缓存中,很容易忘记它的存在,并且在它变得无关紧要之后,仍然保留在缓存中。可以使用WeakHashMap来表示缓存。
  3. 监听器和回调的泄漏。没有进行撤销注册回调,他们会积累。

想要确保垃圾收集,需要保持方法或对象是弱引用,如使用WeakHashMap。

通过实现Stack来说明第1点

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    // 如果一个栈增长后收缩,那么从栈弹出的对象不会被垃圾收集,即使使用栈的程序
    // 不再引用这些对象。 这是因为栈维护对这些对象的过期引用(obsolete references)。 
    // 过期引用简单来说就是永远不会解除的引用。
    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        return elements[--size];
    }

    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}

上述中pop后的对象,保持过期引用,没有被回收,应手动清空对象引用。

public class Stack {
    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        Object element = elements[--size];
        System.out.println("1 :" + element);
        System.out.println("2 :" + elements[size]);
        elements[size] = null;
        System.out.println("3 :" + elements[size]);
        return element;
    }

    public static void main(String[] args) {
        Stack stack = new Stack();
        stack.push("abc");
        Object pop = stack.pop();
        System.out.println("4 :" + pop);
    }
}
  

修改pop方法,使用main进行简单测试,输入结果如下:

1 :abc
2 :abc
3 :null
4 :abc

避免使用Finalizer和Cleaner

  • Finalizer 和 Cleaner 机制的一个缺点是不能保证他们能够及时执行,执行时间是任意的。
  • finalizer机制存在严重安全问题:finalizer机制攻击

解决问题方式:为对象封装需要结束资源,如线程或文件等,应实现AutoCloseable接口,并在不用时调用close方法。

Finalizer 和 Cleaner 机制合法用途

  • 如果资源拥有者忘记调用close,那么最后使用该机制还是可以释放资源,虽然说不好是什么时间释放,但总比一直不释放强
  • 用来清理本地对等类(是一个由普通对象委托的本地非java对象)。由于本地对等类不是java对象,jc并不知道他,当java对象被回收,本地对等类不会被回收。

使用try-with-resources语句替代try-finally语句

举报

相关推荐

0 条评论