Kotlin Null Safety and Exception Handling: Safe Calls, Elvis Operator & try-catch
1. What is null safety in Kotlin?
Null safety is a Kotlin feature that prevents null pointer exceptions at compile time by distinguishing nullable (?) and non-nullable types. It ensures variables are either guaranteed to hold a value or explicitly marked as potentially null, reducing runtime crashes.
Key concepts:
- Non-Nullable Types: Cannot hold null (e.g.,
String). - Nullable Types: Can hold null (e.g.,
String?). - Safe Call Operator (
?.): Calls a method only if the object is not null. - Elvis Operator (
?:): Provides a default value if the expression is null. - Not-Null Assertion (
!!): Forces a non-nullable type, but throws an exception if null (use cautiously).
Use Case: Safely handling optional data (e.g., user input, API responses).
2. How do nullable and non-nullable types work?
- Non-Nullable: Default; cannot assign null.
- Nullable: Append
?to the type; can assign null.
3. Can you give an example of null safety?
fun main() {
// Non-nullable type
val name: String = "Krishna" // OK
// val name2: String = null // Compile error: Type mismatch
// Nullable type
var email: String? = "[email protected]" // OK
email = null // OK
// Safe call operator (?.)
println(email?.length) // Outputs null if null
// Elvis operator (?:)
val safeLength = email?.length ?: 0
println("Safe length: $safeLength") // Outputs 0 if null
// Not-null assertion (!! - use cautiously)
// println(email!!.length) // Throws NullPointerException if null
// Let operator (let) for null-safe operations
email?.let { println("Email is: $it") } // Only executes if not null
}
Output:
16
Safe length: 16
Email is: [email protected]
Note:
email?.lengthreturnsnullifemailis null, avoiding crashes.?:provides a fallback value.
4. What is exception handling in Kotlin?
Exception handling in Kotlin manages runtime errors using try-catch-finally, similar to Java but with more concise syntax. Exceptions are thrown with throw and caught to prevent crashes.
Key components:
try: Encloses code that might throw an exception.catch: Handles specific exceptions.finally: Executes always (cleanup).throw: Explicitly throws an exception.
Custom Exceptions: Extend Exception or RuntimeException.
Use Case: Gracefully handling invalid input or file errors.
5. How do try-catch-finally and custom exceptions work?
class InvalidSalaryException(message: String) : Exception(message)
fun calculateBonus(salary: Double?): Double {
try {
if (salary == null || salary < 0) {
throw InvalidSalaryException("Salary cannot be null or negative")
}
return salary * 0.1
} catch (e: InvalidSalaryException) {
println("Error: ${e.message}")
return 0.0
} finally {
println("Bonus calculation completed")
}
}
fun main() {
val bonus1 = calculateBonus(50000.0)
println("Valid bonus: $bonus1")
val bonus2 = calculateBonus(-100.0)
println("Invalid bonus: $bonus2")
val bonus3 = calculateBonus(null)
println("Null bonus: $bonus3")
}
Output:
Bonus calculation completed
Valid bonus: 5000.0
Error: Salary cannot be null or negative
Bonus calculation completed
Invalid bonus: 0.0
Error: Salary cannot be null or negative
Bonus calculation completed
Null bonus: 0.0
Note:
try-catch-finallyhandles exceptions and ensures cleanup.InvalidSalaryExceptionis a custom exception.finallyalways executes, even if no exception occurs.
6. Can you provide a comprehensive example of null safety and exception handling?
class Employee {
var name: String? = null
var salary: Double? = null
fun validateAndCalculateBonus(): Double? {
try {
val validName = name ?: throw IllegalArgumentException("Name cannot be null")
val validSalary = salary ?: throw IllegalArgumentException("Salary cannot be null")
if (validSalary < 0) {
throw IllegalArgumentException("Salary must be positive")
}
return validSalary * 0.1 // 10% bonus
} catch (e: IllegalArgumentException) {
println("Validation error: ${e.message}")
return null
} catch (e: Exception) {
println("Unexpected error: ${e.message}")
return null
} finally {
println("Employee validation completed")
}
}
fun safePrintInfo() {
val safeName = name ?: "Unknown"
val safeSalary = salary ?: 0.0
println("Employee: $safeName, Salary: $${safeSalary}")
}
}
fun main() {
val emp1 = Employee()
emp1.name = "Krishna"
emp1.salary = 60000.0
val bonus1 = emp1.validateAndCalculateBonus()
println("Bonus for Krishna: $${bonus1 ?: 0.0}")
emp1.safePrintInfo()
val emp2 = Employee()
emp2.name = "Ram"
emp2.salary = -50000.0 // Invalid
val bonus2 = emp2.validateAndCalculateBonus()
println("Bonus for Ram: $${bonus2 ?: 0.0}")
emp2.safePrintInfo()
val emp3 = Employee()
emp3.salary = 55000.0 // Name is null
val bonus3 = emp3.validateAndCalculateBonus()
println("Bonus for emp3: $${bonus3 ?: 0.0}")
emp3.safePrintInfo()
}
Output:
Employee validation completed
Bonus for Krishna: $6000.0
Employee: Krishna, Salary: $60000.0
Validation error: Salary must be positive
Employee validation completed
Bonus for Ram: $0.0
Employee: Ram, Salary: $-50000.0
Validation error: Name cannot be null
Employee validation completed
Bonus for emp3: $0.0
Employee: Unknown, Salary: $55000.0
Description:
- Null Safety: Uses
?:for safe defaults; nullable types (String?,Double?). - Exceptions: Custom
IllegalArgumentExceptionfor validation;try-catch-finallyfor handling. safePrintInfo: Demonstrates safe calls and Elvis operator.- Integrates multiple null checks and exception scenarios.
7. What are common mistakes in Kotlin null safety and exceptions?
Null Safety:
- Forgetting
?on nullable types, causing compile errors. - Overusing
!!(not-null assertion), leading to runtime crashes. - Misusing
?.in chains, where subsequent calls may still be null.
Exceptions:
- Not catching specific exceptions, leading to unhandled errors.
- Throwing generic exceptions without custom messages.
- Omitting
finallyfor cleanup in resource management.
General:
- Ignoring platform types in interop with Java.
- Not documenting nullable parameters in functions.
8. What are best practices for Kotlin null safety and exceptions?
Null Safety:
- Use nullable types (
?) only when necessary; prefer non-nullable. - Use safe calls (
?.) and Elvis (?:) for null handling. - Avoid
!!except in verified non-null scenarios; userequireNotNullfor assertions.
Exceptions:
- Catch specific exceptions (e.g.,
IllegalArgumentException) before general ones. - Use custom exceptions for business logic errors.
- Always include
finallyfor cleanup (e.g., closing resources).
General:
- Use
@Nullable/@NonNullannotations for clarity in shared code. - Document nullability in KDoc (e.g.,
@param name The employee name, nullable). - Test with null inputs to ensure robustness.
- Leverage Kotlin’s type system to catch null issues at compile time.