[...]
You could probably get creative and try to implement the Singleton pattern or some other construct that limits the number of objects created, but doing so would mean the constructors would need to be private/hidden, which prevents extension by other classes. Use of a static field to track number of creations might prevent the error, but then you'd lose all the references without having a place to keep track of them all, and lastly, you'd never have a "perfect" composition, because one class would be missing its component.
[...]
-- Ryan J (accepted answer)
I randomly found this and just for fun I thought of the following structure to make it work:
public class A{
private B myB; // ensure nobody can set this to an instance that does not reference back to here (or null after construction)
String name = "A";
public A(int num){
this.name += num;
this.myB = new B(num, this);
}
public A(int num, B lnk){
if(lnk.getMyA() != null) throw new java.security.InvalidParameterException("B already has an A");
this.name += num;
this.myB = lnk;
}
public B getMyB(){ return this.myB; }
public String getName(){ return this.name; }
}
and
public class B{
private A myA; // ensure nobody can set this to an instance that does not reference back to here (or null after construction)
String name = "B";
public B(int num){
this.name += num;
this.myA = new A(num, this);
}
public B(int num, A lnk){
if(lnk.getMyB() != null) throw new java.security.InvalidParameterException("A already has a B");
this.name += num;
this.myA = lnk;
}
public A getMyA(){ return this.myA; }
public String getName(){ return this.name; }
}
and testing with
public class CycleRefTest{
public static void main(String[] args){
A myA = new A(1);
B myB = new B(2);
try{ A errA = new A(3,myB); }catch(Exception e){ System.err.println(e.getMessage()); }
try{ B errB = new B(4,myA); }catch(Exception e){ System.err.println(e.getMessage()); }
System.out.println(myA == myA.getMyB().getMyA());
System.out.println(myB == myB.getMyA().getMyB());
System.out.println(myA.getMyB() == myA.getMyB().getMyA().getMyB());
System.out.println(myB.getMyA() == myB.getMyA().getMyB().getMyA());
System.out.println("A = " + myA.getName());
System.out.println("A>B = " + myA.getMyB().getName());
System.out.println("A>B>A = " + myA.getMyB().getMyA().getName());
System.out.println("B = " + myB.getName());
System.out.println("B>A = " + myB.getMyA().getName());
System.out.println("B>A>B = " + myB.getMyA().getMyB().getName());
}
}
will output
B already has an A
A already has a B
true
true
true
true
A = A1
A>B = B1
A>B>A = A1
B = B2
B>A = A2
B>A>B = B2
Both are part of each other; the connection is made within each constructor and can't be artificially created or modified (from outside).
But I don't know what the garbage collector thinks of this. There might be some edge cases I'm not aware of since I'm new to Java.