Java 构造函数中的循环依赖关系

2022-09-01 00:57:42

我有以下课程。

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

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

[更新2]最近发现的jCarder。根据该网站的说法,它通过动态检测Java字节代码并在对象图中查找周期来发现潜在的死锁。谁能解释一下该工具如何找到周期?


答案 1

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

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

你可以尝试使用如下方法:

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

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

答案 2

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

这种特殊的模式被称为相互递归,其中您有2个方法A和B(构造函数大多只是方法的特殊情况),A调用B,B调用A。 但是为将军解决它类似于解决停止问题。鉴于解决停止问题是不可能的,即使对于简单的情况,编译器通常也不会费心尝试。

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