Share
Explore

Kotlin Nullables and Type Safety

Kotlin Labs: Nullable Variables

Here are 5 progressive Kotlin labs that teach the operation of KOTLIN nullable variables.
Each lab builds on the previous one.

Lab 1: Introduction to Nullable Variables

/**
* Kotlin Lab 1: Introduction to Nullable Variables
*
* This lab introduces the concept of nullable variables in Kotlin
* and demonstrates how Kotlin's type system distinguishes between
* nullable and non-nullable types.
*/

fun main() {
// In Kotlin, variables are non-nullable by default
// This means they cannot hold null values
var nonNullableString: String = "Hello"
// Attempting to assign null to a non-nullable variable results in a compilation error
// Uncomment the line below to see the error
// nonNullableString = null // Error: Null can not be a value of a non-null type String
// To make a variable nullable, we add a question mark (?) after the type
var nullableString: String? = "Hello"
// Nullable variables can hold null values
nullableString = null // This is allowed
println("Non-nullable string: $nonNullableString")
println("Nullable string: $nullableString")
// Demonstration of type safety with nullable types
// The following line will not compile because Kotlin protects us from operating on possibly null values
// Uncomment to see the error
// println("Length of nullable string: ${nullableString.length}") // Error: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
// To safely use a nullable variable, we need to check if it's null first
if (nullableString != null) {
println("Length of nullable string: ${nullableString.length}")
} else {
println("The nullable string is null")
}
// Let's demonstrate how this compares to Java's approach
javaStyleNullHandling("Hello")
javaStyleNullHandling(null) // This would cause a NullPointerException in Java if not checked
// Now let's see how Kotlin forces us to handle nulls properly
kotlinStyleNullHandling("Hello")
kotlinStyleNullHandling(null)
}

/**
* Demonstrates Java-style null handling (without Kotlin's null safety)
* This approach is prone to NullPointerException if we forget null checks
*/
fun javaStyleNullHandling(input: String?) {
// In Java, we would need to remember to do this check manually
if (input != null) {
println("Java style: The string's length is ${input.length}")
} else {
println("Java style: The string is null")
}
// In Java, it's easy to forget the null check, leading to runtime errors
// Kotlin prevents this at compile time
}

/**
* Demonstrates Kotlin's safer approach to null handling
*/
fun kotlinStyleNullHandling(input: String?) {
// Kotlin forces us to handle the null case by not allowing direct property access on nullable types
// This line won't compile: println("The string's length is ${input.length}")
// We must explicitly check for null or use Kotlin's null-safety operators
if (input != null) {
println("Kotlin style: The string's length is ${input.length}")
} else {
println("Kotlin style: The string is null")
}
}

Lecture Notes: Introduction to Nullable Variables

In this lab, we've introduced one of Kotlin's most powerful features: null safety through the type system. Let's understand what nullable variables are and why Kotlin needs this feature.

What is a Nullable Variable?
A nullable variable is a variable that is explicitly allowed to hold the value null.
In Kotlin, the type system distinguishes between references that can hold null (nullable references) and those that cannot (non-nullable references).
This distinction is made directly in the type system, unlike in Java where any reference can potentially be null.

How to Create a Nullable Variable

To make a variable nullable in Kotlin, we add a question mark (?) after the type:
var nonNullableString: String = "Hello" // Cannot be null
var nullableString: String? = "Hello" // Can be null


Why Kotlin Needs Nullable Types
Kotlin introduced nullable types to address what has been called "The Billion Dollar Mistake" - the null reference invented by Tony Hoare in 1965.
image.png
In many programming languages, including Java, any reference variable can potentially be null, which can lead to the infamous NullPointerException (NPE) during runtime.
These NPEs are one of the most common bugs in Java applications, and they're especially problematic because:
They occur at runtime, not compile time
They can crash your application
They can be difficult to trace and debug
They do NOT represent a condition that could have been checked by the compiler. NPEs are runtime occurances, influencable by user or system actions.

Kotlin's approach offers several benefits:

Compile-time safety: The compiler forces you to handle potential null cases
Clear intentions: When you see a type without a question mark, you know it can never be null
Reduced boilerplate: Kotlin provides concise operators for handling null cases
Interoperability: Kotlin's type system can work with Java's more permissive approach to nulls
By making nullability explicit in the type system, Kotlin helps developers avoid NullPointerExceptions and write more robust code.
You're forced to think about the null case when you declare a variable as nullable, and the compiler won't let you forget to handle it.

Lab 2: Unwrapping Nullables with If-Else Statements

/**
* Kotlin Lab 2: Unwrapping Nullables with If-Else Statements
*
* This lab demonstrates how to safely handle nullable variables
* using if-else statements for null checking.
*/

fun main() {
// Create some nullable variables
val firstName: String? = "John"
val middleName: String? = null
val lastName: String? = "Doe"
// Basic if-else null check
println("--- Basic if-else null check ---")
if (firstName != null) {
println("First name: $firstName (${firstName.length} characters)")
} else {
println("First name is null")
}
if (middleName != null) {
println("Middle name: $middleName (${middleName.length} characters)")
} else {
println("Middle name is null")
}
// Smart casts
println("\n--- Smart casts ---")
printNameLength(firstName)
printNameLength(middleName)
printNameLength(lastName)
// Combining multiple null checks
println("\n--- Combining multiple null checks ---")
// Building a full name with null handling
var fullName = ""
if (firstName != null) {
fullName += firstName
}
if (middleName != null) {
fullName += " $middleName"
}
if (lastName != null) {
fullName += " $lastName"
}
println("Full name: $fullName")
// A more complex example: nested null checks
println("\n--- Nested null checks ---")
val user: User? = User("alice@example.com", Profile("Alice", null))
// val user: User? = null // Try changing to null to see different outcomes
if (user != null) {
if (user.profile != null) {
if (user.profile.bio != null) {
println("User bio: ${user.profile.bio}")
} else {
println("User has a profile but no bio")
}
} else {
println("User has no profile")
}
} else {
println("User is null")
}
// Using let with null check for more concise nested null handling
println("\n--- Using let with null check ---")
if (user != null) {
user.profile?.let {
it.bio?.let {
println("User bio using let: $it")
}
}
}
}

/**
* Demonstrates how Kotlin performs smart casting after null checks
*/
fun printNameLength(name: String?) {
if (name != null) {
// Inside this block, 'name' is automatically cast to non-nullable String
// This is called a "smart cast" - the compiler knows name cannot be null here
println("Name: $name (${name.length} characters)")
// Notice we can directly access name.length without any additional checks
// or operators, because Kotlin knows 'name' is not null in this scope
} else {
println("Name is null")
}
}

// Class definitions for the nested null check example
data class User(val email: String, val profile: Profile?)
data class Profile(val name: String, val bio: String?)

Lecture Notes: Unwrapping Nullables with If-Else Statements

In this lab, we explored how to safely work with nullable variables using if-else statements for null checking. This is the most straightforward approach to handling nullable variables in Kotlin.
Null Checking with If-Else Statements
The most basic way to handle a nullable variable is to check if it's null using an if-else statement:
if (variable != null) {
// Use variable here safely
} else {
// Handle the null case
}

Smart Casts
One of Kotlin's powerful features is "smart casts."
When you check if a variable is null within an if statement, Kotlin automatically casts the variable to a non-nullable type within the scope of that if block.
This means you can directly access properties and methods on the variable without using any special operators.

For example:
val name: String? = "John"
if (name != null) {
// Here, name is automatically cast to String (non-nullable)
println(name.length) // No need for special operators
}

Advantages of If-Else Null Checking
Explicit and clear: The code clearly shows the two different paths for null and non-null cases
Flexible: You can execute completely different code blocks based on nullability
Familiar: Similar to null checking in other languages

Limitations of If-Else Null Checking
Verbosity: For simple operations, it can be more verbose than Kotlin's specialized null-handling operators
Nested null checks: When dealing with multiple nullable variables, the code can quickly become deeply nested and harder to read
Why Kotlin Needs This
While Kotlin provides more concise operators for null handling (which we'll explore in later labs), the if-else approach gives you full control and clarity. It's especially useful when:
The logic for handling null vs. non-null cases is complex
You need to execute substantially different code based on nullability
You're introducing Kotlin to developers from other languages who are familiar with this pattern

Lab 3: The Not-Null Assert Operator (!!)

/**
* Kotlin Lab 3: The Not-Null Assert Operator (!!)
*
* This lab demonstrates how to use the not-null assertion operator (!!)
* and when it's appropriate (or inappropriate) to use it.
*/

fun main() {
// Let's create some nullable variables
var name: String? = "John"
var age: Int? = 30
var address: String? = null
println("--- Basic usage of !! operator ---")
// Using !! to assert a variable is not null
// This is safe because we know name is not null
val nameLength = name!!.length
println("Name length: $nameLength")
// Using !! with a variable we know is not null
try {
// This will work because age is not null
val nextYear = age!! + 1
println("Age next year: $nextYear")
// This will throw a NullPointerException because address is null
val addressLength = address!!.length
println("Address length: $addressLength") // This line will never execute
} catch (e: NullPointerException) {
println("Caught a NullPointerException: ${e.message}")
println("This happened because we used !! on a null value (address)")
}
println("\n--- Debugging with !! ---")
// A safer approach would be using a null check
if (address != null) {
val addressLength = address.length
println("Address length: $addressLength")
} else {
println("Address is null")
}
println("\n--- Appropriate and inappropriate uses of !! ---")
// APPROPRIATE USE CASE: When you're absolutely sure a value won't be null
val configuration = loadConfiguration()
// If the configuration is required for the app to function,
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.