43

我有以下课程。

public class B 
{
    public A a;

    public B()
    {
        a= new A();
        System.out.println("Creating B");
    }
}

public class A 
{
    public B b;

    public A()
    {
        b = new B();
        System.out.println("Creating A");
    }

    public static void main(String[] args) 
    {
        A a = new A();
    }
}

可以清楚地看到,类之间存在循环依赖关系。如果我尝试运行 A 类,我最终会得到一个StackOverflowError.

如果创建了一个依赖图,其中节点是类,则可以轻松识别这种依赖关系(至少对于具有少量节点的图)。那么为什么 JVM 至少在运行时没有识别出来呢?StackOverflowErrorJVM 至少可以在开始执行之前发出警告,而不是 throwing 。

[更新]某些语言不能有循环依赖,因为那样源代码将无法构建。例如,请参阅此问题和接受的答案。如果循环依赖是 C# 的一种设计味道,那么为什么它不是 Java 的呢?仅仅因为Java可以(编译具有循环依赖的代码)?

[update2]最近发现jCarder。根据该网站,它通过动态检测 Java 字节码并在对象图中查找循环来发现潜在的死锁。谁能解释该工具如何找到循环?

4

5 回答 5

40

A 类的构造函数调用 B 类的构造函数。B 类的构造函数调用 A 类的构造函数。你有一个无限递归调用,这就是为什么你最终得到一个StackOverflowError.

Java 支持类之间的循环依赖,这里的问题只与构造函数相互调用有关。

您可以尝试以下方法:

A a = new A();
B b = new B();

a.setB(b);
b.setA(a);
于 2010-09-05T13:04:20.120 回答
21

在 Java 中,在 2 个类之间建立循环关系是完全有效的(尽管可能会询问有关设计的问题),但是在您的情况下,您有每个实例在其构造函数中创建另一个实例的异常行为(这是实际的StackOverflowError 的原因)。

这种特定模式称为相互递归,其中您有 2 个方法 A 和 B(构造函数大多只是方法的一种特殊情况),并且 A 调用 B,B 调用 A。检测这两种方法之间的关系中的无限循环是在微不足道的情况下(您提供的情况下)是可能的,但是为一般情况解决它类似于解决停止问题。鉴于解决停机问题是不可能的,即使是简单的情况,编译器通常也不会费心去尝试。

使用FindBugs模式可能涵盖一些简单的情况,但并非对所有情况都是正确的。

于 2010-09-05T13:16:27.223 回答
12

它不一定像您的示例中那样简单。我相信解决这个问题将等于解决停机问题——众所周知——这是不可能的。

于 2010-09-05T13:03:56.567 回答
5

如果你真的有这样的用例,你可以按需(懒惰地)创建对象并使用 getter:

public class B 
{
    private A a;

    public B()
    {
        System.out.println("Creating B");
    }

    public A getA()
    {
      if (a == null)
        a = new A();

      return a;
    }
}

(对于类也是如此A)。因此,如果您执行以下操作,则只会创建必要的对象:

a.getB().getA().getB().getA()
于 2010-09-05T15:19:49.893 回答
1

使用组合和构造函数注入依赖项的 getter/setter 类似的解决方法。需要注意的重要一点是对象不会为其他类创建实例,它们是传入的(也称为注入)。

public interface A {}
public interface B {}

public class AProxy implements A {
    private A delegate;

    public void setDelegate(A a) {
        delegate = a;
    }

    // Any implementation methods delegate to 'delegate'
    // public void doStuff() { delegate.doStuff() }
}

public class AImpl implements A {
    private final B b;

    AImpl(B b) {
        this.b = b;
    }
}

public class BImpl implements B {
    private final A a;

    BImpl(A a) {
        this.a = a;
    }
}

public static void main(String[] args) {
    A proxy = new AProxy();
    B b = new BImpl(proxy);
    A a = new AImpl(b);
    proxy.setDelegate(a);
}
于 2010-09-05T14:56:07.297 回答