java类中的循环依赖

时间:2010-09-05 12:53:03

标签: java circular-dependency

我有以下课程。

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不能识别这个,至少在运行时? JVM可以在开始执行之前至少发出警告,而不是抛出StackOverflowError

[更新] 某些语言不能具有循环依赖关系,因为这样就不会构建源代码。例如,see this question和接受的答案。如果循环依赖是C#的设计气味那么为什么它不适用于Java呢?只是因为Java可以(编译循环依赖的代码)?

[update2] 最近找到了jCarder。根据该网站,它通过动态检测Java字节代码并在对象图中查找周期来发现潜在的死锁。任何人都可以解释该工具如何找到周期?

5 个答案:

答案 0 :(得分:29)

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

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

您可以尝试使用以下内容:

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

a.setB(b);
b.setA(a);

答案 1 :(得分:14)

它在Java中完全有效,在2个类之间有一个循环关系(虽然可以询问有关设计的问题),但是在你的情况下,你有不同的动作,每个实例在其构造函数中创建另一个的实例(这是StackOverflowError的真正原因。)

这种特殊的模式是已知的相互递归,其中你有2个方法A和B(构造函数大多只是方法的特殊情况)和A调用B和B调用A.检测这些方法之间关系的无限循环在平凡的情况下(你提供的那种)可能有2种方法,但是对于一般情况来说解决它就像解决停止问题一样。鉴于解决暂停问题是不可能的,因此即使对于简单的情况,编制人员通常也不会费心。

有可能使用FindBugs模式覆盖一些简单的案例,但对所有情况都不正确。

答案 2 :(得分:11)

它不一定像你的例子那么容易。我相信解决这个问题就等于解决halting problem - 我们都知道 - 这是不可能的。

答案 3 :(得分:3)

如果您真的有这样的用例,您可以按需创建对象(懒惰)并使用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()

答案 4 :(得分:0)

使用合成和构造函数注入依赖项的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);
}