匿名 class 和父 class 之间的循环依赖是否错误?

Is cyclic dependency between anonymous class and parent class wrong?

我有以下代码片段:

public class Example {

private Integer threshold;

private Map<String, Progress> history;

protected void activate(ComponentContext ctx) {
    this.history = Collections.synchronizedMap(new LinkedHashMap<String, Progress>() {
        @Override
        protected boolean removeEldestEntry(Map.Entry<String, Progress> entry) {
            return size() > threshold;
        }
    });
  }
}

匿名 LinkedHashMap class 和 Example class 之间存在循环依赖。这可以吗?为什么不?它会被垃圾收集器很好地回收吗?

Is this OK or not?

完全没问题。

threshold 是一个字段,因此可以在匿名 class 中毫无问题地引用它。 (如果 threshold 是局部变量,它必须(有效地)最终。)

classes 之间的循环依赖很常见,当依赖图很小时(如本例),它不会造成任何问题。您的 LinkedHashMap 是匿名 class 这一事实在这里无关紧要。

Is it going to be nicely reclaimed by garbage collector?

关于内存泄漏 + 内部 classes 唯一需要警惕的是(非静态)内部 class 有一个对其封闭对象的隐式引用。这意味着如果您创建了很多内部 class 的实例,您就不能指望外部 class 对象的实例被垃圾回收。

在这种情况下,这意味着如果您泄漏对 history 映射的引用,Example 的实例将不会被 GC。


相关说明:

  • 考虑到您正在使用 synchronizedMap,您似乎正在处理多线程程序。如果是这种情况,您需要警惕 threshold 字段的同步和可见性问题。

  • 如果可能,请尝试使 threshold 字段成为最终字段

  • 另一种选择是为您的 LinkedHashMap 创建一个命名的 class 并将 threshold 作为字段包含在 class 中。

循环依赖本身并不坏,但它可能会导致一些意想不到的内存泄漏。

以你的例子为例,它现在很好,因为它做了你想要它做的事情。

但是,如果您或其他人修改了您的代码以暴露您的隐私:

private Map<String, Progress> history;

那你可能有麻烦了。将会发生的是,您也会传递对示例 class 的引用,无论是否有意,因为您的内部 class 隐式引用了它。

我现在不能给你直接引用,但 Steve McConnell 在他的完整代码中将循环依赖称为反模式。您可以在那里阅读,或者我猜 google 可以详细阅读此内容。

我想到的另一个问题是,循环依赖很难进行单元测试,因为您在对象之间创建了非常高级别的耦合。

一般来说,你应该避免循环依赖,除非你有很好的理由不这样做,比如实现循环链​​表。

任何时候你实例化一个非静态内部 class(无论是命名的还是匿名的),这个内部 class' 实例会自动获取对封闭父实例的引用 class.

上面的意思是,如果外部class也持有对非静态内部class的引用(就像你的代码中的情况),那么实例之间存在循环依赖外部 class 和非静态内部 class (同样,命名和匿名)。

此设置中唯一实际的问题是您对现有交叉引用的使用是否合法。在您的特定情况下,我没有看到任何问题 - 非静态内部 class 使用封闭外部 class 的实例变量。对我来说似乎是犹太洁食。

在这种情况下,内存泄漏通常发生在对内部 class 实例的引用被传递到外部 class 的外部时(这通常是各种 Listeners) - 由于此实例引用了外部 class 的实例,因此无法对外部 class 进行垃圾回收。但是,我认为如果您只是交叉引用外部和内部 classes 不会导致内存泄漏 - 它们将一起被垃圾收集。

无论如何你都有这种依赖性,因为匿名内部 class 的每个对象都隐式引用了封闭 class 的对象。 Java 就是这样设计的,嵌套的内部 classes 有这个引用是有原因的,所以从语言规范的角度来看,这个编译看起来非常正常。

关于(不存在)"design smell",如果这个匿名 class 对象完全封装在 Example class 中,没有其封闭上下文就没有明显的意义,并且不会在 Example class 之外的任何地方泄漏,引用包含 class 的字段没有任何问题。您只需使用这个内部 class 来对一些逻辑进行分组。

但是,如果此对象从封闭对象中泄漏出来(例如,您通过 getter return 它),您应该禁止此操作或将其重构为静态内部 [=21] =] 接收 threshold 作为参数。此内部对象持有对封闭对象的引用,可能会阻止 GC,从而导致内存泄漏。

我不喜欢你的解决方案(即使我同意这可行):

  1. 您的 class 示例应该实现 Map 或扩展 LinkedHashMap,因为实例变量 threshold 在那里定义,并用自己的定义完善了 LinkedHashMap 的概念。

  2. 您的 class 示例不应实现 Map 或扩展 LinkedHashMap,因为 activate 方法不会优化 LinkedHashMap 或 Map,而是使用 Maps 的概念。

1+2 => 受孕问题