Some Awesome Features of Kotlin
Recently Google has introduced Kotlin (also supporting Java) as their official language. This article will state some of the exciting features you can use while coding in Kotlin
- Extension functions:
Let’s say you are using a library for which you do not have code access and you want to add a method inside the library class.
or
For a String object you want to call toString() method but do not want to check for the object’s nullability.
or
For an integer you want to call <Int>.isPrime().
With the help of extension functions, you can achieve what I said.
fun Int.isPrime(): Boolean {
if (this <= 1) return false
// Check from 2 to n-1
for (i in 2 until this)
if (this % i === 0)
return false
return true
}Log.d("Kotlin", 5.isPrime().toString())
Log.d("Kotlin", 15.isPrime().toString())
Here first log statement would print “true” and second will return “false”.
Same way Kotlin has implemented the toString method so that you do not have to worry about the object being null before calling the method.
Make a note that, Extension functions are not getting adding into the corresponding class, but merely make new functions callable with the dot-notation on variables of this type.
2. Higher-Order Functions & Lambda Expressions:
These functions take functions as a parameter and return a function. I will try to explain higher-order functions using an example.
In Java most of the time we end up writing code like this:
try{
//TODO
}
catch (e : Exception){
// Catch Exception
}
Using Kotlin this can be reduced to AvoidException { //TODO }. AvoidException can be defined as follows:
fun AvoidException( body : () -> Unit){
try{
body
}
catch (e : Exception){
Log.d("Kotlin","Exception")
}
}
Here we are passing a function as a parameter in AvoidException and in the definition, the function is being implemented where the body part is put in to try block.
fun divide(div: Int, number: Int) {
number / div
}
Let’s say we want to call the above function, but this might throw an exception if we try to divide the number by 0.
In Kotlin we can call it like this:
AvoidException { divide(0, 3) }
AvoidException { divide(3, 3) }
As a parameter, we are passing a function which has not been declared and called lambda expressions. Lambda expressions are always covered by curly braces.
3. Getting rid of Null Pointer Exception :
In Kotlin by default, the object is not null. If you want an object to have a null value, you have to declare it explicitly And once you do that every time you try to access a variable on that object Kotlin will throw a compile-time error unless you check for the null check.
val a : String = null -- compile time error
val a: String? = null-- Right way of allowing null for an object
val b = a?.length -- this will make sure that b is assigned a value only if a is not null.
In case you want to assign a value to b even if a is null, you have to use Elvis operator.
val c = a?.length ?: 1 -- if a is null, c would be equal to 1.
In Other case if you are sure that "a" wont be null then you can write an expression like this :
val d = a!!.length
4. Smart Typecast :
Kotlin has “is” and “as” operator defined. using “is” operator, On run time you can confirm data type of an object and use “as” you can typecast the object to its corresponding type and start using.
fun Any?.getLength() : Int{
return if(this is String) this. length else 0
}
fun String?.getString() : String{
return if(this is String) this as String else ""
}
Using the first function we can call getLength() on any type of object but only if it is a string it will return the length of object else it will return 0.
The same way “this as String” will typecast object to string type and once you do that you can start calling methods of String class on the object.
5. Operator Overloading
In Kotlin, You can have the implementation of predefined operators for an object. An operator has a symbol and a translation. The operator can be implemented using a member function with the translation name for the corresponding object type.
e.g.
a + b = a.plus(b), Here '+' is the symbol and 'plus' is the translation.
a > b = a.compareTo(b) > 0, Symbol is '>' and 'compareTo' is translation
Let’s try to understand overloading via an example:
class Person(
val age: Int = 0,
val name: String = ""
)
operator fun Person.plus(p: Person) = p.age + this.age
operator fun Person.compareTo(p: Person) = this.age - p.age
Here plus function sums the age of two persons and compareTo functions compare two persons based on their ages.
val p1 = Person(20, "A")
val p2 = Person(22, "C")
val totalAge = p1 + p2, // '+' will call 'plus' function, returns 42
val p1IsGreater = p1 > p2 // '>' will call compareTo, returns false
You can explore more on operator overloading here.
6. Lazy Properties
Lazy property delays object initialization and are useful for objects which are resources heavy. We can load such objects on the fly when needed.
Lazy is a function that takes a lambda and returns an instance of Lazy<T> which serves as a delegate for the corresponding property.
Kotlin comes up with lazy delegate which prevents unnecessary initialization and loads the data only when it is required. Once data is fetched, It will be put into in-memory so that it can be further used. e.g.
Let’s say we are showing the final score of a football game, now there is an option using which user can see all the players and their corresponding contribution in the game. In our code, we can only do it by making a database call but we also know that it is not necessary that every one of the users would want to see a detailed score. So here we could use lazy property, only when a user clicks on a detailed view of the score, we get the data from database and display it to the user.
Team class has name, score and players properties, players are lazily loaded.
class Team {
var name: String = ""
var score: Int = 0
val players by lazy {
println("fetching")
getPlayers(this)
}
}
fun getPlayers(team: Team): List<String> {
//call database here and get full score card along with each player. Here for the sake of example i am simply returning list of players.
return listOf("A", "B", "C")
}
Let’s find out what happens when I try to execute the below statements:
val team = Team()
println(team.players.toString())
println(team.players.toString())
//prints
fetching
[A, B, C]
[A, B, C]
First time group.players prints “fetching“ but does not do that next time. Since because of lazy loading once the lambda expression is executed, it will use the same result rather than calculating it again.
7. Collection functions:
Kotlin comes up with predefined functions that can be applied to different collections, these functions are easy to use and may make things easier for you. These functions are nothing but higher-order functions that can be implemented easily.
Let’s see some of the important ones.
filter: will filter out the data set based on the provided condition.
map: will give results after applying given transformation.
groupBy: will divide the results into groups based on the given condition.
sortedWith: can sort data based on given attributes, multiple attributes can be applied for sorting.
In the example below, we are using ‘it’ keyword which is an implicit parameter and can be used to access properties of a class.
class Person(val name: String, val age: Int)
var listOfPerson = mutableListOf<Person>()
listOfPerson.add(Person("A", 18))
listOfPerson.add(Person("E", 14))
listOfPerson.add(Person("C", 14))
listOfPerson.add(Person("D", 16))
val sortedList = listOfPerson.sortedWith(compareBy ({ it.age},{it.name}))
println(sortedList.toString()) // this will print C,E,D,A
val list = listOf(1, 2, 3).filter { it != 2 }.map { it * 2 }.sortedWith(compareBy { it })
println(list.toString()) // this will print 2,6
These examples are to give an idea of available functions, there are many other functions available. You might want to give them a try.
8. Optional Parameter in a Function
An example ahead
fun addTwoDigits(first: Int = 1, second: Int): Int {
return first + second
}
var sum = addTwoDigits(2, 3) // prints 5
sum = addTwoDigits(second = 2) // prints 3
We can declare an optional parameter with a default value. So in case, you don’t pass a value of that parameter, the optional value would be used. In Kotlin you can also pass parameters in a different order as well by using the name of the parameter.
9. Local Functions
Local functions are good for code reuse, just be careful not to overuse them to avoid confusion.
fun foo(a: Int) {
fun local(b: Int) {
return a + b
} return local(1)
}
10. Infix functions
Infix functions are good for readability because it allows typing something like "test" foo "x"
for example, pretty cool eh!
infix fun String.foo(s: String) {
...
}// Call extension function.
"test".foo("x")// Or call extension function using infix notation.
"test" foo "x"
Infix functions must have a single parameter.
11. Inline functions
A lambda expression in Kotlin is translated to Java anonymous classes in Java 6 or 7, that is overhead. Lambda calls are affecting the call stack which has a performance impact.
inline
functions can be used to flat out calls instead of invoking another method call and adding that to the call stack. So it makes sense to use inline
functions when we pass in the lambdas.
inline fun callBlock(block: () -> Unit) {
println("Before calling block")
block()
println("After calling block")
}
When we call callBlock
it gets translated to something like the following:
callBlock { println("The block operation") }// Rough java bytecode
String var1 = "Before calling block";
System.out.println(var1)
String var2 = "The block operation";
System.out.println(var2);
var1 = "After calling block";
System.out.println(var1);
vs. the following, if the function was not marked as inline
callBlock { println("The block operation") }// Rough java bytecode
callBlock((Functinos0)null.INSTANCE);
You have to be careful with inline
functions though because it literary copies the method content where it is called and if the body of the functions is too large you really do not want to do this.
Knowing that the following obviously will not make any sense because it has zero effect.
inline fun foo(noinline block: () -> Unit) {// Single lambda marked as noinlineinline fun foo() { // No lambdas
12. Tail recursion
By using tailrec
we let the compiler know that it can replace the method call with a for loop or goto statement.
You can only use it if the last call in a function is only calling itself and only itself.
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))