In this section, we will learn what the Generics are and how to use them in Kotlin.
What is Generic Class in Kotlin?
A generic class is a type of class that has members with generic data-types!
For example, it might have a property literally of type T!
It might be confusing at first because, after all, in Kotlin, you won’t see any built-in type that is named T!
Well, there isn’t and also this T data type is known as a stand-in or a placeholder data type for the future actual data type!!!
Basically, in a generic class you can define parameters, return types or properties of generic types, which means the data type is not clear until at run time when we want to create an object from that generic class.
The generic classes are especially useful when it comes to collection type objects! For example, consider arrays!
Have you ever noticed that you can store String, Float, Int or any other data-type value in an Array object? Well, this is because an Array class is actually a generic one (meaning it has a generic type name).
Now when we want to create an object from the Array class using the arrayOf<>(), we first set the type and then start to add elements of that specific type as the argument of the function.
For example:
arrayOf<String>(…)
This function tells the Kotlin compiler that we want to replace that generic name of the Array class with the data type String. So now Kotlin will look into the body of the Array class looking for any generic type name and will replace that with the String data-type. That’s why we call the generic type name as a stand-in or a temporal placeholder.
Alright, enough with theory and now let’s jump into the details of how to create a generic class and after that we’ll continue our discussion related to how to work with this type of classes.
How to Create a Generic Class in Kotlin? Generic Syntax:
This is how we can create a generic class in Kotlin:
class ClassName<Generic-Type-Name> { //body of the class }
The structure of a generic class is almost the same as a normal class with one exception and that is the use of a pair of angle brackets that comes after the name of a class and the value we put inside that bracket which is known as the generic type name.
Note that we can put more than just one generic-type-name within the pair of angle brackets like:
class ClassName<T,Y,X,E>{...}
Kotlin Generic Type Name
The value inside the angle bracket of a generic class is known as the generic type name.
Think of this value as a stand-in or a place holder!
For example, the value here could be something like T
as:
class ClassName<T>{…}
Now the value T can be used inside the body of the class as the data type of any member, including properties, the data type of a parameter or the data type for the return value of a function, etc.
The point is, we’re setting a stand-in or a placeholder and telling the compiler that we don’t know at this time what will be the actual data type for these members (the ones that we’ve used this generic name as their data type) but we promise that at runtime we first explicitly set an actual data type to be replaced with this stand-in type name and after that we will create an object from the class.
Example: creating generic class in Kotlin
class ClassName<T>{ val prop1:T val prop2:T fun functionName(parameter:T):T{ } }
As you can see, we’ve used the generic type name T
as the data type for the members of this class. Now, as you’ll soon see, if we wanted to create an object from the ClassName, we first need to replace this generic name with an actual data type and after that, create an object from the class.
OK, let’s say we’ve somehow replaced the generic name with the type String.
This means behind the scene in every place that this generic name is used, it must be replaced with the data type String!
For example:
class ClassName<String> { val prop1:String val prop2:String fun functionName(parameter:String):String{...} }
As mentioned before, the beauty of the generic class is that they are not biased toward a specific type! Basically, every time you create an object from a generic class, you can set any actual data-type as the replacement of the generic name of that class!
For example, if you use the type String for the replacement of the generic type name, then next time you can use another type like Int!
class ClassName<Int>{ val prop1:Int val prop2:Int fun functionName(parameter:Int):Int{...} }
The important note to remember is that, by replacing a generic name with an actual data type, that data type will replace generic name in every part of the class that it was used.
Instance Objects from Generic Class in Kotlin
The way we create an object from a generic class is almost the same as the way we create an object from a typical class, with one exception:
Using a generic class, we must set an actual data type for the generic type name that we’ve used for the target class!
For example, if the generic class is:
class ClassName<T,E>{…}
This class has two generic names and now if we want to create an object from the class, we need to pass an actual type for each of these generic names as:
var variableName = ClassName<String,Int>()
Here for this new object, the generic name T is replaced with the data type String and that means in every place that we’ve used the T value, it’ll be replaced with the data type String.
Also, for the generic name E we’ve used the data type Int. This also means in every part of the class that the value E is used, it must be replaced with the data type Int.
Let’s see an example of how to create an object from a generic class and then we continue our discussion from there.
Example: creating instance objects from generic classes in Kotlin
class Details<T>{ fun printDetails(val1:T, val2:T){ println("The value of the first parameter is: $val1 and the value of the second parameter is: $val2") } } fun main(){ var detail = Details<String>() detail.printDetails("John","Doe") var detail2 = Details<Int>() detail2.printDetails(20,30) }
Output:
The value of the first parameter is: John and the value of the second parameter is: Doe The value of the first parameter is: 20 and the value of the second parameter is: 30
Here, after replacing the generic name T with the data type String, we will have this body for our new object:
Details<String>{ fun printDetails(val1:String, val2:String){ println("The value of the first parameter is: $val1 and the value of the second parameter is: $val2") } }
As you can see for the first object, the generic name T is replaced with String and so in every part of the class that this value is used, it is now replaced with the data type String.
Also, for the second object, we used the data type Int as the replacement for the generic type name.
This means the body of the second object becomes something like this:
Details<Int>{ fun printDetails(val1:Int, val2:Int){ println("The value of the first parameter is: $val1 and the value of the second parameter is: $val2") } }
That’s how we could pass String values as the arguments to the printDetails() function for the first object and could pass Int values as the argument of this function for the second object.
Kotlin Function parameters and generic type
As you saw from the last example, a generic type name can be used in any part of a class that you use other data types. This includes as the data type for parameters as well.
Example: using generic types as type of function parameters
class Details<T,X>{ fun printDetails(val1:T, val2:X){ println("The value of the first parameter is: $val1 and the value of the second parameter is: $val2") } } fun main(){ var detail = Details<String,Int>() detail.printDetails("John",10) var detail2 = Details<Int,String>() detail2.printDetails(20,"Jack") }
Output:
The value of the first parameter is: John and the value of the second parameter is: 10 The value of the first parameter is: 20 and the value of the second parameter is: Jack
Example: returning a generic type value from a function in Kotlin
class Details<T>{ fun echo(val1:T):T{ return val1 } } fun main(){ var detail = Details<String>() println(detail.echo("Jack")) var detail2 = Details<Int>() println(detail2.echo(40)) }
Output:
Jack 40
Kotlin Generic Class: Restricting Parameter Type
By default, when setting a generic type name for a member in a class, that member’s functionality will be limited to only those that are shared between every data type!
This is because the actual data type of a member with a generic type name is not clear until runtime! For this reason, Kotlin won’t allow you to invoke a specific type of functions on a parameter with a generic type name, for example!
This means you can’t treat a variable with generic type as if it’s of string type and so call those functions related to string on such variable!
Basically, for a generic type you can only call those functions and members that are defined in the Any class (which is the parent of every class in the Kotlin and so it is rest assured that every type will have the access to the members of the Any class).
But what if you are certain that at runtime, the generic type will be of a specific type or one of its sub-type then? For example, you have three classes named Employee
, Person
, and Manager
, where the Person class is the parent of the Employee and the Employee is the parent of the Manager.
So now you’re absolutely sure that the generic name will be limited to only these types!
Here, Kotlin has provided a method that we can use to limit the type of a generic type name and this is a way of telling the Kotlin compiler about the possibilities of the future type that will be used for a generic type!
That way, because the compiler can now figure out the possible data type, it’ll allow us to use the members of that future data type in the body of the class for the variables that have used the generic name as their data type.
This is how this generic type restriction is done in Kotlin:
class ClassName<T:data-type>{…}
For example:
class ClassName <T:Person>{…}
This means the T class will be replaced with either Person data type or one of its sub-types in the future when we’re going to create an object from the ClassName.
This way, Kotlin allows us to invoke any members of the Person class on the variables that used the T as their generic type.
Example: restricting a parameter type in a generic class
open class Person{ fun sayHi(){ println("Hello from the person class") } } open class Employee:Person(){ } class Manager:Employee(){ } class SimpleClass<T:Person>{ fun greet(param:T){ param.sayHi() } } fun main(){ var mang = Manager() var emp = Employee() var simp = SimpleClass<Person>() simp.greet(mang) simp.greet(emp) }
Inferring Generic Types
So far, every time we called a generic class, we’ve used the <> symbol between the name of that class and the constructor parentheses when wanted to create an object from the class.
But Kotlin is also capable of inferring the type for the generic type name, which allows us to skip the use of <> at the object creation time.
This is done by setting the data type for a generic type when declaring a variable!
For example:
var variableName:ClassName<data-type>
As you can see, the variableName is now declared its data type for the generic type name and so when we wanted to assign an actual object to this variable; we don’t need to call the <> and set the data-type once again!
Example: compilers infer generic types
open class Person{ fun sayHi(){ println("Hello from the person class") } } open class Employee:Person(){ } class Manager:Employee(){ } class SimpleClass<T:Person>{ fun greet(param:T){ param.sayHi() } } fun main(){ var mang = Manager() var emp = Employee() var simp: SimpleClass<Person> simp = SimpleClass() simp.greet(mang) simp.greet(emp) }
Output:
Hello from the person class Hello from the person class