How to keep a 1 to 1 relation consistent

159 Views Asked by At

Consider the following code in Lecturer.java. The setter in Lecture.java has the exact same form. Please also note that the attribute of interest is private.

public void setLecture(Lecture lecture) {
    if (this.lecture == lecture) return;

    if (this.lecture != null) {
        this.lecture.setLecturer(null);
    }

    this.lecture = lecture;

    if (this.lecture != null) {
        this.lecture.setLecturer(this);
    }
}

This results in an infinite loop when trying to set a null value that has previously not been null.

I can't believe it's this hard to keep a 1:1 relation consistent - but I just can't figure out how to do so. How would one solve this issue?

3

There are 3 best solutions below

7
On

Finally, here's your solution:

public class A {
    private B b;

    public void setB(B b) {
        if(this.b != null) {
            this.b.unsetA();
        }
        this.b = b;
        if(b != null && b.getA() != this) {
            this.b.setA(this);
        }
    }

    public void unsetB() {
        this.b = null;
    }

    public B getB() {
        return b;
    }

}

public class B {
    private A a;

    public void setA(A a) {
        if(this.a != null) {
            this.a.unsetB();
        }
        this.a = a;
        if(a != null && a.getB() != this) {
            this.a.setB(this);
        }
    }

    public void unsetA() {
        this.a = null;
    }

    public A getA() {
        return a;
    }

}

My Test Class:

public class Test {

    public static void main(String ... args) {
        A a1 = new A();
        B b1 = new B();
        A a2 = new A();
        B b2 = new B();

        checkForZeroOrTwoRelations(a1, a2, b1, b2);
        a1.setB(b1);
        checkForZeroOrTwoRelations(a1, a2, b1, b2);     
        b1.setA(a2);
        checkForZeroOrTwoRelations(a1, a2, b1, b2);
        b2.setA(a2);
        checkForZeroOrTwoRelations(a1, a2, b1, b2);
        b2.setA(null);
        checkForZeroOrTwoRelations(a1, a2, b1, b2);
    }

    private static void checkForZeroOrTwoRelations(A a1, A a2, B b1, B b2) {
        int i = 0;
        if(a1.getB() != null) i++;
        if(a2.getB() != null) i++;
        if(b1.getA() != null) i++;
        if(b2.getA() != null) i++;
        if(i != 0 && i != 2) {
            throw new IllegalStateException();
        }
    }
}   
0
On

I post it as a different answer cause actually I think, my first answer is what you are looking for.

As for my proposal, you can create a two way map (alternatively you can use Guava HashBiMap) to ensure the relation integrity.

Map<Lecturer, Lecture> lecturerMap = new HashMap();
Map<Lecture, Lecturer> lectureMap = new HashMap();

public void addRelation(Lecturer lecturer, Lecture lecture) {

    this.checkIntegrity(lecturerMap,lectureMap, lecturer, lecture);
    this.checkIntegrity(lectureMap, lecturerMap, lecture, lecturer);

    lecturerMap.put(lecturer, lecture);
    lectureMap.put(lecture, lecturer);
}

private <T,V> void checkIntegrity(Map<T,V> mapf, Map<V,T> maps, T t, V v) {
    if (mapf.containsKey(t)) {
        V value = mapf.get(t);

        if (maps.containsKey(value)) {
            maps.remove(value);
        }

        mapf.remove(t);
    }
}

For sure there are more sophisticated ways to do, but this work as well.

Gist

0
On

You only have to set the object property to null in order to break the infinite loop. But first you have to store the previous value to a temporary variable. It will stop at the first if statement, where null equals null.

public void setLecture(Lecture lecture) {

    if (this.lecture == lecture) {
        return;
    }

    if (this.lecture != null) {

        Lecture pastLecture = this.lecture;
        this.lecture = null;

        pastLecture.setLecturer(null);

        pastLecture = null;

    }

    this.lecture = lecture;

    if (lecture != null) {
        lecture.setLecturer(this);
    }
}

Gist