Круговая зависимость в классах 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 может на хотя бы предупредите перед началом исполнения.
[обновление] некоторые языки не могут иметь круговых зависимостей, потому что тогда исходный код не будет строиться. Например, посмотреть здесь и принятый ответ. Если круговая зависимость-это запах дизайна для C# , то почему это не для Java? Только потому, что Java может (компилировать код с круговыми зависимостями)?
[обновление 2] недавно нашел jCarder. Согласно веб-сайту, он находит потенциальные тупики, динамически инструментируя байтовые коды Java и ища циклы в графе объектов. Может ли кто-нибудь объяснить, как инструмент находит циклы?
6 ответов
конструктор вашего класса A вызывает конструктор класса B. конструктор класса B вызывает конструктор класса A. У вас есть Бесконечный вызов рекурсии, поэтому у вас есть StackOverflowError
.
Java поддерживает циклические зависимости между классами, проблема здесь связана только с конструкторами, вызывающими друг друга.
Вы можете попробовать что-то вроде:
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
его совершенно допустимо в Java иметь круговую связь между 2 классами (хотя вопросы могут быть заданы о дизайне), однако в вашем случае у вас есть необычное действие каждого экземпляра, создающего экземпляр другого в его конструкторе (это является фактической причиной StackOverflowError).
этот конкретный шаблон известен взаимной рекурсией, где у вас есть 2 метода A и B (конструктор в основном является частным случаем метода) и A вызывает B и B вызовы A. обнаружение бесконечного цикла в связи между этими 2 методами возможно в тривиальном случае (тот, который вы предоставили), но решение его для общего сродни решению проблемы остановки. Учитывая, что решение проблемы остановки невозможно, нарушителей, как правило, не пытайтесь даже для простых случаев.
можно было бы охватить несколько простых случаев, используя в FindBugs шаблон, но это было бы неправильно для всех случаев.
Это не обязательно так просто, как в вашем примере. Я считаю, что решение этой проблемы будет равносильно решению проблема останова
Если у вас действительно есть такой вариант использования, вы можете создавать объекты по требованию (лениво) и использовать геттер:
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()
аналогичное обходное решение для геттеров / сеттеров с использованием композиции и инъекции конструктора для зависимостей. Важно отметить, что объекты не создают экземпляр для других классов, они передаются (он же инъекция).
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);
}
пожалуйста, взгляните на мою статью в http://java.dzone.com/articles/tackling-circular-dependency
Я думаю, это развеет ваши сомнения...