Skip to content

Instantly share code, notes, and snippets.

@lotharschulz
Last active October 6, 2020 18:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lotharschulz/ba2656ebf3278fc3b75a6c290ec87ed2 to your computer and use it in GitHub Desktop.
Save lotharschulz/ba2656ebf3278fc3b75a6c290ec87ed2 to your computer and use it in GitHub Desktop.
kotlin-null

Approaches how to deal with null and exceptions in Kotlin

Code examples for lotharschulz.info - How to: Nulls and Exceptions in Kotlin from github.com/lotharschulz/kotlin-null

Exception Handling

    fun reciprocal(i: Int): Double =
        when(i == 0){
            true -> throw IllegalArgumentException("Can not take reciprocal of 0.")
            false -> 1.0 / i
        }
    @Test(expected = IllegalArgumentException::class)
    fun testReciprocalException() {
        ExceptionStyle.reciprocal(ZERO_INT)
    }

Nothing

    fun parse(): Nothing = throw IllegalArgumentException("Can not parse anything.")
    fun reciprocal(): Nothing = throw IllegalArgumentException("Can not take reciprocal of anything.")
    @Test(expected = IllegalArgumentException::class)
    fun testParse(){
        NothingStyle.parse()
    }

    @Test(expected = IllegalArgumentException::class)
    fun testReciprocal(){
        NothingStyle.reciprocal()
    }

Unit

    fun callFunction(function: (Int) -> Unit): Unit = (1..2).forEach(function)
    fun reciprocal(){}
    private val printNumber = { x: Number -> println("Number: $x") }
    @Test
    fun testCallFunction(){
        assertEquals(UnitStyle.callFunction(printNumber), Unit)
    }
    @Test
    fun testReciprocal(){
        assertEquals(UnitStyle.reciprocal(), Unit)
    }

Any

    fun parse(s: String): Any =
        when(s.matches(Regex("-?[0-9]+"))){
            true -> s.toInt()
            false -> NumberFormatException("$s is not a valid integer.")
        }
    @Test
    fun testParse(){
        assertEquals(AnyStyle.parse("42"), 42, "42 expected")
        assertEquals(
            AnyStyle.parse("some string").toString(), 
            NumberFormatException("some string is not a valid integer.").toString(), 
            "some string can not be parsed"
        )
    }

Nullable

    fun parse(s: String): Int? =
        when(s.matches(Regex("-?[0-9]+"))){
            true -> s.toInt()
            false -> null
        }
    // https://kotlinlang.org/docs/reference/null-safety.html#elvis-operator
    @Test
    fun testParseElvisOperator(){
        val parseResult = NullableStyle.parse("some string") ?: -1
        assertEquals(parseResult, -1, "Elvis operator turns it into -1")
    }

    // https://kotlinlang.org/docs/reference/null-safety.html#the--operator !! - operator
    @Test
    fun testParseDoubleExclamationOperator(){
        val parseResultForSureNotNull = NullableStyle.parse("42")!!.div(42)
        assertEquals(parseResultForSureNotNull, 1, "42/42 must be one")
    }

LateInit

data class MyInt(val value: Int)

    private lateinit var toBeDefinedInt : MyInt
    fun parse(s: String): Int =
        when (s.matches(Regex("-?[0-9]+"))) {
            true -> s.toInt()
            false -> when (this::toBeDefinedInt.isInitialized) {
                true -> toBeDefinedInt.value
                false -> 79
            }
        }
    @Test
    fun testParse() {
        assertEquals(LateInitStyle.parse("42"), 42, "42 expected")
        assertEquals(LateInitStyle.parse("some string"), 79, "some string shall be parsed to 79.")
    }

NotNull Delegate

    var toBeDefinedInt : Int by Delegates.notNull()
    fun parse(s: String): Int {
        toBeDefinedInt = 79
        return when(s.matches(Regex("-?[0-9]+"))){
            true -> s.toInt()
            false -> toBeDefinedInt
        }
    }
    @Test
    fun testParse() {
        assertEquals(NotNullDelegateStyle.parse("42"), 42, "42 expected")
        assertEquals(NotNullDelegateStyle.parse("some string"), 79, "some string shall be parsed to 79.")
    }

Sealed Classes

sealed class ParseResult {
    data class IntResult(val value: Int): ParseResult()
    data class Exception(val error: String): ParseResult()
}

    fun parse(s: String): ParseResult =
        when(s.matches(Regex("-?[0-9]+"))){
            true -> ParseResult.IntResult(s.toInt())
            false -> ParseResult.Exception(NumberFormatException("$s is not a valid integer.").message.toString())
        }
    @Test
    fun testParse(){
        assertEquals(SealedStyle.parse("42"), ParseResult.IntResult(42), "42 expected")
        assertEquals(SealedStyle.parse("some string"), ParseResult.Exception("some string is not a valid integer."), 
            "some string can not be parsed")

        when(val r = SealedStyle.parse("42")) {
            is ParseResult.IntResult -> assertEquals(r.value, 42)
            is ParseResult.Exception -> fail(r.error)
        }
    }

Annotations - Nullable

    @Nullable
    fun parse(s: String): Int? =
        when(s.matches(Regex("-?[0-9]+"))){
            true -> s.toInt()
            false -> null
        }
    @Test
    fun testParse() {
        assertEquals(NullableAnnotationStyle.parse("42"), 42, "42 expected")
        assertEquals(NullableAnnotationStyle.parse("some string"), null, "some string can not be parsed")
    }

Λrrow - Option

    fun parse(s: String): Option<Int> =
        when(s.matches(Regex("-?[0-9]+"))){
            true -> Some(s.toInt())
            false -> None
        }
    fun testParseWithOption() {
        assertTrue(OptionStyle.parse("42").isDefined(), "some expceted")
        assertEquals(OptionStyle.parse("42"), Some(42), "some containing 42 expceted")
        assertTrue(OptionStyle.parse("some string").isEmpty(), "none expected")
        assertEquals(OptionStyle.parse("some string"), None, "none expected")
    }

Λrrow - Either

    fun parse(s: String): Either<NumberFormatException, Int> =
        when(s.matches(Regex("-?[0-9]+"))){
            true -> Either.Right(s.toInt())
            false -> Either.Left(NumberFormatException("$s is not a valid integer."))
        }
    @Test
    fun testParse() {
        assertTrue(EitherStyle.parse("42").isRight(), "42 is expected")
        // consider ShouldSpec as in https://github.com/BTheunissen/toy-robot-kotlin/blob/master/src/test/kotlin/robot/DirectionSpec.kt @ 2020 09 21
        EitherStyle.parse("42").right().shouldBeRight(Right(42))
        assertTrue(EitherStyle.parse("some string").isLeft(), "some string can not be parsed")
        EitherStyle.parse("some string").left().shouldBeLeft()
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment