sirksel
sirksel

Reputation: 787

How to implement a validating constructor in Kotlin?

I'm implementing vars in Kotlin that could receive out-of-bounds input from certain of their users. I'd like to be able to optionally call (only on input from these untrusted users) a validating constructor that can return null, and then use the Elvis operator to specify defaults in the case of invalid input, as in: var myFoo = Foo.fromInt(i) ?: 1. I'm currently using a companion object method to validate the input:

open class Foo(val x:Int) {   // relies on valid x in 1..3
  companion object {
    fun fromInt(x: Int): Foo? = if (x in 1..3) Foo(x) else null
  }
}
class Bar(x:Int) : Foo(x)

fun main(args:Array<String>) {
  println(Foo.fromInt(2))    // Foo(2)
  println(Foo.fromInt(20))   // null
  // println(Bar.fromInt(2))
  // would like Bar(2) but as expected, companion isn't inherited
}

When I subclass Foo, I know I have to recreate the companion object. I've tried inheriting the companion from an abstract class; however, the call to Foo(x) still points to Foo and not Bar, unless I override fromInt in each subclass' companion. Is there a better or more Kotlin-idomatic way to deal with this kind of pattern of a validating constructor that can return null rather than creating the requested object?

Upvotes: 10

Views: 11599

Answers (3)

smac89
smac89

Reputation: 43206

You can use the init block as well as require.

open class Foo(val x:Int) {
  init {
    require(x in 1..3) {
      "relies on valid x in 1..3"
    }
  }
}
class Bar(x:Int) : Foo(x)

Example run:

>>> open class Foo(val x:Int) {
...   init {
...     require(x in 1..3) {
...       "relies on valid x in 1..3"
...     }
...   }
... }
>>> class Bar(x:Int) : Foo(x)
>>> val b = Bar(3)
>>> val b = Bar(4)
java.lang.IllegalArgumentException: relies on valid x in 1..3
    at Line_18$Foo.<init>(Line_18.kts:3)
    at Line_19$Bar.<init>(Line_19.kts:1)
>>> b.x
res22: kotlin.Int = 3

Upvotes: 21

Android14
Android14

Reputation: 1125

You could also look for "overloading invoke() function for constructor validation".

class Student private constructor(val rollNo: Int) {
companion object {
    operator fun invoke(rollNo: Int): Student {
        //Do your validation here
        if (rollNo < 1) {
            throw Exception() // or any default Student
        } else
            return Student(rollNo)
    }
}

}

val student1 =Student(4)
val student2 =Student(-1) // will throw exception

This approach hides the constructor(private) and because of the overloaded syntax it looks like we are making a constructor call .

Upvotes: 5

s1m0nw1
s1m0nw1

Reputation: 82017

could you simply use a single constructor and always validate its argument x and assign it to a property like here:

open class Foo (x: Int) {   // relies on valid x in 1..3
    val x: Int = if (x in 1..3) x else 1
}

class Bar(x: Int) : Foo(x) 

Now calling the constructor with a value that is not in range, a default Foo is created, which is basically what you wanted to have I think. In my opinion it would be better if you threw a IllegalArgumentException instead of creating a default behind the scenes. What do you think?

Upvotes: 7

Related Questions