Try   HackMD

Roughly cover the materials in Chapter 6: Null Safety and Exceptions.

null

  • A special value means "Does not exist", "No such data", "Operation failed", "A special state", "not repersentable", or similar concept.

  • In many languages, null causes crashes!

    • Including C/C++/Java.
    • You may not open a file "does not exist".
    • You may not do arithmetic operation on "No such data".
      • 1 + null
      • "abc" + null
    • Member function call
  • Almost all Java data types may have null values.

  • In Kotlin, you need special declaration to assign null to a variable or a value.

  • Nullability

    • Nullable: can be assigned to null
      • Type with ? mark
        • var x: Int? = null
    • Non-nullable: cannot be assigned to null
      • Type without ? mark
        • var x: Int = 5
  • Almost all types of Java are nullable.

    • "Check before use" make your code much longer and hard to read.
  • Kotlin does not allow to assign a value of a nullable type to a variable of the corresponding non-nullable type.

    • Safer: less unexpected crashes

Null Safety

  • For compatibility, Kotlin limits the usage of nullable variables.
    • Convert nullable type values to non-nullable values
    • Safe (member function) calls
  • A function returns nullable type: readLine()
    • Read a line from the standard input.
    • Return type: String?
    • It is in kotlin-stdlib, so you don't need to import anything.
  • The following code does not work:
fun main() {
    println("Input your name:")
    val name = readLine()
    println("Hello, $name!")
    println("Your name has ${name.count()} characters")
}
  • name can be null: Press ctrl + d to terminate the input.

    • name.count() is an unsafe call!
  • In Kotlin, there are several ways to convert nullable type to non-nullable type.

  • Non-null Assert: Double-bang operator !!

    • Works on nullable variables / values
    • If the value is null, then this operator crash the program with an "Exception"
    • Use !! when you are extremely confident that the value is not null.
    • Two ways to use !! to fix the above sample code:
      • Use readLine()!!
      • Use name!!.count()
      • Note: does not solve the problem if the user terminates the standard input immediately.
  • Use if and != null to assign a "default value" when readLine() returns null.

fun main() {
    println("Input your name:")
    val buf = readLine()
    val name = if (buf != null) buf else ""
    println("Hello, $name!")
    println("Your name has ${name.count()} characters")
}
  • Null coalescing: Elvis operator ?:
    • Try val name = readLine() ?: ""
    • If the value on the left is null, then the result is the value on the right.
    • If the value on the left is not null, then the result is the value on the left.
    • Will not crash
  • Safe calls ?.: call the member function only when the value is not null.
    • Consider a String?-type variable str
    • If str is not null:
      • str?.count() is the number of characters in str
      • Function count() is called.
    • If str is null:
      • str?.count() is null.
      • Function count() is not called.
    • str?.count() has type Int?.

Why null?

  • To provide a special state without crash immediately.
    • readLine() returns null when end-of-file reached.
  • As an initial value: "Not initialized yet"
    • Sometimes, the initialization costs much
  • Kotlin provides tools to prevent problems with null.

Exception

  • When something went wrong, we cannot properly execute the code, then we "throw" an Exception.
    • readLine()!! throws NullPointerException when end-of-file reached.
    • 1/0 throws ArithmeticException with message / by zero
  • Unhandled Exception will crash your program.
  • Handled Exception can change workflow.

Throwing Exception

  • Basic usage:
throw Exception("Error message")
  • Sample code:
fun main() {
    println("What is your name?")
    val name = readLine() ?: throw Exception("End-Of-File is reached.")
    println("Hello, $name!")
}

Custom Exception

  • Will be discuss later. We need the concept of class to define custom data objects (including Exception).

Exception Handling

  • In Kotlin, we use try/catch statement to intercept Exceptions to prevent the crash.
try{
    statements...
} catch (e1: ExceptionType1) {
    // If any ExceptionType1 thrown in the try-block
    // It will be caught and run the catch-block
    statements...
} catch (e2: ExceptionType2) {
    ...
    ...
} catch (eN: ExceptionTypeN) {
    // If any ExceptionTypeN thrown in the try-block
    // It will be caught and run the catch-block
    statements...
}
// If the thrown exception is not ExceptionType1, 
// ExceptionType2, ..., ExceptionTypeN, then
// throw it the the caller of the function
  • We can only catch certain kind of Exception.

  • The exception thrown in a function myFun

    • If the exception is not caught by any try/catch will terminate that myFun.
    • The caller of myFun will throw the exception again.
    • If at some stage, a try/catch handles the exception, then the program will not crash.
    • If the exception is thrown to main and no try/catch there, then the program will crash.
fun myFun(): Nothing {
    println("Input p:")
    val p = readLine()?.toInt()
    println("Input q:")
    val q = readLine()?.toInt()
    println(p!! / q!!)
    throw Exception("No exception caught.")
}

fun main() {
    try{
        myFun()
    }
    catch (npe: NullPointerException) {
        println("An NPE is detected: ${npe}")
    } catch (ae: ArithmeticException) {
        println("An AE is detected: ${ae.message}")
    }
    // An uncaught Exception will crash the program
}

Return Type: Nothing

  • A function return Nothing is a function that always throws an exception.
  • Example: TODO

Preconditions

import java.util.Scanner

private fun scoreToGPA(score: Int): Double {
    require(0 <= score && score <= 100) {
        "$score is not a valid score in NYCU."
    }
    val yourGPA = when (score) {
        in 90..100 -> 4.3
        in 85..89 -> 4.0
        in 80..84 -> 3.7
        in 77..79 -> 3.3
        in 73..76 -> 3.0
        in 70..72 -> 2.7
        in 67..69 -> 2.3
        in 63..66 -> 2.0
        in 60..62 -> 1.7
        in 50..59 -> 1.0
        else -> 0.0
    }
    check(0.0 <= yourGPA && yourGPA <= 4.3)
    return yourGPA
}

fun main() {
    val scanner = Scanner(System.`in`)
    print("What is your score? ")
    val yourScore = scanner.nextInt()
    val yourGPA = scoreToGPA(yourScore)
    assert(1.0 <= yourGPA && yourGPA <= 4.3)
    print("Your GPA is $yourGPA")
}