Dépendance circulaire dans les classes java
j'ai les cours suivants.
public class B
{
public A a;
public B()
{
a= new A();
System.out.println("Creating B");
}
}
et
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();
}
}
comme on peut le voir clairement, il existe une dépendance circulaire entre les classes. si j'essaie de diriger la classe A, j'obtiens finalement un StackOverflowError
.
si un graphe de dépendance est créé, où les noeuds sont des classes, alors cette dépendance peut être facilement identifiée (au moins pour les graphiques avec peu de noeuds). Alors pourquoi la JVM ne l'identifie-t-elle pas? runtime? Au lieu d'un lancer StackOverflowError
, JVM peut au moins donner un avertissement avant de commencer l'exécution.
[Update] certaines langues ne peuvent pas avoir des dépendances circulaires, parce que le code source ne sera pas construit. Par exemple, voir cette question et la réponse acceptée. Si la dépendance circulaire est une odeur de design pour C#, alors pourquoi N'est-ce pas pour Java? Seulement parce que Java peut (compiler du code avec des dépendances circulaires)?
[update2] Récemment trouvé jCarder . Selon le site, il trouve des blocages potentiels en instrumentant dynamiquement les codes Java byte et en recherchant des cycles dans le graphe objet. Quelqu'un peut-il expliquer comment l'outil trouver les cycles?
6 réponses
le constructeur de votre classe A appelle le constructeur de la classe B. Le constructeur de la Classe B appelle le constructeur de la classe A. Vous avez un appel de récursion infini, c'est pourquoi vous finissez par avoir un StackOverflowError
.
les supports Java ayant des dépendances circulaires entre les classes, le problème ici n'est lié qu'aux constructeurs qui s'appellent les uns les autres.
vous pouvez essayer avec quelque chose comme:
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
son parfaitement valide en Java d'avoir une relation circulaire entre 2 classes (bien que des questions pourraient être posées sur le design), cependant dans votre cas vous avez l'action inhabituelle de chaque instance créant une instance de l'autre dans son constructeur (ce qui est la cause réelle de la Stackoverfowerror).
Ce modèle particulier est connu une récursion mutuelle où vous avez 2 méthodes A et B (Un constructeur est la plupart du temps juste un cas spécial d'une méthode) et un appels B et b appels A. détecter une boucle infinie dans la relation entre ces 2 méthodes est possible dans le cas trivial (celui que vous avez fourni), mais le résoudre pour le général est similaire à la résolution du problème d'arrêt. Étant donné qu'il est impossible de résoudre le problème de l'arrêt, les responsables ne prennent généralement pas la peine d'essayer, même pour les cas simples.
il pourrait être possible de couvrir quelques cas simples en utilisant un modèle FindBugs , mais il ne serait pas correct pour tous les cas.
Il n'est pas nécessairement aussi facile que dans votre exemple. Je crois que résoudre ce problème équivaudrait à résoudre le halting problem qui, comme nous le savons tous, est impossible.
si vous avez vraiment un cas d'utilisation comme celui-ci, vous pouvez créer les objets à la demande (paresse) et utiliser un 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;
}
}
(et de même pour la classe A
). Ainsi, seuls les objets nécessaires sont créés si vous faites par exemple:
a.getB().getA().getB().getA()
une solution similaire aux getters/setters utilisant la composition et l'injection du constructeur pour les dépendances. La grande chose à noter est que les objets ne créent pas l'instance aux autres classes, ils sont passés dans (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);
}
s'il vous Plaît jeter un oeil à mon article sur http://java.dzone.com/articles/tackling-circular-dependency
je pense que ça dissipera vos doutes...