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がこれを識別しないのはなぜですか?スローする代わりにStackOverflowError、JVMは実行を開始する前に少なくとも警告を出すことができます。

[更新]一部の言語は、ソースコードがビルドされないため、循環依存関係を持つことができません。たとえば、この質問と受け入れられた回答を参照してください。循環依存がC#の設計上の臭いである場合、なぜJavaではないのでしょうか。Javaが(循環依存のコードをコンパイルする)ことができるという理由だけで?

[update2]最近見つかったjCarder。Webサイトによると、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を呼び出す相互再帰として知られています。これら2つのメソッド間の関係で無限ループを検出するのは些細な場合(あなたが提供したもの)で可能ですが、一般的にそれを解決することは、停止性問題を解決することに似ています。停止性問題を解くことは不可能であることを考えると、一般的に、コンパイラーは単純な場合でもわざわざ試みることはありません。

FindBugsパターンを使用していくつかの単純なケースをカバーすることは可能かもしれませんが、すべてのケースで正しいとは限りません。

于 2010-09-05T13:16:27.223 に答える
12

例のように必ずしも簡単ではありません。この問題を解くことは、私たち全員が知っているように、不可能である停止性問題を解くことに等しいと私は信じています。

于 2010-09-05T13:03:56.567 に答える
5

このようなユースケースが本当にある場合は、オンデマンドで(遅延して)オブジェクトを作成し、ゲッターを使用できます。

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

依存関係にコンポジションとコンストラクターインジェクションを使用するゲッター/セッターと同様の回避策。注意すべき重要な点は、オブジェクトは他のクラスへのインスタンスを作成せず、それらが渡されることです(別名インジェクション)。

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 に答える