Подтвердить что ты не робот

Круговая зависимость в 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 не идентифицирует это, по крайней мере во время выполнения? Вместо метания StackOverflowError JVM может по крайней мере дать предупреждение перед началом выполнения.

[Обновить] Некоторые языки не могут иметь круговые зависимости, потому что исходный код не будет создан. Например, см. Этот вопрос и принятый ответ. Если круговая зависимость является дизайнерским запахом для С#, то почему это не для Java? Только потому, что Java может (компилировать код с круговыми зависимостями)?

[update2] Недавно найдено jCarder. Согласно веб-сайту, он находит потенциальные взаимоблокировки путем динамического кодирования байт-кодов Java и поиска циклов в графе объектов. Может ли кто-нибудь объяснить, как инструмент находит циклы?

4b9b3361

Ответ 1

Конструктор вашего класса A вызывает конструктор класса B. Конструктор класса B вызывает конструктор класса A. У вас есть бесконечный вызов рекурсии, поэтому у вас есть StackOverflowError.

Java поддерживает наличие круговых зависимостей между классами, проблема здесь связана только с конструкторами, вызывающими друг друга.

Вы можете попробовать что-то вроде:

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

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

Ответ 2

Его вполне корректно в Java, чтобы иметь круговую связь между двумя классами (хотя вопросы могут быть заданы по дизайну), однако в вашем случае у вас есть необычное действие каждого экземпляра, создающего экземпляр другого в его конструкторе (это являясь фактической причиной StackOverflowError).

Этот конкретный шаблон известен как взаимная рекурсия, в которой у вас есть 2 метода A и B (конструктор - это скорее всего частный случай метода), а A вызывает вызовы B и B A. Обнаружение бесконечного цикла во взаимосвязи между этими В тривиальном случае (тот, который вы предоставили) возможно 2 метода, но их решение для генерала сродни решению проблемы остановки. Учитывая, что решение проблемы с остановкой невозможно, обычно устроители не утруждают себя попытками даже для простых случаев.

Возможно, можно будет покрыть несколько простых случаев, используя шаблон FindBugs, но это было бы неверно для всех случаев.

Ответ 4

Если у вас действительно есть пример использования, вы можете создавать объекты по требованию (лениво) и использовать 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()

Ответ 5

Аналогичное обходное решение для геттеров/сеттеров, использующих композицию и инъекцию конструктора для зависимостей. Следует отметить, что объекты не создают экземпляр для других классов, они передаются (ака инъекции).

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