Before we learn about reified,
Generics in any language is the powerful features that allow us to define classes, methods and properties which are accessible using different data types while keeping a check of the compile-time type safety.
The best example for a generics is Array or any List/Collection implementation.
package kotlin
/**
* Represents an array (specifically, a Java array when targeting the JVM platform).
* Array instances can be created using the [arrayOf], [arrayOfNulls] and [emptyArray]
* standard library functions.
* See [Kotlin language documentation](https://kotlinlang.org/docs/reference/basic-types.html#arrays)
* for more information on arrays.
*/
public class Array<T> {
/**
* Creates a new array with the specified [size], where each element is calculated by calling the specified
* [init] function.
*
* The function [init] is called for each array element sequentially starting from the first one.
* It should return the value for an array element given its index.
*/
public inline constructor(size: Int, init: (Int) -> T)
/**
* Returns the array element at the specified [index]. This method can be called using the
* index operator.
* ```
* value = arr[index]
* ```
*
* If the [index] is out of bounds of this array, throws an [IndexOutOfBoundsException] except in Kotlin/JS
* where the behavior is unspecified.
*/
public operator fun get(index: Int): T
/**
* Sets the array element at the specified [index] to the specified [value]. This method can
* be called using the index operator.
* ```
* arr[index] = value
* ```
*
* If the [index] is out of bounds of this array, throws an [IndexOutOfBoundsException] except in Kotlin/JS
* where the behavior is unspecified.
*/
public operator fun set(index: Int, value: T): Unit
/**
* Returns the number of elements in the array.
*/
public val size: Int
/**
* Creates an iterator for iterating over the elements of the array.
*/
public operator fun iterator(): Iterator<T>
}
Here the templete T
can be of any class that the reason we can create an Array
or List
of any class objects.
Now, let’s try to get the type of this template at run time within the function.
fun <T> Array<T>.average() : Float {
print("${T::class.java}") <e>//Compilation Error</e>
//return default value
return 0.0f
}
What we are trying to achieve here is we want to add an extension function to Array which will check the type at run time and if that type is Int
, it will return the average of all the elements in that array else this will return 0.0f
This will give the below compilation error
Cannot use 'T' as reified type parameter. Use a class instead.
The possible way to access the type of the Template class is by passing it as a parameter.
Since we can not use the type of template at runtime directly, we need to use reified
here.
Reified
To get the information about the type of Template class, we will have to use a keyword called reified
in Kotlin. Also, in order to use the reified
type, we need to mark the function as inline.
inline fun <reified T> Array<T>.average() : Float {
print("${T::class.java}")
//return default value
return 0.0f
}
Lets try to understand what is happening exactly using byte code for the same example
public static final float average(@NotNull Object[] $this$average) {
int $i$f$average = 0; //this variable is not used anywhere
Intrinsics.checkParameterIsNotNull($this$average, "$this$average");
Intrinsics.reifiedOperationMarker(4, "T");
String var2 = String.valueOf(Object.class);
boolean var3 = false; //this variable is not used anywhere
System.out.print(var2);
//return default value
return 0.0F;
}
Since, we have used extenstion function, we can see Object
class but when we actually use it and check its bype code,
//Kotlin code
var arr = arrayOf(1, 2, 3, 4, 5)
var average = arr.average()
//Java ByteCode
Integer[] arr = new Integer[]{1, 2, 3, 4, 5};
int $i$f$average = false; //this variable is not used anywhere
String var4 = String.valueOf(Integer.class);
boolean var5 = false; //this variable is not used anywhere
System.out.print(var4);
float average = 0.0F;
As its an inline function, the implementation will be copied at every calling place.
We can cleary see in the bytecode that the compiler copied the type ie Integer
in our case at the calling place and then printed the class of it. The bolier plate code of checking the type and copying it at the calling place was taken care by compiler itself and we got the exact same type in the compiled code.
In order to achieve the average calculation functionality in the case of Integers array, we can use the below function.
inline fun <reified T> Array<T>.average() : Float {
if (T::class.java == Integer::class.java){
var sum = 0
for (item in this){
if (item is Int) {
sum += item
}
}
return (sum / this.size).toFloat()
}
//return default value
return 0.0f
}
Same function with different return types
Think of a situation where you want to return different data types or class objects from same function.
We can’t use method overloading as overloading only works with different number of params or different data types of params.
In this situation also, reified
will be really useful.
inline fun <reified T> displayAmountAsText(amount: Int): T {
return when (T::class) {
Int::class -> amount as T
String::class -> "$amount USD left in your account" as T
else -> "Please enter valid type" as T
}
}
The only difference in using reified for return type is we need to explicitly define the data type while using the function else the compiler will throw a compile-time error.
var amountInDouble : Int = displayAmountAsText(10)
println(amountInDouble)
var amountInString : String = displayAmountAsText(10)
println(amountInString)