Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
145: Unit Type vs Void
You've likely written dozens of functions in Kotlin that don't return a value. You just leave the return type off, and it works. But as you start working with generics or interfacing with Java libraries, you'll run into two terms: Unit and Void. They seem to mean the same thing—"nothing comes back"—but they behave very differently under the hood.
To see this in action, let's build a simple TaskRunner. I want a system where I can define different types of tasks: some that return a result (like a string) and some that just perform an action and finish.
Defining a generic Task interface
I'll start by creating a generic interface. Since some tasks return data and others don't, the return type needs to be flexible.
interface Task<T> {
fun execute(): T
}
Now, I want a task that just logs a message to the console. It doesn't need to return a value, so my instinct is to make it "void."
The mistake: Trying to use Void for 'nothing'
Coming from a Java background, I might try to use the Void type (capital V). In Java, Void is a placeholder used in generics to signal that the method returns nothing. Let's see what happens when I try that in Kotlin.
class LogTask(val message: String) : Task<Void> {
override fun execute(): Void {
println("Log: $message")
// Wait, the compiler is complaining here!
return null
}
}
Here's the problem: Void in Kotlin is actually the Java java.lang.Void type. It's a class, but it can't be instantiated. To satisfy the return type Void, I'm forced to return null. That feels clunky and wrong for a language that emphasizes null safety. I'm essentially lying to the compiler, saying "I'm returning a type that can't exist, so here's a null."
Switching to the Kotlin way with Unit
This is where Unit comes in. Unlike Java's void, which is a keyword meaning "no value," Kotlin's Unit is a real object. It's a singleton. When a function returns Unit, it's actually returning a value—it's just a value that carries no useful information.
Let's rewrite the LogTask using Unit instead.
class LogTask(val message: String) : Task<Unit> {
override fun execute(): Unit {
println("Log: $message")
// I don't even need a return statement here.
// Kotlin automatically returns the Unit object.
}
}
This is much cleaner. Because Unit is a real type, it fits perfectly into the generic Task<T> interface. The execute() function still returns something, but that "something" is just the Unit singleton.
When would you actually use Void?
You might be wondering why Void even exists in Kotlin. You'll almost never use it in pure Kotlin code. You'll only encounter it when you're overriding a method from a Java library that was defined with <Void> in its generics. If the Java side demands Void, you have to provide null. Otherwise, always stick with Unit.
To wrap this up, let's look at how we'd use these tasks in a runner. Notice how we can treat them all the same because they both conform to the Task interface, regardless of whether they return a meaningful result or just Unit.
fun main() {
val tasks: List<Task<*>> = listOf(
LogTask("System starting..."),
object : Task<String> {
override fun execute() = "Task Complete!"
}
)
for (task in tasks) {
val result = task.execute()
println("Result was: $result")
}
}
When the LogTask executes, the result variable will literally hold the kotlin.Unit object. It's not null; it's just "Unit."
📋 Practical Task
Refactoring the NotificationDispatcher
You are working on a notification system. You have a generic Dispatcher<T> interface. Currently, the SmsDispatcher is incorrectly using Void and returning null, which is causing confusion and potential null-pointer risks in the codebase.
Your Task:
- Modify the
SmsDispatcherclass to useUnitinstead ofVoid. - Remove the
return nullstatement from thesend()method. - Ensure the code compiles and correctly implements the
Dispatcher<Unit>interface.
interface Dispatcher<T> {
fun send(message: String): T
}
class SmsDispatcher : Dispatcher<Void> {
override fun send(message: String): Void {
println("Sending SMS: $message")
return null // Fix this!
}
}
fun main() {
val dispatcher: Dispatcher<Unit> = SmsDispatcher()
dispatcher.send("Hello Kotlin!")
}There are no comments for now.