This content was originally published as a series of articles on zsmb.co (with some runnable code snippets!).
I am currently defending the third place on the top users list of the Kotlin tag on StackOverflow, and I wanted to make use of the bragging rights this gives me while I can. The best way I found is to have a look at some of the most frequently asked questions about Kotlin on StackOverflow.
1. Array<Int>
vs IntArray
What's the difference between Array<Int>
and IntArray
?
This is a simple one to start with.
Array<Int>
uses the generic Array
class, which can store a fixed number of elements for any T
type. When using this with the Int
type parameter, what you end up in the bytecode is an Integer[]
instance, in Java parlance.
This is what you get when you use the generic arrayOf
method to create an array:
val arrayOfInts: Array<Int> = arrayOf(1, 2, 3, 4, 5)
IntArray
is a special class that lets you use a primitive array instead, i.e. int[]
in Java terms. (There are similarly named classes for the other primitive types as well, such as ByteArray
, CharArray
, etc.)
These can be created with their own (also non-generic, like the class itself) intArrayOf
factory method:
val intArray: IntArray = intArrayOf(1, 2, 3, 4, 5)
When to use which one?
Use IntArray
by default. Primitive arrays are more performant, as they don't require boxing for every element. They are also easier to create - an Array<Int>
requires a non-null value for each of its indexes, while IntArray
initializes them automatically to 0
values. Here's an example of this, using their constructors:
val intArray = IntArray(10)
val arrayOfInts = Array<Int>(5) { i -> i * 2 }
Use Array<Int>
when you're forced to use the Array
class by an API, or if you need to store potentially null
values, which an Array<Int?>
is of course able to do. If you need to create an Array<T?>
, the simplest way is using the arrayOfNulls
function of the standard library:
val notActualPeople: Array<Person?> = arrayOfNulls<Person>(13)
2. Iterable
vs Sequence
What's the difference between an Iterable
and a Sequence
?
Iterable
is mapped to the java.lang.Iterable
interface on the JVM, and is implemented by commonly used collections, like List
or Set
. The collection extension functions on these are evaluated eagerly, which means they all immediately process all elements in their input and return a new collection containing the result.
Here's a simple example of using the collection functions to get the names of the first five people in a list whose age is at least 21:
val people: List<Person> = getPeople()
val allowedEntrance = people
.filter { it.age >= 21 }
.map { it.name }
.take(5)
First, the age check is done for every single Person
in the list, with the result put in a brand new list. Then, the mapping to their names is done for every Person
who remained after the filter
operator, ending up in yet another new list (this is now a List<String>
). Finally, there's one last new list created to contain the first five elements of the previous list.
In contrast, Sequence
is a new concept in Kotlin to represent a lazily evaluated collection of values. The same collection extensions are available for the Sequence
interface, but these immediately return Sequence
instances that represent a processed state of the date, but without actually processing any elements. To start processing, the Sequence
has to be terminated with a terminal operator, these are basically a request to the Sequence
to materialize the data it represents in some concrete form. Examples include toList
, toSet
, and sum
, to mention just a few. When these are called, only the minimum required number of elements will be processed to produce the demanded result.
Transforming an existing collection to a Sequence
is pretty straightfoward, you just need to use the asSequence
extension. As mentioned above, you also need to add a terminal operator, otherwise the Sequence
will never do any processing (again, lazy!).
val people: List<Person> = getPeople()
val allowedEntrance = people.asSequence()
.filter { it.age >= 21 }
.map { it.name }
.take(5)
.toList()
In this case, the Person
instances in the Sequence
are each checked for their age, if they pass, they have their name extracted, and then added to the result list. This is repeated for each person in the original list until there are five people found. At this point, the toList
function returns a list, and the rest of the people in the Sequence
are not processed.
There's also something extra a Sequence
is capable of: it can contain an infinite number of items. With this in perspective, it makes sense that operators work the way they do - an operator on an infinite sequence could never return if it did its work eagerly.
As an example, here's a sequence that will generate as many powers of 2 as required by its terminal operator (ignoring the fact that this would quickly overflow):
generateSequence(1) { n -> n * 2 }
.take(20)
.forEach(::println)
Which one should I use?
Use Iterable
by default. You will be creating intermediary collections, but these generally don't affect performance too badly. In fact, for small collections, they might be faster than the overhead that using a Sequence
introduces.
Use a Sequence
if you need to handle an infinite number of elements - this is something they are uniquely capable of doing. Consider a Sequence
if you have very large collections to manipulate, and expect a performance gain from doing so lazily - perhaps you know that there are elements you won't need to process. As with always with performance advice, you'll have to benchmark your specific code to see if this really is the right choice for you.
Finally, use a Stream
if you are going to be interoperating with Java code that already uses them. They work just fine in Kotlin (while Sequence
s do not work in Java).
3. Iteration with indexes
How can/should I iterate over a collection of items?
Here's probably everyone's first idea of how to do this after seeing the for
loop syntax with ranges:
for (i in 0..args.size - 1) {
println(args[i])
}
Then you might find out that Array
has a lastIndex
extension property that's easier to read:
for (i in 0..args.lastIndex) {
println(args[i])
}
Then you realize that you don't actually need the last index, you just need to have an open ended range, which is what until
creates:
for (i in 0 until args.size) {
println(args[i])
}
Of course, then again you'll find out that you can get this same range with the indices
extension property:
for (i in args.indices) {
println(args[i])
}
But you can also iterate the collection directly instead of a range of indexes, with this syntax:
for (arg in args) {
println(arg)
}
Or you can go a bit functional and use the forEach
function, passing a lambda to it that will do the work on each element:
args.forEach { arg ->
println(arg)
}
These are all very, very similar in terms of the generated code - in this case of iterating over an Array
, they all increment an index variable and get elements by the index in a loop.
But if we were iterating a List
instead, the last two solutions would instead use an Iterator
under the hood, while the rest would still make a get
call with each index. The Iterator
approach here has the potential of being more efficient for certain collections1.
1 For example, iterating over a LinkedList
would be an O(n^2)
operation, as a LinkedList
has O(n)
lookup times. It wouldn't matter for an ArrayList
, which has O(1)
lookups, since it uses an Array
to store its items.
Using the indices
extension property is a close third in the race - it uses a for loop and looks up elements by index, but it has fairly clean bytecode compared to the three other methods before it.
What if I need the current item's index as well?
Firstly, there's the withIndex
extension function that returns an Iterable
of objects that can be destructured into the current index and element:
for ((index, arg) in args.withIndex()) {
println("$index: $arg")
}
However, there's also the forEachIndexed
function that calls the provided lambda for each index and argument.
args.forEachIndexed { index, arg ->
println("$index: $arg")
}
Both of these functions use iterators for lists, but while the withIndex()
solution uses an iterator for arrays too, the forEachIndexed
is nicely optimized and uses indexes in that case.
4. SAM conversions
SAM conversions are a very frequent topic of questions. To address these, let's construct a single code example to show off various issues on.
We'll have an OnClickListener
interface with a single method declared in Java:
public interface OnClickListener {
void onClick(Button button);
}
And a Button
class which can be given a listener that's called for every click of the button:
public class Button {
public void setListener(OnClickListener listener) { ... }
}
The full syntax for creating an anonymous listener in Kotlin would look like this:
button.setListener(object: OnClickListener {
override fun onClick(button: Button) {
println("Clicked!")
}
})
Thanks to SAM conversion, you can also pass in just the inner braces and their contents in this case, as a lambda:
button.setListener {
println("Clicked!")
}
Why is my click listener not being called?
A common mistake when using SAM conversion is not directly putting the code you want to run inside your first set of braces which represent your lambda. This code will work as expected:
button.setListener {
println("Clicked!")
}
However, none of the following ones will.
This one gives the button a lambda that will create an anonymous instance of an OnClickListener
every time the button is clicked, which is then just thrown away (never assigned anywhere, never invoked).
button.setListener {
object: OnClickListener {
override fun onClick(button: Button) {
println("Clicked!")
}
})
}
This one declares a useless local function in a very similar fashion:
button.setListener {
fun onClick(button: Button) {
println("Clicked!")
}
}
How do I refer to the anonymous instance if I create it with a SAM conversion?
Sometimes you need to refer to the listener instance itself when it's called, for example, you might want to remove it in some cases. In Java, you could reference the anonymous class with the this
keyword. If you use SAM conversion, this is simply not possible. For these cases, you have to use the full object
expression syntax:
button.setListener(object: OnClickListener {
override fun onClick(button: Button) {
button.removeListener(this)
}
})
Why is my SAM conversion complaining about a return type?
SAM conversions by their concise nature hide the required return types of the methods that they're overriding. For example, if the OnClickListener
interface looked like this instead, with a boolean
return to signify whether the click has been handled:
public interface OnClickListener {
public boolean onClick(Button button);
}
Then this implementation would no longer be valid:
button.setListener {
println("Clicked!")
}
You'd get the following error on the println
statement:
Kotlin: Type mismatch: inferred type is Unit but Boolean was expected
This is a problem on the println
statement because lambdas (somewhat controversially) implicitly return their last expression's return value, in this case, Unit
. The fix of course is to add a statement that matches the required return type as declared in the interface:
button.setListener {
println("Clicked!")
true
}
I can't use SAM conversions for Kotlin interfaces! What do I do now?
It often comes up that SAM conversions only work as described above if both the OnClickListener
interface and the Button
class (or more importantly, the method that takes the listene
If the interface is defined in Java, r parameter) are defined in Java.
If the interface is defined in Java, but the function that takes an instance of it is defined in Kotlin, you'll have to use a more explicit language construct, a SAM constructor:
button.setListener(OnClickListener {
println("Clicked!")
})
And if your interface is defined in Kotlin, you can do nothing but use a full object expression syntax:
Java interface | Kotlin interface | |
---|---|---|
Java method | SAM conversion | Object expression |
Kotlin method | SAM constructor | Object expression |
The explanation in the documentation for this is that in Kotlin we have function types, and we should use those instead - however, doing so leads to occasional weird usage from Java code. Note that the table above doesn't mention usage from Java, since all of the above combinations can simply be used with lambdas there.
In conclusion, if you don't need to support Java, you can just use function types freely in Kotlin, perhaps with type aliases to make your types clearer. If you do have to interop with Java still, define your interfaces in Java, and if you can, also the methods that take these interfaces. This will give you the best experience in both languages.
5. Replacing static things
How can I make a function or variable static?
This turned out to be a way too long and boring topic to explain in a talk like this, but here's the basics quickly. If your class has "non-static" parts as well, place the "static" parts in a companion object:
class Foo {
companion object {
fun x() { ... }
}
fun y() { ... }
}
If your class was completely static, you can also replace it with a singleton object
:
object Foo {
fun x() { ... }
}
And if you don't want to always scope your calls as Foo.x()
but want to just use x()
instead, you can use a top level function:
fun x() { ... }
Additionally, if you'll need to call these "static" parts from Java, look into using the @JvmStatic
and @JvmName
annotations to make those calls nicer. Here's a reference table for what the annotations do to Java calls:
Function declaration | Kotlin usage | Java usage |
---|---|---|
Companion object | Foo.f() |
Foo.Companion.f(); |
Companion object with @JvmStatic |
Foo.f() |
Foo.f(); |
Object | Foo.f() |
Foo.INSTANCE.f(); |
Object with @JvmStatic |
Foo.f() |
Foo.f(); |
Top level function | f() |
UtilKt.f(); |
Top level function with @JvmName |
f() |
Util.f(); |
For variables, the same rules about where to place them apply, and so the the above annotations. Additionally, there's a @JvmField
annotation that can be used with variables, plus the const
keyword which optimizes calls to compile time constants by inlining their values to the call sites at compilation time. Here's the reference:
Variable declaration | Kotlin usage | Java usage |
---|---|---|
Companion object | X.x |
X.Companion.getX(); |
Companion object with @JvmStatic |
X.x |
X.getX(); |
Companion object with @JvmField |
X.x |
X.x; |
Companion object with const | X.x |
X.x; |
Object | X.x |
X.INSTANCE.getX(); |
Object with @JvmStatic |
X.x |
X.getX(); |
Object with @JvmField |
X.x |
X.x; |
Object with const | X.x |
X.x; |
Top level variable | x |
ConstKt.getX(); |
Top level variable with @JvmField |
x |
ConstKt.x; |
Top level variable with const | x |
ConstKt.x; |
Top level variable with @JvmName |
x |
Const.getX(); |
Top level variable with @JvmName and @JvmField |
x |
Const.x; |
Top level variable with @JvmName and const |
x |
Const.x; |
How can I declare a static initializer?
This is a simpler and much quicker question. If you need to run certain code just once for a class, you can use initializer blocks inside their companion object
to do so - these will be translated to regular static initializers.
class X {
companion object {
init {
println("Static initialization!")
}
}
}
Nullability
6. Smart casts on mutable properties
Why doesn't smart cast work on mutable properties?
Here's what the problematic code looks like:
class Dog(var toy: Toy? = null) {
fun play() {
if (toy != null) {
toy.chew()
}
}
}
toy
is expected to be smart cast inside the if
block from Toy?
to Toy
, as it has been checked for nullability. However, this error appears:
Kotlin: Smart cast to 'Toy' is impossible, because 'toy' is a mutable property that could have been changed by this time
The issue is that this Dog
instance could be modified from another thread between the time the toy != null
check is made, and the toy.chew()
method is called. This could potentially cause a NullPointerException
.
What can I do to make it work?
The issue doesn't exist if the property is not mutable. It's a good idea to make everything (both properties and variables) a val
by default, and only make them mutable if you must.
If they really have to be mutable, using a local immutable copy solves the problem - smart cast works as expected on an immutable variable:
class Dog(var toy: Toy? = null) {
fun play() {
val _toy = toy
if (_toy != null) {
_toy.chew()
}
}
}
But of course, there's a nicer way to make a temporary copy:
class Dog(var toy: Toy? = null) {
fun play() {
toy?.let {
it.chew()
}
}
}
At this point, you should probably have noticed the obvious solution for this simple case of calling just a single function on the property in question:
class Dog(var toy: Toy? = null) {
fun play() {
toy?.chew()
}
}
But the solution using let
works for the cases where you do more complex things after your null
check.
7. null!!
Why exactly does null!!
crash?
Here's some example code of a property which is meant to be non-null, but for some reason can't be initialized when the class is constructed. Putting !!
after null
lets the code compile, but at what cost? Any time an Episode
is constructed, it will crash with a KotlinNullPointerException
.
class Episode {
var airdate: Date = null!!
}
The problem here is that the !!
operator is often perceived to be similar to the safe call operator ?.
, and therefore thought to not do anything in the code above, since there's nothing on its right hand side anyway. It's also sometimes thought to be a compile time signal to the compiler to "just let your code compile", which it is not.
Let's take a look at another code sample:
fun scheduleEpisode(airdate: Date?) {
airdate!!.getWeekday()
}
The most important point here is that the !!
operator can exist and does its job without anything following it. While airdate
is the first expression of the line, the next expression is not airdate!!.getWeekday()
, but instead just airdate!!
. The way this is evaluated is the following:
- If
airdate
is non-null, it returnsairdate
, but now as a non-nullable type, in this case,Date
, which is why thegetWeekday
method can be then called without a safe call operator. - If
airdate
isnull
, it throws aKotlinNullPointerException
.
You can think of !!
as having the same functionality as an extension function like this would:
fun <T> T?.forceNonNull(): T {
if (this == null) {
throw KotlinNullPointerException("Oops, found null")
} else {
return this as T
}
}
So the previous usage of !!
would be equivalent to this:
fun scheduleEpisode(airdate: Date?) {
airdate.forceNonNull().getWeekday()
}
Finally, let's get back to our original problem:
class Episode {
var airdate: Date = null!!
}
The issue should now be obvious. Before the value of the expression null!!
is assigned to the property, it has to be evaluated, with throws the exception.
8. Platform types
How do I decide the nullability of a parameter when overriding a Java function? What are the consequences of each choice?
Let's return to our OnClickListener
class defined in Java, with a single callback method.
public interface OnClickListener {
void onClick(Button button);
}
We'll assume that sometimes a valid Button
instance isn't provided for the click event (perhaps the Button
is disabled by the time the listener is called). We'll also forget to annotate these parameters in the Java code accordingly.
Let's override this interface in Kotlin - we'll end up with the following method signature if we let IDEA generate it:
class KtListener: OnClickListener {
override fun onClick(button: Button?): Unit {
val name = button?.name ?: "Unknown button"
println("Clicked $name")
}
}
Since the Java method has no nullability annotations, the parameters of the Kotlin function will have a platform type, in this case, Button!
. This means that it's up to us to decide (based on experience, or hopefully, documentation) whether the parameter received by the method can be null
.
By default, it's safer to use the nullable types for all parameters, and suffer the restrictions that the compiler forces on us to handle these safely. This results in some extra code, but is always safe.
For parameters which are known never to be null
, the non-nullable type can be used instead - both will compile. If the method does get called with a null
value for a parameter we marked to be non-null, an injected null
check will produce an IllegalArgumentException
before any of the code in the method body would run, making this a slightly riskier choice. Of course, having a non-nullable parameter simplifies our code:
class KtListener: OnClickListener {
override fun onClick(button: Button): Unit {
val name = button.name
println("Clicked $name")
}
}
Small bonus tip
9. Import aliases
How can I use multiple extension functions with the same name in a file?
Let's say you implement two different indent
extension functions on the String
class in two different packages. If this was an ordinary function, you could call it with a fully qualified package name, however, the syntax doesn't allow for it with an extension function - there's quite simply nowhere to put the package name:
"hello world".indent()
However, you can use the as
keyword at the import
statement to rename one (or both) of them, like so:
import com.example.code.indent as indent4
import com.example.square.indent as indent2
Which then lets you use each of them by their new names:
"hello world".indent4()
Another use case is that you could be using two classes with the same name from different packages in the same file (e.g. java.util.Date
and java.sql.Date
), and you don't want to use either of them by their fully qualified name. You could do the following here:
import java.util.Date as UtilDate
import java.sql.Date as SqlDate
Within this file, you can now refer to these classes by their aliases.
Wrap-up
10. Kotlin vs Java
Which one should I learn if I'm just starting to learn Android development?
❓
❓
❓
❓
❓
❓
❓
❓
❓
❓
❓
I'd consider nothing other than Xamarin. Don't waste your time on native development, it's 2018. /s
Anyhow, that's a wrap, thank you for reading this article, and see you on StackOverflow 🙂
Follow us on social media