web-dev-qa-db-ja.com

Javaクラスの循環依存関係

次のクラスがあります。

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 can(循環依存関係でコードをコンパイルする)?

[update2]最近見つかった jCarder 。 Webサイトによると、Javaバイトコードを動的に計測し、オブジェクトグラフでサイクルを探すことにより、潜在的なデッドロックを見つけます。ツールがどのようにサイクルを見つけるか説明できますか?

33
athena

クラスAのコンストラクターはクラスBのコンストラクターを呼び出します。クラスBのコンストラクターはクラスAのコンストラクターを呼び出します。無限再帰呼び出しがあるため、最終的にStackOverflowErrorになります。

Javaはクラス間の循環依存関係をサポートします。ここでの問題は、お互いを呼び出すコンストラクターにのみ関連しています。

次のようなものを試すことができます:

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

a.setB(b);
b.setA(a);
32
Vivien Barousse

Javaは2つのクラス間で循環関係を持つために(設計について質問することができますが))で完全に有効ですが、あなたの場合、各インスタンスの異常なアクションがあり、その他のコンストラクター(これがStackOverflowErrorの実際の原因です)。

この特定のパターンは、2つのメソッドAとB(コンストラクターはほとんどメソッドの特別なケースです)があり、BとBがAを呼び出す相互再帰として知られています。これら2つのメソッド間の関係で無限ループを検出するのは些細なケース(あなたが提供したもの)では可能ですが、一般的な問題を解決することは、停止する問題を解決することに似ています。停止する問題を解決することは不可能であることを考えると、コンパイラーは一般に単純な場合でも試行することを気にしません。

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

14
Michael Barker

例のように必ずしも簡単ではありません。この問題を解決することは、 halting problem を解決することと等しいと信じています。

11
musiKk

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

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()
3
Andre Holzner

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

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