
Why I Fell in Love With Kotlin
Recently I’ve posted a survey on my Facebook group asking about main programming language used by its members. It was very interesting to see a great variety of answers, so I thought, that today I’ll share my personal choice - Kotlin - and in addition I’ll tell you about 6 things, which made me fall in love with this programming language almost instantly. Who knows, maybe I’ll convince you to give Kotlin a try?
Data Classes
When working on a project, we often have to create classes, which sole purpose is to hold data. Sounds easy, right? No! Developers need to remember about implementing at least 3 crucial things inside these classes:
- Proper equality checks (overriding not only method related to equality like
equals()
, but also the one calculating hash likehashCode()
). - Proper string representation (overriding a method such as
toString()
) - Proper copying behavior (creating copy constructors).
The resulting class may become really really long and verbose even when it holds just few fields of data. In Kotlin, to combine all three requirements above you can just use one keyword - data
. Look at example below. By using data class
Kotlin automatically generates for you under the hood all required equality methods, copying behavior and clear string representation. It saves you a ton of time and code.
data class UserProfileData( val id: String, val name: String, val numberOfFriends: Int ) val userProfileData = UserProfileData( "2d44d1be99", "Matt", 150 ) val otherUserProfileData = UserProfileData( "tg771be99", "Jane", 150 ) // Output: UserProfileData(id=2d44d1be99, name=Matt, numberOfFriends=150) print(userProfileData.toString()) // Output: false (equals() and hashCode() are properly implemented by Kotlin) print(userProfileData == otherUserProfileData) // Output: true (generated copy constructor in action) print(userProfileData == userProfileData.copy())
Collection Operations
Lists, sets and maps are used every day by all developers. We manipulate data inside them, quite often writing very cumbersome helper methods to support these manipulations. Luckily Kotlin has a plethora of built-in functions, that you can use directly on collections. Grouping similar elements? Of course! Filtering items? Hell yeah! Mapping? Kotlin has got you covered, baby! They all fit together nicely into elegant functional code.
val listOfNames = listOf("Matt", "Jane", "Rob", "Ron", "John", "Tom") val listOfNamesLongerThanThree = listOfNames.filter { name -> name.length > 3 } val namesGroupedByFirstLetter = listOfNames.groupBy { name -> name.first() } val listOfNameHashes = listOfNames.map { name -> name.hashCode() } // Output: [Matt, Jane, John] // The list contains only elements with size greater than 3. print(listOfNamesLongerThanThree) // Output: {M=[Matt], J=[Jane, John], R=[Rob, Ron], T=[Tom]} // We grouped the list into a map, where key is the first letter of the name. print(namesGroupedByFirstLetter) // Output: [2390836, 2301262, 82341, 82353, 2314539, 84274] // Hash is a numerical representation of a given object. print(listOfNameHashes)
Scope Functions
My name is Matt. If you shout “Hey, Matt!” in a room filled with ten thousand people how will I know, that you’re addressing me? There are surely some guys with the same name there. But what if we would shrink the room and let only five people in with distinct names, including me? In this situation saying “Hey, Matt!”, without any additional effort in specifying the correct person, would get my attention instantly. What we effectively did by shrinking the room is a scope change. My name is now temporarily unique and we can use that to our advantage when performing a certain task (getting my attention). This is exactly how scope functions work in Kotlin. They manipulate a variable scope by making it smaller, so that you can reference this variable far easier and accomplish a task in a more efficient and readable way. Here are just some examples:
// Before using apply() scope function. val beforePaint = Paint() beforePaint.color = Color.MAGENTA beforePaint.style = Paint.Style.STROKE beforePaint.textSize = 10f // After using apply() scope function. val afterPaint = Paint().apply { color = Color.MAGENTA style = Paint.Style.STROKE textSize = 10f } val namesList = listOf("Matt", "Jane", "John", "Bob", "Rob") // Before using let() scope function. val correctNamesList = namesList.filter { name -> name.length > 3 } sendRequest(correctNamesList) saveToDatabase(correctNamesList) showConfirmationDialog(correctNamesList) // After using let() scope function. namesList.filter { name -> name.length > 3 }.let { output -> sendRequest(output) saveToDatabase(output) showConfirmationDialog(output) }
Coroutines
Most of the programming, that developers perform nowadays, is asynchronous: we create some request and wait for a result from the server or other data source. No wonder that many languages adopted a lot of solutions to handle this aspect of dev work: callbacks, Futures, Promises and Rx being the most popular - each with its own sets of pros and cons. But wouldn’t it be amazing to perform asynchronous programming in the regular sequential fashion, that we are used to? Imagine the readability and ease of learning. This is what coroutines (also called “lightweight threads”) do in Kotlin. They take complicated asynchronous code and with a few tweaks make it look, well, just like a regular one.
// Output: // Starting... // Hello! Function execution is here now! // Operations in the background have finished! println("Starting...") GlobalScope.launch { doSomethingLongInTheBackground() performServerRequest() println("Operations in the background have finished!") } println("Hello! Function execution is here now!")
Extensions
Extension functions enable us to extend functionality of existing class without usage of complicated design patterns, shaky inheritance or weird static helpers. Imagine that you come up with a great idea for another method in String
class. You can’t inherit from String
by making it a parent of your new StringWithMyNewFancyMethod
class, since String
class is final. Look at the example below to see what you can do instead in Kotlin:
// Notice that inside this extension function // we're acting as if we were inside the String class itself. fun String.myNewFancyMethod(): Map<Char, Int> { val outputMap = hashMapOf<Char, Int>() for (letter in this) { outputMap[letter] = letter.toInt() } return outputMap }
Inline Classes
This language feature is still experimental, but nevertheless I’ve started to like it lately. I am happy to steer you towards great, simple and straight-to-the-point article about inline classes from Adrian Bukros, which you can find here. If I were to “sell” you inline classes in one sentence, I would say something like this: Inline classes will combine your love to type safety, performance, object oriented programming and software free of stupid bugs (that you spend more time than you should to fix) by usage of a single keyword.