1. Introduction

Generics in Kotlin enable developers to craft reusable and type-safe code, offering flexibility while handling various data types.

As we know, generic classes and methods are defined using angle brackets to specify the type parameter. We commonly need to pass type parameters to generic methods.

In this tutorial, we’ll explore various approaches to passing a type to a generic method.

2. Using Class Parameters

The first way to pass a type to a generic method is by using Class parameters. This allows the generic method to access and use the specified type:

fun <T> passTypeUsingClassParameter(clazz: Class<T>): String {
    return clazz.simpleName
}

Here, our helper method accepts a Class parameter and returns the simpleName as a String:

@Test
fun `pass type to generic method using class parameters`() {

    assertEquals("String", passTypeUsingClassParameter(String::class.java))
    assertEquals("int", passTypeUsingClassParameter(Int::class.java))
}

In our test, we verify that our passTypeUsingClassParameter() method correctly returns the simple name string of the String and Int class.

Kotlin can infer the type based on the provided class references. Therefore, the angle brackets for specifying generic types are omitted.

3. Using Reified Type Parameters

Kotlin’s reified type parameters can also be used to pass the type of a class to a generic method. Reified type parameters allow accessing the actual class of the type at runtime, enabling direct usage of the type within the method:

inline fun <reified T> passTypeUsingReifiedParameter(): String? {
    return T::class.simpleName
}

Subsequently, inside the method, T::class.simpleName retrieves the simple name of the class represented by the generic type.

As usual, we should test our methods to ensure they work correctly:

@Test
fun `pass type to generic method using reified parameters`() {

    assertEquals("String", passTypeUsingReifiedParameter<String>()) 
    assertEquals("Int", passTypeUsingReifiedParameter<Int>())
}

The reified keyword allows direct access to the type parameter, enabling the client to specify a type without passing a class reference. This simplifies the usage of generic methods and enhances code readability.

4. Using Higher-Order Methods

Higher-order functions can also pass a type to a generic method indirectly. By accepting a function as a parameter, we can delegate the responsibility of providing the type to the caller:

fun <T> passTypeUsingHigherOrderFunction(action: () -> T): T {
    return action()
}

*Instead of directly determining the type, this method immediately executes the provided lambda by invoking action(), which returns an inferred type*.

Hence, passTypeUsingHigherOrderFunction() effectively delegates the type inference to the lambda function, directly returning the result produced by it. Finally, we can prove that this works with lambdas that return different types:

@Test
fun `pass type to generic method using higher order functions`() {
    val intValue = passTypeUsingHigherOrderFunction{42}
    val stringValue = passTypeUsingHigherOrderFunction{"Generic Method!"}

    assertEquals(42, intValue)
    assertEquals("Generic Method!", stringValue)
}

5. Conclusion

In this article, we’ve explored various approaches to passing a type to a generic method. We started with class references as parameters, which is a basic approach. Then we leveraged reified type parameters to provide direct access to the class type at runtime. Finally, we utilized higher-order methods to demonstrate the flexible generics system and showcase a complex way to pass a generic type.

By understanding and using these versatile approaches, we can effectively address various scenarios while maintaining type safety and clarity in our projects.

As always, the complete source code used in this article is available over on GitHub.