StackOverflowError on kotlin properties setter

62 Views Asked by At

Once I have a class like bellow:

class Mutex {

  var a: String? = null
    set(a) {
      this.b = null
      field = a
    }
  var b: String? = null
    set(b) {
      this.a = null
      field = b
    }
}

While I call any of properties setter mutex.a = "A" or mutex.b = "B" will cause StackOverflowError, I knew that it's recusive invoke problem. How to avoid these language level syntax issue because It doesn't contains this.b.field = null sentence.

3

There are 3 best solutions below

2
jun On

The StackOverflowError occurs because setting a automatically sets b to null, which in turn sets a to null again, creating an infinite recursion. To avoid this, you can introduce a private backing field to control when the setter should actually modify the other property, thus breaking the recursion.

class Mutex {
    private var _a: String? = null
    var a: String?
        get() = _a
        set(value) {
            if (_a != value) {
                _b = null
                _a = value
            }
        }
    
    private var _b: String? = null
    var b: String?
        get() = _b
        set(value) {
            if (_b != value) {
                _a = null
                _b = value
            }
        }
}
0
Sweeper On

You can check if the new value is null. Only when it is non-null, do you set the other property to null, hence avoiding the infinite recursion.

class Mutex {

    var a: String? = null
        set(a) {
            field = a?.also { b = null }
        }
    var b: String? = null
        set(b) {
            field = b?.also { a = null }
        }
}

This means setting a or b to null will not also set the other property to null, which may or may not be desirable:

val m = Mutex()
m.a = "Foo"
m.b = null // this will not set m.a to null

If you want this behaviour, use a separate backing property as jun's answer suggests.

0
Tenfour04 On

You could check if the other property is null before setting it to null.

class Mutex {
    var a: String? = null
        set(a) {
            field = a
            if (b != null) b = null
        }
    var b: String? = null
        set(b) {
            field = b
            if (a != null) a = null
        }
}