简体   繁体   English

Java 构造函数中的循环依赖

[英]Circular dependency in Java constructors

I have the following classes.我有以下课程。

public class B 
{
    public A a;

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

and

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();
    }
}

As can be clearly seen, there is a circular dependency between the classes.可以清楚地看到,类之间存在循环依赖关系。 if I try to run class A, I eventually get a StackOverflowError .如果我尝试运行 A 类,我最终会得到StackOverflowError

If a dependency graph is created, where nodes are classes, then this dependency can be easily identified (at least for graphs with few nodes).如果创建了一个依赖关系图,其中节点是类,那么这个依赖关系很容易被识别(至少对于节点很少的图)。 Then why does the JVM not identify this, at least at runtime?那么为什么 JVM 不识别这一点,至少在运行时是这样? Instead of a throwing StackOverflowError , JVM can at least give a warning before starting execution. JVM 至少可以在开始执行之前发出警告,而不是抛出StackOverflowError

[Update] Some languages cannot have circular dependencies, because then the source code will not build. [更新]某些语言不能有循环依赖,因为这样源代码将无法构建。 For example, see this question and the accepted answer.例如, 请参阅此问题和已接受的答案。 If circular dependency is a design smell for C# then why is it not for Java?如果循环依赖是 C# 的一种设计味道,那么为什么不是 Java? Only because Java can(compile code with circular dependencies)?仅仅因为 Java 可以(编译具有循环依赖的代码)?

[update2] Recently found jCarder . [update2]最近发现jCder According to the website, it finds potential deadlocks by dynamically instrumenting Java byte codes and looking for cycles in the object graph.据该网站称,它通过动态检测 Java 字节代码并在对象图中寻找循环来发现潜在的死锁。 Can anyone explain how does the tool find the cycles?谁能解释该工具如何找到循环?

The constructor of your class A calls the constructor of class B. The constructor of class B calls the constructor of class A. You have an infinite recursion call, that's why you end up having a StackOverflowError .你的类 A 的构造函数调用类 B 的构造函数。类 B 的构造函数调用类 A 的构造函数。你有一个无限递归调用,这就是为什么你最终有一个StackOverflowError

Java supports having circular dependencies between classes, the problem here is only related to constructors calling each others. Java 支持类之间的循环依赖,这里的问题只与构造函数相互调用有关。

You can try with something like:您可以尝试使用以下方法:

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

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

Its perfectly valid in Java to have a circular relationship between 2 classes (although questions could be asked about the design), however in your case you have the unusual action of each instance creating an instance of the other in its constructor (this being the actual cause of the StackOverflowError).在 Java 中,在 2 个类之间建立循环关系是完全有效的(尽管可能会提出有关设计的问题),但是在您的情况下,您有每个实例在其构造函数中创建另一个实例的异常操作(这是实际的StackOverflowError 的原因)。

This particular pattern is known a mutual recursion where you have 2 methods A and B (a constructor is mostly just a special case of a method) and A calls B and B calls A. Detecting an infinitely loop in the relationship between these 2 methods is possible in the trivial case (the one you've supplied), but solving it for the general is akin the solving the halting problem.这种特殊的模式被称为相互递归,其中有 2 个方法 A 和 B(构造函数通常只是方法的一个特例),A 调用 B 和 B 调用 A。检测这两个方法之间的关系中的无限循环是在微不足道的情况下(您提供的那个)是可能的,但是为一般情况解决它类似于解决停机问题。 Given that solving the halting problem is impossible, compliers generally don't bother trying even for the simple cases.鉴于解决停机问题是不可能的,即使对于简单的情况,编译器通常也不会费心去尝试。

It might be possible to cover a few of simple cases using a FindBugs pattern, but it would not be correct for all cases.使用FindBugs模式可能会涵盖一些简单的情况,但并非对所有情况都正确。

It isn't necessarily as easy as in your example.它不一定像您的示例中那样容易。 I believe solving this problem would be equal to solving the halting problem which -- as we all know -- is impossible.我相信解决这个问题就等于解决停机问题——众所周知——这是不可能的。

If you really have a use case like this, you could create the objects on demand (lazily) and use a getter:如果您真的有这样的用例,您可以按需(懒惰地)创建对象并使用 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;
    }
}

(and similarly for the class A ). (对于A类也类似)。 So only the necessary objects are created if when you eg do:因此,如果您执行以下操作,则只会创建必要的对象:

a.getB().getA().getB().getA()

A similar workaround to getters/setters using composition and and constructor injection for the dependencies.对依赖项使用组合和构造函数注入的 getter/setter 的类似解决方法。 The big thing to note is that the objects don't create the instance to the other classes, they are passed in (aka injection).需要注意的重要一点是,对象不会为其他类创建实例,而是传入(也称为注入)。

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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM