Referência circular

7 respostas
D

Pessoal

Tenho um objeto A, que acessa métodos de outro objeto B. Porém, este objeto B também precisa da referência de A.

Sempre li que deve-se evitar isto:

class A {

   private B b;

   public A(){
      this.b = new B(this);
   }
}

class B {

   private A a;

   public B(A a){
      this.a = a;
   }
}

Como posso fazer neste caso?
Em alguns lugares, li que é melhor passar a referência do this em um método set e não no construtor, mas isto não dá na mesma?

7 Respostas

B

Não vejo problema algum no seu código, inclusive isso aí é feito para navegação em persistências de tabela, tipo:

class TabelaPai{
    private TabelaFilha tf;
    public getTF(){return tf;}
}

class TabelaFilha{
   private TabelaPai tp;
  public TabelaFilha(TabelaPai tp){this.tp = tp}
}

Além disso ser utilizado tb em listas duplamente encadeadas...

o q deve ser evitado é chamadas que possam resultar em deadlock, tipo: TP chama TF que dentro do próprio método chama TP d novo q chama TF....

V

Isso deve ser evitado pq B receberá uma instância de um A que não foi completamente construido. Se seu construtor se resumir a fazer essa atribuição, então, ok, passa.

Se não, você teria que ter um método fábrica:

public class A {
   private A() {
   }

   private void setB(B b) {
      this.b = b;
   }

   public static A create() {
      A a = new A();
      B b = new B(a);
      a.setB(b);
      return a;
   }
}
D

Obrigado pelas respostas, pessoal.

Só fiquei com uma dúvida:

ViniGodoy:
Isso deve ser evitado pq B receberá uma instância de um A que não foi completamente construido. Se seu construtor se resumir a fazer essa atribuição, então, ok, passa.

Se não, você teria que ter um método fábrica:

public class A {
   private A() {
   }

   private void setB(B b) {
      this.b = b;
   }

   public static A create() {
      A a = new A();
      B b = new B(a);
      a.setB(b);
      return a;
   }
}

Mesmo que a instância de A não esteja completamente construída quando eu inicializar B, eu vou inicializar B passando uma referência para esta instância de A. Neste caso, mesmo que eu continua construindo A depois de inicializar B, a referência continuará sendo a mesma, o que não geraria problemas, não?

Por exemplo:

class A {

   private B b;
   private String str1;

   public String getStr1(){
      return str1;
   }

   public A(){
      this.b = new B(this); 
      // neste momento, estou inicializando B, e a variável str1 de A está nula

      str1 = "123";
      // neste momento, atualizei o valor de str1 para a String "123". 
      // levando em conta que a referência de b para este A ainda é a mesma, se eu chamar getStr1() de b, o retorno vai ser "123", não?
   }
}

class B {

   private A a;

   public B(A a){
      this.a = a;
   }
}

Ou existe algum outro problema que pode ser gerado?

S

diegogmarques:
Pessoal

Tenho um objeto A, que acessa métodos de outro objeto B. Porém, este objeto B também precisa da referência de A.

Sempre li que deve-se evitar isto:

class A {

   private B b;

   public A(){
      this.b = new B(this);
   }
}

class B {

   private A a;

   public B(A a){
      this.a = a;
   }
}

Como posso fazer neste caso?
Em alguns lugares, li que é melhor passar a referência do this em um método set e não no construtor, mas isto não dá na mesma?

Se x= 2, e y =3 como trocar os valores ?

A resposta é : vc precisa de um terceiro cara.
Em objetos isso é um proxy.

Vc cria uma implementação de A, ProxyA que contém A, mas começa vazio

ai vc faz

ProxyA pa = new ProxyA()

como ProxyA herda A, vc faz

B b = new B(pa);

B está agora complementa construido e inicializado.
Agora vc faz

A a = new A(b);

A está agora completamente construido.

finalmente vc amarra as pontas

pa.setA(a);

Agora vc pode chamar métodos de B porque eles irão para em A.
Versões mais sofisticadas podem ser feitas, em particular fazer ou utilziar um injeto de dependencia que consiga lidar com circularidade.

B

Sabe, eu gosto d desafios e não pude deixar de tentar…

A resposta é: XOR 3x

public static void permuta() { int x = 2; int y = 3; System.out.format("x= %s: y= %s\n", x, y); x = x ^ y; y = x ^ y; x = x ^ y; System.out.format("x= %s: y= %s\n", x, y); }

V
diegogmarques:
Mesmo que a instância de A não esteja completamente construída quando eu inicializar B, eu vou inicializar B passando uma referência para esta instância de A. Neste caso, mesmo que eu continua construindo A depois de inicializar B, a referência continuará sendo a mesma, o que não geraria problemas, não?

E porque teria? O problema não está necessariamente em cruzar referências, mas na forma como isso é feito, ou seja, em passar um objeto A parcialmente construído para B, na hora de fazer isso.

Não entendi o seu exemplo. O getStr() refere-se a uma variável local, e não faz qualquer menção com a referência. O que ele tem a ver com A?

No caso, você não deve passar o this como parâmetro numa construção por causa disso aqui:

public class A {
   private int a;
   private String x;
   private B b;

   public A() {
       b = new B(this);
       a = 10;
       x = "Vinicius";
    }

    //gets e sets
}


public class B {
    private int c;

    public void B(A a) {
        c = a.getA() * 2;
    }

    //gets e sets
}

Pergunta-se. Qual é o valor que método getA() retornará, quando B fizer a chamada?

A coisa pode ficar ainda pior se A tiver subclasses e B chamar um método sobrescrito de A. Por exemplo, suponha que você tenha "corrigido" a classe A, e colocado a inicialização de B na última linha. Aí fica correto, certo?
public class A {
   private int a;
   private String x;
   private B b;

   public A() {
       a = 10;
       x = "Vinicius";
       b = new B(this);
    }

    //gets e sets
}

Agora, imagine que posteriormente alguém cria a classe C, dessa forma:

public class C extends A {
   private int x;
   public C() {
     this.x = 20;
   }

   public void getA() {
      return super.getA() * x;
   }
}
O que acontece quando criamos os objetos assim?
A a = new C();

Note que agora o construtor de B irá novamente tentar chamar o método getA(). Aparentemente está tudo certo, mas o método sobrescrito precisará do valor de x, que não foi inicializado ainda, já que a construção ocorre de cima para baixo e o construtor da classe C, portanto, não foi chamado.

É uma má prática passar objeto incompleto para B, porque quem quer que programe B deve ter como pressuposto que A é um objeto válido, e completamente construído. Quando você troca referências da forma que você mostrou, esse pressuposto é inválido, o que pode gerar erros de programação difíceis de corrigir. O comportamento do Java nessas situações é indefinido.

S

barenko:
sergiotaborda:

Se x= 2, e y =3 como trocar os valores ?

A resposta é : vc precisa de um terceiro cara.

Sabe, eu gosto d desafios e não pude deixar de tentar…

A resposta é: XOR 3x

public static void permuta() { int x = 2; int y = 3; System.out.format("x= %s: y= %s\n", x, y); x = x ^ y; y = x ^ y; x = x ^ y; System.out.format("x= %s: y= %s\n", x, y); }

Leia o Efective Java e Java Puzzles para ver pq isso é uma horrivel ideia.

Criado 16 de outubro de 2009
Ultima resposta 21 de out. de 2009
Respostas 7
Participantes 4