In Kotlin, inline classes offer a way to create value classes that do not incur the memory overhead of regular classes. By using inline classes, developers can create wrappers around basic types without the runtime cost associated with boxing. This makes them an excellent option for performance-sensitive code that benefits from compile-time type safety.
What are Kotlin Inline Classes?
Inline classes, introduced in Kotlin 1.3, are a special kind of value class that contains only one property. At runtime, instances of inline classes are represented as the underlying value, which means no actual object is created. The Kotlin compiler performs inline substitution wherever possible, thereby reducing memory consumption and improving performance.
Why Use Inline Classes?
- Performance: Eliminates the overhead of creating wrapper objects.
- Type Safety: Provides compile-time type checking, ensuring data integrity.
- Reduced Memory Consumption: Avoids boxing and unboxing operations.
- Enhanced Readability: Clearly represents domain-specific types, improving code understanding.
How to Define Inline Classes in Kotlin
To define an inline class, use the inline
modifier before the class keyword and ensure the class has exactly one property initialized in the primary constructor.
Example 1: Simple Inline Class
inline class Username(val value: String)
fun greet(username: Username) {
println("Hello, ${username.value}!")
}
fun main() {
val username = Username("john_doe")
greet(username) // Output: Hello, john_doe!
}
In this example, Username
is an inline class that wraps a String
. When greet
is called, the Username
instance is treated as a String
at runtime, thus avoiding object creation.
Example 2: Inline Class with Integer
inline class Age(val value: Int) {
init {
require(value >= 0) { "Age must be non-negative" }
}
fun isAdult(): Boolean = value >= 18
}
fun main() {
val age = Age(25)
println("Is adult: ${age.isAdult()}") // Output: Is adult: true
}
Here, Age
is an inline class that wraps an Int
. The inline class includes an init
block to validate the value and a function isAdult
to encapsulate related logic. At runtime, the Age
instance behaves like an Int
.
When to Use Inline Classes
Inline classes are particularly useful in scenarios where:
- You want to create a domain-specific type without runtime overhead.
- You need compile-time type safety for basic types like
String
,Int
, orBoolean
. - Performance is critical, and you want to avoid boxing/unboxing operations.
Example 3: Using Inline Classes for Measurements
Consider a scenario where you need to represent measurements with different units (e.g., meters, kilometers). You can use inline classes to ensure type safety and avoid misinterpretation.
inline class Meters(val value: Double)
inline class Kilometers(val value: Double)
fun calculateDistance(meters: Meters, kilometers: Kilometers): Meters {
val metersFromKilometers = Kilometers(kilometers.value * 1000)
return Meters(meters.value + metersFromKilometers.value)
}
fun main() {
val distanceInMeters = Meters(100.0)
val distanceInKilometers = Kilometers(1.0)
val totalDistance = calculateDistance(distanceInMeters, distanceInKilometers)
println("Total distance in meters: ${totalDistance.value}") // Output: Total distance in meters: 1100.0
}
In this example, Meters
and Kilometers
are inline classes that wrap a Double
. The calculateDistance
function ensures that the measurements are correctly combined and prevents mixing up units.
Limitations of Inline Classes
While inline classes are powerful, they have certain limitations:
- An inline class must have exactly one property, which must be initialized in the primary constructor.
- Inline classes cannot extend other classes or be extended by other classes.
- Inline classes cannot implement the
equals()
,hashCode()
, ortoString()
methods directly; they use the default implementations from the underlying type. - Be cautious with nullability as it can introduce boxing.
Example 4: Inline Class with Nullability
inline class NullableUsername(val value: String?)
fun main() {
val nullableUsername: NullableUsername = NullableUsername(null)
println(nullableUsername.value) // Output: null
}
When the underlying type is nullable (String?
), the inline class can still provide benefits, but be aware that the nullable value can introduce boxing in certain scenarios.
Inline Classes vs. Type Aliases
It’s important to distinguish between inline classes and type aliases. While both provide ways to create named types, they serve different purposes.
- Type Aliases: Simply create an alternative name for an existing type without introducing a new type. Type aliases are entirely interchangeable with their underlying type.
- Inline Classes: Create a new type with compile-time safety and without runtime overhead. Inline classes are not directly interchangeable with their underlying type.
Example 5: Type Alias vs. Inline Class
typealias Name = String
inline class InlineName(val value: String)
fun printName(name: Name) {
println("Name: $name")
}
fun printInlineName(inlineName: InlineName) {
println("Inline Name: ${inlineName.value}")
}
fun main() {
val name: Name = "Alice"
val inlineName = InlineName("Bob")
printName(name) // Output: Name: Alice
printInlineName(inlineName) // Output: Inline Name: Bob
val regularString: String = name
// val regularString2: String = inlineName // This will cause a compile-time error
printName(inlineName.value) //This is correct way to print name for inlineName
}
In this example, Name
is a type alias for String
, so it is entirely interchangeable with String
. However, InlineName
is an inline class that is not directly interchangeable with String
, providing additional type safety.
Conclusion
Kotlin inline classes are a powerful tool for creating lightweight, type-safe wrappers around basic types. They offer significant performance benefits by eliminating the overhead of object creation and boxing. Understanding when and how to use inline classes can lead to more efficient and maintainable Kotlin code. By leveraging inline classes appropriately, you can improve the performance and robustness of your applications.