Java构造函数中的循环依赖

Java构造函数中的循环依赖,java,constructor,circular-dependency,Java,Constructor,Circular Dependency,我有以下课程 public class B { public A a; public B() { a= new A(); System.out.println("Creating B"); } } 及 可以清楚地看到,类之间存在循环依赖关系。如果我尝试运行类A,我最终会得到一个stackoverflowerrror 如果创建了一个依赖关系图,其中节点是类,那么可以很容易地识别该依赖关系(至少对于节点很少的图)。那么,为什么JV

我有以下课程

public class B 
{
    public A a;

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

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

如果创建了一个依赖关系图,其中节点是类,那么可以很容易地识别该依赖关系(至少对于节点很少的图)。那么,为什么JVM至少在运行时没有识别出这一点呢?JVM至少可以在开始执行之前发出警告,而不是抛出堆栈溢出错误

[Update]某些语言不能具有循环依赖项,因为这样源代码将无法生成。例如,和公认的答案。如果循环依赖是C#的设计风格,那么为什么它不适用于Java?仅仅因为Java可以(编译具有循环依赖关系的代码)


[update2]最近发现。据该网站称,它通过动态检测Java字节码并在对象图中查找周期来发现潜在的死锁。有人能解释一下该工具是如何找到周期的吗?

它不一定像您的示例中那样简单。我相信解决这个问题等于解决一个问题,我们都知道这是不可能的。

你的类A的构造函数调用类B的构造函数。类B的构造函数调用类A的构造函数。你有一个无限递归调用,这就是为什么你最终会有一个
堆栈溢出错误

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

您可以尝试以下方法:

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

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

在Java中,两个类之间有一个循环关系是完全有效的(尽管可能会对设计提出问题),但是在您的例子中,每个实例都会在其构造函数中创建另一个实例的一个实例,这是StackOverflower错误的实际原因

这种特殊的模式称为相互递归,其中有两个方法a和B(构造函数通常只是一个方法的特例),a调用B和B调用a。在平凡的情况下(您提供的情况下),检测这两个方法之间关系中的无限循环是可能的,但是,为将军解决这个问题就像解决停滞不前的问题。考虑到解决停顿问题是不可能的,即使是简单的情况,编译器通常也不会费心去尝试


使用模式可以覆盖一些简单的情况,但并不适用于所有情况。

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


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

为什么你希望得到警告呢?你有没有在什么地方读到JVM会为你做这件事?对于开发人员来说,这类问题很容易检测出来,而且首先要解决。JVM倾向于警告一些您不容易检测到的问题,例如损坏的类文件。我喜欢5个答案中只有2个(截至我写这篇文章时)真正回答您的问题:
为什么编译器不检测并警告潜在的问题
。这两个人都不是得票最多的人(同样,至少在我写这篇文章的时候是这样)。@BertF:七年后,仍然是这样。当时谁选择了被接受的答案?但这不坏吗?如果代码中确实存在循环依赖项(如您给出的示例),这不是糟糕设计的指标吗?如果是,那么为什么java支持它?如果没有,那么您能告诉我一些涉及循环依赖的设计是首选的情况吗?生产者/消费者或任何回调/事件情况如何?类似的事情最终会发生,尽管可能并不完全取决于两个类之间的关系。可能是接口。在这种情况下,“依赖性”的含义并不清楚。依赖关系通常转换为
X
需要在
Y
之前发生。举一个实际例子:例如,您可以将
a
重命名为
Parent
,将
B
重命名为
Child
。这种关系在计算中经常出现(父类需要知道它的子类是什么,
子类需要知道父类是谁),例如,在处理XML文档时(子标记包含在父标记中等),这并不能回答问题(关于为什么在运行前没有通过适当的工具检测到问题)。我同意,确定复杂情况下是否存在循环依赖可能是不可行的。但是,我们可以近似,在这种情况下,JVM可能只是表明存在潜在的循环依赖。然后开发人员可以查看代码。我认为大多数情况下,编译器可以在合理的时间内通过近似检测到e直接跳到你身上(就像你问题中的例子)。将此作为一项要求将使编写编译器变得非常困难,但收获很少。JVM正通过堆栈限制接近此测试。具有无休止堆栈的更健壮的执行模型将无法停止;堆栈帧限制是准确发现此类问题的理想机制。我认为您几乎永远不会遇到不是循环依赖的堆栈溢出。我看不出这是如何解决问题的,因为您依赖于B的实现者在注入a时不调用B的构造函数中的a上的任何方法。这样做会导致在代理上调用方法,最终导致空指针异常,因为您需要代理
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.getB().getA().getB().getA()