Kotlin is a statically typed language that runs on JVM that may compile to JavaScript source code or use the LLVM compiler infrastructure. Since then, it’s grown exponentially in popularity. In 2017, Google officially backed Kotlin as the official language for Android.
And that's just the beginning! Join us here at Kotlin Town and be a part of the evolution of this exciting new language.
Hi folks! Welcome to Kotlin Thursdays. I’m really excited to kick off the first one of the year, and what better way to do it than tackling Generics in Kotlin? Generics in code can actually create stronger type-checking systems in your code, as this gives programmers the ability to fix issues at compile time rather than runtime.
Before we can run, we have to walk first. Let’s start with covering the difference betweenclassesandtypesin Kotlin. Often, when we hear these two words in Kotlin, they might be used interchangeably; but it turns out, there are differences between the words!
In Kotlin, atypedescribes properties a set of objects may share, which tells the compiler how the programmer intends to the use the data. A Kotlinclass is just an implementation of that type. What this means is that while a class is always a type, a type is not necessarily a class.
Let’s take this non-genericSlothclass for example:
data class Sloth(val name: String, val isTwoFingered: Boolean) { fun eat() {...} fun sleep() {...}
}
This sloth doesn’t do very much. I want his life.
So is aSlotha class or a type? Well, it’s both, as it is a class but also the name of the type. But what aboutSlothtypes saved in variables?
var jerryTheSloth: Slothdeclares a variable that holds an instance of theSlothclass.
var jerryTheSloth: Sloth?shows that Jerry the Sloth can also be declared as a nullable type. Every Kotlin class can be used to construct at least 2 types!
The distinction betweentypesandclassescan actually be traced back toprogramming language theory! Is it a pain? Yes. But it isuseful?Also yes, especially when we start talking about Generics.
Generics Type Parameters
As with many generic collections you may see in Kotlin such as Lists, Sets, and Maps, type arguments can be inferred by the Kotlin complier as long as there are objects in the collection.
For empty collections, the type will need to be explicitly defined either with
val slothReg: List<Sloth> = listOf()
or
val slothReg = listOf<Sloth>()
Generic Functions
We have our sloth crew, but we all want them to be fed at once. We could write our function to take a list of Sloths:
fun feedCrew(crew: List<Sloth>) { crew.forEach { it.eat()
} }
Jerry, Bae, and Chrissy chillin in my paint masterpiece
This is all fine and dandy, but what if we wanted a panda crew joined the party and we also had to feed them? We couldn’t use this same function for Sloths, so we’d have to write a similar function for Pandas.
data class Sloth(val name: String, val isTwoFingered: Boolean) { fun eat() {} fun sleep() {}}
fun feedCrew(crew: List) {
crew.forEach {
it.eat()
}
}
data class Panda(val name: String) {
fun eat() {}
fun sleep() {}
}
fun feedCrew(crew: List) {
crew.forEach {
it.eat()
}
}
If they do the same thing, couldn’t we try to consolidate this function into one? Let’s look into the possibility of using Generic functions to work with any list. We could write a genericfeedCrew()function that extends the typeListand when we use it, define the type in the parameter.
The problem is, we can’t actually make theeat()call in this function. This is because as far as the compiler is concerned, not all List types have aneat()function. Here, we can write our function and data classes so that have these two classes inherit from aMammalclass as a subtype.
open class Mammal(val name: String) { fun eat() {} fun sleep() {}}
data class Sloths(val slothName: String, val isTwoFingered: Boolean): Mammal(slothName)
data class Pandas(val pandaName: String) : Mammal(pandaName)
fun feedCrew(crew: List) { // List is covariant on its element type
crew.forEach {
it.eat()
}
}
fun main() {
val slothCrew = listOf(
Sloths("Jerry", false),
Sloths("Bae", true),
Sloths("Chrissy", false),
)
val pandaCrew = listOf(
Pandas("Jay"),
Pandas("Peggy")
)
feedCrews(slothCrew)
feedCrews(pandaCrew)
}
Covariance & Contravariance
Why does this really work? If you doCmd + Clickon List in the parameter for feedCrew, we see the signature
public interface List<out E> : Collection<E>
where the list iscovarianton its element type.
Acovarianceclass is a generic class wheresubtypingis preserved. Making a type parameter of a covariant class makes it possible to pass values of that class as function arguments and return values whenthe type arguments don’t exactly matchthe ones in the function definition. More specifically, the acceptance criteria for these type arguments when they don’t match is that they are at least the subtype of the defined type argument.
In Kotlin, to declare the class to be covariant on a certain type parameter, you put theoutkeyword before the name of the type parameter toproducethe element type.
Of course, in covariance, this only goes one way. We can say that “aPandais a subtype of aMammal” but we can’t say aMammalis aSloth.
Subtyping for the class is preserved: Producer<Sloth/Panda> is a subtype of Producer<Mammal>
Contravariance, on the other hand, is a reflection ofcovariance.
Subtyping for the class is reversed: Consumer<Mammal> is a subtype of Consumer<Sloth/Panda>
In Kotlin, to declare the class to be contravariant on a certain type parameter, you put theinkeyword before the name of the type parameter toconsumethe element type.
Let’s say we wanted to mash the whole crew together and sort the names in alphabetical order.
Squaaaaadddd
We can use the Comparator interface to be able to do this:
interface Comparator<in T> { fun compare(e1: T, e2: T): Int {...}
This is what the implementation in the code looks like!
[Sloths(slothName=Bae, isTwoFingered=true), Sloths(slothName=Chrissy, isTwoFingered=false), Sloths(slothName=Jerry, isTwoFingered=false), Pandas(pandaName=Peggy), Pandas(pandaName=Tegan)]
I hope you enjoyed this blurb on an introduction in Kotlin Generics! Next week, we will continue on covering Higher-Order Functions and Reified Generics in Kotlin. See you next week!
Kotlin Town
6 members
Description
Interested in Kotlin? Are you a practitioner?
Kotlin is a statically typed language that runs on JVM that may compile to JavaScript source code or use the LLVM compiler infrastructure. Since then, it’s grown exponentially in popularity. In 2017, Google officially backed Kotlin as the official language for Android.
And that's just the beginning! Join us here at Kotlin Town and be a part of the evolution of this exciting new language.
Kotlin Thursdays - Introduction to Kotlin Generics, Part 1
by Amanda Hinchman-Dominguez
Mar 22, 2019
Hi folks! Welcome to Kotlin Thursdays. I’m really excited to kick off the first one of the year, and what better way to do it than tackling Generics in Kotlin? Generics in code can actually create stronger type-checking systems in your code, as this gives programmers the ability to fix issues at compile time rather than runtime.
Topics
Resources
Kotlin Classes and Types
Before we can run, we have to walk first. Let’s start with covering the difference between classes and types in Kotlin. Often, when we hear these two words in Kotlin, they might be used interchangeably; but it turns out, there are differences between the words!
In Kotlin, a type describes properties a set of objects may share, which tells the compiler how the programmer intends to the use the data. A Kotlin class is just an implementation of that type. What this means is that while a class is always a type, a type is not necessarily a class.
Let’s take this non-generic
Sloth
class for example:This sloth doesn’t do very much. I want his life.
So is a
Sloth
a class or a type? Well, it’s both, as it is a class but also the name of the type. But what aboutSloth
types saved in variables?var jerryTheSloth: Sloth
declares a variable that holds an instance of theSloth
class.var jerryTheSloth: Sloth?
shows that Jerry the Sloth can also be declared as a nullable type. Every Kotlin class can be used to construct at least 2 types!The distinction between types and classes can actually be traced back to programming language theory! Is it a pain? Yes. But it is useful? Also yes, especially when we start talking about Generics.
Generics Type Parameters
As with many generic collections you may see in Kotlin such as Lists, Sets, and Maps, type arguments can be inferred by the Kotlin complier as long as there are objects in the collection.
For empty collections, the type will need to be explicitly defined either with
or
Generic Functions
We have our sloth crew, but we all want them to be fed at once. We could write our function to take a list of Sloths:
Jerry, Bae, and Chrissy chillin in my paint masterpiece
This is all fine and dandy, but what if we wanted a panda crew joined the party and we also had to feed them? We couldn’t use this same function for Sloths, so we’d have to write a similar function for Pandas.
If they do the same thing, couldn’t we try to consolidate this function into one? Let’s look into the possibility of using Generic functions to work with any list. We could write a generic
feedCrew()
function that extends the typeList
and when we use it, define the type in the parameter.The problem is, we can’t actually make the
eat()
call in this function. This is because as far as the compiler is concerned, not all List types have aneat()
function. Here, we can write our function and data classes so that have these two classes inherit from aMammal
class as a subtype.Covariance & Contravariance
Why does this really work? If you do Cmd + Click on List in the parameter for feedCrew, we see the signature
where the list is covariant on its element type.
A covariance class is a generic class where subtyping is preserved. Making a type parameter of a covariant class makes it possible to pass values of that class as function arguments and return values when the type arguments don’t exactly match the ones in the function definition. More specifically, the acceptance criteria for these type arguments when they don’t match is that they are at least the subtype of the defined type argument.
In Kotlin, to declare the class to be covariant on a certain type parameter, you put the
out
keyword before the name of the type parameter to produce the element type.Of course, in covariance, this only goes one way. We can say that “a
Panda
is a subtype of aMammal
” but we can’t say aMammal
is aSloth
.Subtyping for the class is preserved: Producer<Sloth/Panda> is a subtype of Producer<Mammal>
Contravariance, on the other hand, is a reflection of covariance.
Subtyping for the class is reversed: Consumer<Mammal> is a subtype of Consumer<Sloth/Panda>
In Kotlin, to declare the class to be contravariant on a certain type parameter, you put the
in
keyword before the name of the type parameter to consumethe element type.Let’s say we wanted to mash the whole crew together and sort the names in alphabetical order.
Squaaaaadddd
We can use the Comparator interface to be able to do this:
This is what the implementation in the code looks like!
The output, of course, gives us:
[Sloths(slothName=Bae, isTwoFingered=true), Sloths(slothName=Chrissy, isTwoFingered=false), Sloths(slothName=Jerry, isTwoFingered=false), Pandas(pandaName=Peggy), Pandas(pandaName=Tegan)]
I hope you enjoyed this blurb on an introduction in Kotlin Generics! Next week, we will continue on covering Higher-Order Functions and Reified Generics in Kotlin. See you next week!