Recent Guides
One of the most common questions I get about Kotlin’s sequences is this:
“When should I use sequences, and when should I use normal collections?”
That’s the question we’re going to answer in this third and final article in this series! We’re going to look at the trade-offs of each, including the main things that affect their performance. Then, we’ll look at how Kotlin’s sequences stack up against Java streams, a similar concept.
Sequences are a fantastic way to process collections of data in a way that can perform better than the standard collection operations, as we saw in the previous article, Kotlin Sequences: An Illustrated Guide. Today, we’re going even deeper! We’re going to look under the hood - inside Kotlin sequences - in order to understand how they work like never before!
In fact, by the end of this article, we’ll even create our very own Sequence class and plug it into an operation chain!
You’ve probably come across Kotlin’s sequences at one time or another. Maybe you’ve heard that they can process data more efficiently than normal collections. But have you ever wondered exactly what they do, how they achieve the efficiency, or when you should use them?
In this article series, we’re going to cover just about everything there is to know about them!
In this article, we’ll walk through a story that will help us visualize the difference between sequences and normal collections.
In the last article, we discovered how Kotlin’s inline classes feature allows us to “create the data types that we want without giving up the performance that we need.” We learned that:
Inline classes wrap an underlying value. When the code is compiled, instances of the inline class get replaced with the underlying value. This can improve our app’s performance, especially when the underlying type is a primitive. In some situations, though, inline classes could actually perform more slowly than traditional classes!
Whether you’re writing massive data-chomping processes that run in the cloud, or mobile apps that run on low-powered cell phones, most of us want our code to run fast. And now, Kotlin’s inline classes feature allows us to create the data types that we want without giving up the performance that we need!
In this series, we’re going to take a look at inline classes from top to bottom!
This article explores what they are, how they work, and the trade-offs involved when choosing to use them.
Heads Up This article was written waaaay back in 2018 when the current version of Kotlin was 1.2.60. At that point, inline classes were an experimental feature, and you had to explicitly opt into them.
Good news! As of Kotlin 1.5, they’re now a stable language feature. If you’re ready to start learning about them, see the article, Introduction to Inline Classes
This article is preserved only for historical context.
If you’ve ever had trouble remembering the difference between parameters and arguments, today’s article is for you! Understanding this distinction can help when we’re talking about functions, and it can help even more when we’re talking about generics.
Parameters and Arguments - the TL;DR It’s a parameter when you’re inside the definition. It’s an argument when you’re outside the definition. The easiest way to recall the difference between the two is to associate the word argument with the word outside, by remembering this phrase:
Have you ever had a conversation like this?
Hopefully you haven’t had a conversation like that in real life, but you might have had one like that with your code!
For example, take a gander at this:
interface RestaurantPatron { fun makeReservation(restaurant: Organization<(Currency, Coupon?) -> Sustenance>) fun visit(restaurant: Organization<(Currency, Coupon?) -> Sustenance>) fun complainAbout(restaurant: Organization<(Currency, Coupon?) -> Sustenance>) } When you see a chunk of code with so many types smushed together, it’s easy to get lost in the details.
Have you ever wondered how star-projections work? Or why they change your function parameter and return types? Or why it seems like sometimes you can actually get by without them?
In the first article in this series, An Illustrated Guide to Covariance and Contravariance in Kotlin, we uncovered two simple, easy-to-understand rules that illuminate variance, and saw how they applied to regular class and interface inheritance in Kotlin.
In the second article, The Ins and Outs of Generic Variance in Kotlin, we saw how those same two rules played out for generics, discovering what type projections are and how they work.
Have you ever wondered why generic variance works like it does? Or why Kotlin won’t let you use a type parameter as an argument when it’s marked as out? Have you wondered why the compiler sometimes won’t let you call a certain function on a generic?
Yes, generics can seem mysterious, but with just two simple, easy-to-understand rules, we can reason our way through almost everything related to variance.
Generics can often seem confusing. How often have you started to solve a problem with generics, only to realize that they don’t quite work like you thought they did?
The good news is that there are some simple, foundational concepts that underpin generic variance. And once you understand those concepts, you won’t have to memorize acronyms or resort to trial-and-error - you’ll simply understand how and why they work!
In this article, I’m going to cover these foundational concepts, and then demonstrate how they play out in Kotlin class and interface inheritance.
Let’s think for a moment about everything that you can do with a class name in Kotlin - think of all the cases where you literally type out the name of a class in your source code. I came up with the following 15 cases, but I probably missed a few. Get your scrolling finger ready, here we go…
Define a member property: private val thing: Thing 2. Function argument: fun doSomething(thing: Thing) {} 3.
Kotlin allows you to constrain a type parameter on a generic when you declare it, limiting the range of types that it can accept. Why is this helpful? Well, let’s take a look at an example.
Let’s say you’ve got a few pets at home, and you want to pick a favorite:
fun <T> chooseFavorite(pets: List<T>): T { val favorite = pets[random.nextInt(pets.size)] // This next line won't compile - because `name` can't be resolved println("My favorite pet is ${favorite.
In Kotlin’s standard library, the Standard.kt file contains a number of scope functions that put a receiver object into a new scope as either this or as an argument (e.g., it).
The with() function tends to be a bit of an outlier. The others are all extension functions, so they’re invoked on an object. with(), on the other hand, takes its receiver (the object that it’s operating upon) as an argument.
Kotlin’s standard library includes some often-used scope functions that are so abstract that even those who have been programming in Kotlin for a while can have a hard time keeping them straight. In this guide, we’re going to clarify four of these scope functions in particular - let(), also(), run(), and apply(). By the end of this guide, you’ll have a framework for understanding them and should have a good idea of which one is most applicable in different scenarios.
When Java 8 introduced Streams for operating on collections of data, it also introduced a similar concept, Optional, which has many methods that are similar to Stream, but operates on a single value that might or might not be present.
As you migrate your projects from Java to Kotlin, you might come across some Optional objects. What should you do? Should you leave them as Optional, or change them to more idiomatic Kotlin?