除了已经提到的关于良好的编程风格和继承滥用的问题,还有一个微妙的问题 – 内部类和(非静态)匿名类实例充当闭包。这意味着它们保留对包含类实例的隐式引用。这可以导致防止垃圾收集,并最终导致内存泄漏。
给出一个示例源代码段:
public interface Inner {
void innerAction();
}
public class Outer {
public void methodInOuter() {}
private Inner inner = new Inner() {
public void innerAction() {
// calling a method outside of scope of this anonymous class
methodInOuter();
}
}
}
编译时会发生什么,是编译器为Inner的新的匿名子类创建一个类文件,它获得一个所谓的合成字段,引用Outer类的实例。生成的字节码大致相当于这样:
public class Outer$1 implements Inner {
private final Outer outer; // synthetic reference to enclosing instance
public Outer$1(Outer outer) {
this.outer = outer;
}
public void innerAction() {
// the method outside of scope is called through the reference to Outer
outer.methodInOuter();
}
}
即使对于从不实际访问任何方法或包含类的字段(例如问题中的双括号初始化(DBI)列表)的匿名类,也捕获对包含实例的引用。
这导致以下事实:DBI列表保持对包围实例的引用,只要它存在,防止封闭实例被垃圾收集。假设DBI列表恰好在应用程序中存在很长时间,例如作为MVC模式中的模型的一部分,并且捕获的封闭类是例如JFrame,这是具有大量字段的相当大的类。如果你创建了几个DBI列表,你会很快得到内存泄漏。
一个可能的解决方案是仅在静态方法中使用DBI,因为在它们的作用域中没有这样的封闭实例。
另一方面,我仍然认为在大多数情况下使用DBI仍然没有必要。至于列表加入,我会创建一个简单的可重用的方法,这不仅更安全,而且更简洁明了。
public static List join(List extends T> first, List extends T> second) {
List joined = new ArrayList<>();
joined.addAll(first);
joined.addAll(second);
return joined;
}
然后客户端代码变得简单:
List newList = join(listOne, listTwo);