使用私有构造方法执行非实例化
有时候写一些工具类什么的,里面都是静态方法和静态成员变量,此时根本不需要实例化这个类,就可以正常使用这个类。
怎么保证这种类不会被实例化呢?当类中没有显示的构造函数时,会默认生成一个构造方法。我们可以通过显示构造函数私有化的方式,避免这个类被实例化。由于方法是私有的,类外是没办法调用的。
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中也会存在内存泄漏,虽然有垃圾回收机制,但有些情况下,对对象的引用不会被回收。
- 无意的对象保留,会导致内存泄漏。应及时清空对象引用,即element = null
- 缓存中的泄漏。一旦将对象引用放入缓存中,很容易忘记它的存在,并且在它变得无关紧要之后,仍然保留在缓存中。可以使用WeakHashMap来表示缓存。
- 监听器和回调的泄漏。没有进行撤销注册回调,他们会积累。
想要确保垃圾收集,需要保持方法或对象是弱引用,如使用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对象被回收,本地对等类不会被回收。