Kotlin is packed with features that make it both powerful and expressive. One such hidden gem is the underscore (_) operator for type arguments in Kotlin. While it may not be as widely known as other features, it provides a concise and efficient way to work with generics.
In this post, we’ll explore what this operator does, why it’s useful, and how you can leverage it to simplify your Kotlin code.
What is the Underscore (_) Operator for Type Arguments in Kotlin?
In Kotlin, when working with generics, you often have to specify the exact type argument. However, sometimes you just want Kotlin to infer the type for you without explicitly providing it. The underscore (_
) operator allows you to do just that—it acts as a placeholder for type arguments.
The syntax looks like this:
val list: List<_> = listOf(1, 2, 3)
Here, instead of specifying List<Int>
, we use _
, and Kotlin automatically infers that the list contains integers.
Understanding Underscore ( _ ) Operator for type arguments with Example
The underscore operator _
in Kotlin is a type inference placeholder that allows the Kotlin compiler to automatically infer the type of an argument based on the context and other explicitly specified types.
abstract class SomeClass<T> {
abstract fun execute() : T
}
class SomeImplementation : SomeClass<String>() {
override fun execute(): String = "Test"
}
class OtherImplementation : SomeClass<Int>() {
override fun execute(): Int = 42
}
object Runner {
inline fun <reified S: SomeClass<T>, T> run() : T {
return S::class.java.getDeclaredConstructor().newInstance().execute()
}
}
fun main() {
// T is inferred as String because SomeImplementation derives from SomeClass<String>
val s = Runner.run<SomeImplementation, _>()
assert(s == "Test")
// T is inferred as Int because OtherImplementation derives from SomeClass<Int>
val n = Runner.run<OtherImplementation, _>()
assert(n == 42)
}
Don’t worry! Let’s break down the code step by step:
- In this code, we have an abstract class called
SomeClass
with a generic typeT
. It declares an abstract functionexecute()
that returns an object of typeT
. - We have a class called
SomeImplementation
which extendsSomeClass
and specifies the generic type asString
. It overrides theexecute()
function and returns the string value"Test"
. - Similarly, we have another class called
OtherImplementation
which extendsSomeClass
and specifies the generic type asInt
. It overrides theexecute()
function and returns the integer value42
. - Below that, we have an object called
Runner
with a functionrun()
. This function is generic and has two type parametersS
andT
. It uses thereified
keyword to access the type information at runtime. Inside the function, it creates an instance of the specified classS
using reflection (getDeclaredConstructor().newInstance()
) and calls theexecute()
function on it, returning the result of typeT
.
In the above code, the underscore operator is used in the main()
function when calling the Runner.run()
function. Let’s take a closer look:
val s = Runner.run<SomeImplementation, _>()
In this line, the type parameter T
is explicitly specified as _
for the Runner.run()
function. Here, _
acts as a placeholder for the type to be inferred by the compiler. Since SomeImplementation
derives from SomeClass<String>
, the compiler infers T
as String
for this invocation. Therefore, the variable s
is inferred to be of type String
, and the Runner.run()
function returns the result of executing SomeImplementation
, which is the string "Test"
.
val n = Runner.run<OtherImplementation, _>()
Similarly, in this line, the type parameter T
is specified as _
for the Runner.run()
function. Since OtherImplementation
derives from SomeClass<Int>
, the compiler infers T
as Int
for this invocation. Consequently, the variable n
is inferred to be of type Int
, and the Runner.run()
function returns the result of executing OtherImplementation
, which is the integer 42
.
By using the underscore operator
_
as a type argument, the compiler can automatically infer the appropriate type based on the context and the explicitly specified types.
Why Use the Underscore (_) Operator for Type Arguments in Kotlin?
Using the underscore operator has several benefits:
- Simplifies Type Declarations — You don’t have to explicitly specify type arguments when they can be inferred.
- Improves Code Readability — It makes code cleaner and easier to read, especially when dealing with complex generics.
- Reduces Boilerplate Code — Less repetitive type annotations mean more concise code.
- Works with Generic Functions — It allows you to call generic functions without explicitly passing type arguments.
How to Use the Underscore (_) Operator in Kotlin
1. Using _
with Generic Functions
Kotlin lets you use _
when calling generic functions. Suppose you have a function that takes a generic type parameter:
fun <T> printType(value: T) {
println("Type: ${value::class.simpleName}")
}
You can call this function without explicitly specifying the type argument:
printType<_>(42) // Kotlin infers the type as Int
2. Using _
with Collections
The underscore operator works well with collections, making them more flexible when type inference is possible.
val numbers: List<_> = listOf(10, 20, 30)
println(numbers) // Output: [10, 20, 30]
Here, Kotlin automatically infers that numbers
is a List<Int>
.
3. _
in Function Returns
If a function returns a generic type, _
allows Kotlin to infer it without explicit declaration.
fun getList(): List<_> = listOf("Kotlin", "Java", "Swift")
Kotlin infers that getList()
returns a List<String>
without us specifying it explicitly.
When to Avoid Using the Underscore (_
) Operator in Kotlin
While the underscore operator is useful, there are situations where avoiding it can improve code clarity and maintainability:
- When Type Inference Fails — If Kotlin cannot determine the type of a variable or lambda parameter, using
_
will result in a compilation error. - In Public APIs — Overusing
_
in public functions can make the API less clear, potentially confusing users who rely on explicit parameter names for readability. - When Explicit Types Improve Readability — In complex expressions or function signatures, explicitly defining types can enhance code comprehension and maintainability.
Conclusion
The underscore (_
) operator for type arguments in Kotlin is a simple yet powerful tool that helps reduce boilerplate code and improve readability. Whether you’re working with collections, or generic functions, it can make your code cleaner and more concise.
Next time you’re dealing with generics, give _
a try and let Kotlin do the heavy lifting for you..!