It is easy to fall in love with Kotlin. I would like to tell you about 10 Kotlin features I consider to be the best ones. I will also describe each of them in detail and explain why they made it to the top 10.
Kotlin – Top 10 Features You’ll Love
1. Null Safety
Kotlin is null safety language:
class Owner { var adress: String = "" var telephone: String = "" var email: String? = null }
An address and telephone cannot be null in this example – an error occurs at compile time if you wish to set their value as null:
You need to say explicitly that a variable is nullable in order to be able to assign null to it. This could be done via “?” inserted right after the variable type (see an example of variable email above).
var a: String = "abc" a = null // compilation error var b: String? = "abc" b = null // ok val y: String = null // Does not compile.
Your code could fail at compile-time whenever an NPE is thrown at run-time:
val x: String? = "Hi" x.length // Does not compile.
In order to solve a compilation error of x.length
, you can use if-statement:
if (x != null) { x.length // Compiles! Not idiomatic just to get length! }
The better way of achieving the same is via “safe calls in Kotlin” which returns the value of the call if the caller is not null, otherwise null returns.
x?.length /* You can tell to compiler that you know the variable cannot be null at run-time via non-null operator "!!" */ val len = x!!.length // Will throw if null.
Elvis Operator
You can work with nullable types more effectively by using the “Elvis Operator”.
// Elvis operator. val len = x?.length ?: -1
In this case if x is not null its length will be used, otherwise -1 is used (as it is a default value).
2. Data Class
When you are creating business logic, persistent layer etc., you are creating domain objects, classes that do nothing but hold data (POJOs)… For instance, you must create Customer class which holds data about the customer’s id, name, and email.
In Java:
public class Customer { private int id; private String name; private String email; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
In Kotlin a class can be marked as data class:
data class Customer(val id: Int, val name: String, val email: String)
The Kotlin compiler will do all of this for you:
- generate functions:
hasCode()
,equals()
,toString()
- add
copy()
function (see more about this function below) - componentN() functions
Copying
Sometimes you need to copy an object to a new one with different values of your
variables. This is what the copy()
function is for:
val customerSouhrada = Customer(id = 2001, name = "Vaclav Souhrada", email = "vsouhrada@email.com") val updatedCstSouhrada = customerSouhrada.copy(email = "vaclav_souhrada@email.com")
3. Extension Functions
Kotlin allows us to extend the functionality of existing classes without inheriting from them.
fun String.capitalize(): String { return this.toUpperCase() }
The function capitalize()
is an extension function of the String
class. Inside the extension function, you access the object on which it was called using the keyword “this”.
fun String.hello() { println("Hello, $this!") } fun String.and(input: String): String { return "${this} $input" }
Extensions do not modify classes they extend and are resolved statically.
fun main(args: Array<String>) { println("vaclav souhrada".capitalize()) // prints VACLAV SOUHRADA "Vaclav".hello() // prints 'Hello, Vaclav!' var testString = "This is a string".and("This is another") println(testString) // prints 'This is a string This is another' }
Thanks to extensions, function code in projects where I’m using Kotlin is much cleaner. I do not have xxUtils(.java)
classes anymore.
Conversion to JSON could be a good example for using extension functions. Let’s demonstrate it on a very simple example that adds extension function to all “objects” in Kotlin. (kotlin.Any
and java.lang.Object
are different types, but in runtime, they are represented with the same java.lang.Object
class).
val gson = Gson() // ... fun Any.toJSON(): String { return gson.toJson(this) } // ... // Now if we want to convert it to JSON we can just simple call ourObject.toJSON() val customer = Customer(id = 2001, name = "Vaclav Souhrada", email = "vsouhrada@email.com") val json = customer.toJSON()
4. Smart Cast
This is one of my favorite features as it makes my life much easier.
How often have you already casted objects? How often did you find out afterwards that it was actually redundant?
For example, if you want to check Object which is an instance of java.lang.String
, in order to print size this text you need to check the type first and then cast the object to the String before getting access to the specific method from the String object. When I started using Kotlin I did not consider this to be anything special, but after a few days I realized I really miss this feature in Java.
The Kotlin compiler is really smart when it comes to casting. It will handle all redundant casts for you.
The instanceof
Java operator in Kotlin is called is
. You can see in the example above that in Kotlin you do not need to cast object inside of statement if you already checked it with an operator is. I did not consider those redundant casts in Java as something strange until I began using Kotlin.
When expressions represent another example of smart casts:
Smart cast together with when expression make code readable:
In the example above you can see how to check if the event which comes from event bus can be checked and cast to correct java object. I agree that in this case it does not look so bad in Java but in Kotlin you can sum it up into this code:
I may be wrong but from my point of view, Kotlin takes the cake.
5. Singleton (Object)
Another Kotlin feature which I like is the simplicity of defining “singleton”. Consider the following example of how singleton could be created in Java.
Now you can call i.e.
public class SingletonInJava { private static SingletonInJava INSTANCE; public static SingletonInJava getInstance() { if (INSTANCE == null) { INSTANCE = new SingletonInJava(); } return INSTANCE; } }
Kotlin has a feature to define a singleton in a very clever way. You can use a keyword object which allows you to define an object which exists only as a single instance.
object SingletonInKotlin { } // And we can call SingletonInKotlin.doSomething()
6. Functional Programming
A combination of lambda expression and the Kotlin library makes our life easier when working with collections:
val numbers = arrayListOf(10 ,5 , -9, 9, 11, 5, -6) val nonNegative = numbers.filter { it >= 0} println(nonNegative) // [10, 5, 9, 11, 5]
Or more:
// Sum of all elements: 25 println(numbers.foldRight(0, { a, b -> a + b })) //20 10 -18 18 22 10 -12 numbers.forEach { println("${it * 2} ") } val kindOfNumbers: Iterable<String> = numbers.filter { it < 0 } .map { "$it is negative" } println(kindOfNumbers) // [-9 is negative, -6 is negative]
7. Type Inference
In Kotlin, you do not have to specify the type of each variable explicitly:
val name = "Vaclav" val age = 31 // Only need Iterable interface val list: Iterable<Double> = arrayListOf(1.0, 0.0, 3.1415, 2.718) // Type is ArrayList val arrayList = arrayListOf("Kotlin", "Scala", "Groovy")
8. Default Arguments
In Java, you often have to duplicate code in order to define different variants of a method or a constructor:
public class OperatorInfoInJava { private final String uuid; private final String name; private final Boolean hasAccess; private final Boolean isAdmin; private final String notes; public OperatorInfoInJava(String uuid, String name, Boolean hasAccess, Boolean isAdmin, String notes) { this.uuid = uuid; this.name = name; this.hasAccess = hasAccess; this.isAdmin = isAdmin; this.notes = notes; } public OperatorInfoInJava(String uuid, String name) { this(uuid, name, true, false, ""); } public OperatorInfoInJava(String name, Boolean hasAccess) { this(UUID.randomUUID().toString(), name, hasAccess, false, ""); }
You can simply remove all of this when you switch to Kotlin by using default arguments.
class OperatorInfoInKotlin(val uuid: String = UUID.randomUUID().toString(), val name: String, val hasAccess: Boolean = true, val isAdmin: Boolean = false, val notes: String = "") {}
9. Named Arguments
Default arguments become more powerful in combination with named arguments. You can see for yourself in the example of creating an instance of our OperatorInfoInKotlin from the previous section:
OperatorInfoInKotlin(name = "Vaclav") OperatorInfoInKotlin(name = "Vaclav", hasAccess = false, isAdmin = false, notes = "blabla")
Now back to Java.
Based on this Java code, you do not know which values are set without knowing the
OperatorInfoInJava class.
// Java
new OperatorInfoInJava("Vaclav", false, false, "blabla");
10. Collections
In Kotlin you have higher-order functions, lambda expressions, operator overloading, lazy evaluation and lots of other useful methods for working with the collection.
Lets demonstrate it on simple tasks:
“What is the average age of employees in the company in Pilsen city?”
(code below is in Java 1.6, of course you could use stream in Java 1.8 and write the code through lambda in Java as well)
Extension functions and functional programming are very powerful and Kotlin has many useful extensions for working with collections.
public double getAverageAge(@NotNull List<Employee> employees) { int ageSum = 0; int count= 0; for (Employee employee : employees) { if ("Pilsen".equals(employee.getCity()) { ageSum += employee.getAge(); count++; } } if (count == 0) return 0 return ageSum / count; }
You can implement the same example in Kotlin using the following steps.
In the first step, you filter and keep all employees which are from the Pilsen city (Czech Republic)
fun getAverageAge(val employees: List<Employee>): Double { return employees.filter{ it.city == City.PILSEN }.map{ it.age }.average() }
For more info about collection see the official documentation or the blog post written by Antonio Leiva.
Which Kotlin features do you appreciate the most when working with it?