ti-enxame.com

Dependência circular nas classes Java

Eu tenho as seguintes aulas.

public class B 
{
    public A a;

    public B()
    {
        a= new A();
        System.out.println("Creating B");
    }
}

e

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

Como pode ser visto claramente, há uma dependência circular entre as classes. se eu tentar executar a classe A, eventualmente recebo um StackOverflowError.

Se um gráfico de dependência for criado, onde os nós são classes, essa dependência poderá ser facilmente identificada (pelo menos para gráficos com poucos nós). Então, por que a JVM não identifica isso, pelo menos em tempo de execução? Em vez de um StackOverflowError de lançamento, a JVM pode pelo menos emitir um aviso antes de iniciar a execução.

[Update] Alguns idiomas não podem ter dependências circulares, porque o código fonte não será construído. Por exemplo, consulte esta pergunta e a resposta aceita. Se a dependência circular é um cheiro de design para C #, por que não é para Java? Somente porque Java pode (compilar código com dependências circulares)?

[update2] Encontrado recentemente jCarder . De acordo com o site, ele encontra possíveis impasses ao instrumentar dinamicamente códigos de bytes Java Java e procurar ciclos no gráfico de objetos. Alguém pode explicar como a ferramenta encontra os ciclos?

33
athena

O construtor da sua classe A chama o construtor da classe B. O construtor da classe B chama o construtor da classe A. Você tem uma chamada de recursão infinita, é por isso que você acaba tendo um StackOverflowError.

Java suporta ter dependências circulares entre classes, o problema aqui está relacionado apenas aos construtores que se chamam.

Você pode tentar algo como:

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

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

É perfeitamente válido em Java ter um relacionamento circular entre duas classes (embora possam ser feitas perguntas sobre o design), no entanto, no seu caso, você tem a ação incomum de cada instância criando uma instância do outro em seu construtor (essa é a causa real do StackOverflowError).

Esse padrão em particular é conhecido como recursão mútua, em que você tem 2 métodos A e B (um construtor é principalmente apenas um caso especial de um método) e A chama B e B chama A. A detecção de um loop infinito na relação entre esses 2 métodos é possível no caso trivial (o que você forneceu), mas resolvê-lo para o general é como resolver o problema da parada. Dado que a solução do problema de parada é impossível, os complementadores geralmente não se incomodam em tentar, mesmo nos casos simples.

Pode ser possível cobrir alguns casos simples usando um padrão FindBugs , mas não seria correto para todos os casos.

14
Michael Barker

Não é necessariamente tão fácil quanto no seu exemplo. Acredito que resolver esse problema seria igual a resolver o problema parada que - como todos sabemos - é impossível.

11
musiKk

Se você realmente tiver um caso de uso como esse, poderá criar os objetos sob demanda (preguiçosamente) e usar um 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;
    }
}

(e da mesma forma para a classe A). Portanto, apenas os objetos necessários serão criados se, por exemplo, Faz:

a.getB().getA().getB().getA()
3
Andre Holzner

Uma solução semelhante aos getters/setters usando composição e injeção de construtor para as dependências. O importante a ser observado é que os objetos não criam a instância para as outras classes, eles são passados ​​(também conhecido como injeção).

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