Kotlin for developers - an introduction
What if we make a language that takes all the good parts from other popular languages?
The most closer answer is Kotlin
.
Kotlin is gaining popularity now. Kotlin is 4th fastest growing language according to this report.
Let us explore it.
If you are coming from Javaland, Kotlin makes you write lesser code. It removes all the redundant things and makes your code elegant ✨ and concise ✅.
For example, you can write a simple hello world as below:
println("Hello World!!!")
If you are wondering that looks like scripting language, then you are most probably right. But the main difference is that Kotlin is statically typed and compiled language. When we execute the above println
function, the function is compiled and executed.
It is a fun
to write Kotlin functions:
fun hello() {
println("Hello World!!!")
}
No semicolons 🎉
No class definitions required 🎉💥
No default modifiers needed 🎉💥✨
The above function is a package-level
function. When we decompile the code into Java it will be final static
function inside the auto-generated class by Kotlin (based on the file name).
Klassy
Great, I love classes how will you define a class in Kotlin.
Kotlin makes it elegant and concise again:
class Greeting
That is it, it is a class definition, you don't even need a curly braces there to scope it. The compiler takes care of everything.
The Greeting class is a final
class by default. If you are wondering why remember the Good old Effective Java definition.
Let us define functions inside the class.
class Greeting {
fun sayHello(): String {
return "Hello"
}
fun sayHelloWithName(name: String) = "Hello $name"
}
The sayHello
is a classic function which returns a String
Hello. Note that we define the return type using : String
notation.
The second function sayHelloWithName
takes an argument name
that is String
. Since it is a function with a single line we can use =
sign and followed by the value that it returns. Again Kotlin tries to reduce the amount of boilerplate code.
Kotlin makes you enjoy the process of writing code by eliminating the boilerplate code. But do remember the compiler does that for you. This means compile time will be higher than compared with Java.
Constructors
In Object Oriented Programming, classes represent object. The objects will have member functions and constructors to initialize them.
For example, If you want to create an employee class with a constructor.
class Employee (name: String, salary: Long)
This is called as primary constructor.
We can initialise the Employee:
val employee = Employee("Raj", 100000L)
Note: there is no
new
word to initialize the object.
What if you want to add in an experience field for some employees who joins the company with some experience. We will use secondary constructors
.
Kotlin provides a way to define multiple constructors using the constructor
keyword.
class Employee (name: String, salary: Long) {
constructor(name: String, salary: Long, experience: Int): this(name, salary)
}
We defined the secondary constructor with constructor
keyword followed by the list of input arguments.
Note: When you define a secondary constructor along with a primary constructor then we should delegate the secondary constructor with the primary constructor.
We cannot define code in the primary constructor. This means if you want to define any code during the initialization you should either rely on constructor
method or init
method.
The init method is called when the class is initialised.
class Employee (name: String, salary: Long) {
init {
println("Employee ${this.name} is added")
}
}
Initialiser method will be called before the secondary constructor is initialised.
You can have multiple init
method inside a single class. The init
methods are called in the order it is defined.
Inheritance
Kotlin provides an easy syntax for inheritance.
class Manager {}
Now Manager class has to extend the Employee class. At the end of the day they are employees right.
class Manager: Employee() {}
When you run the above code, the compiler will yell at you. The main reason is that the employee class has a primary constructor with two arguments. We need to pass in the arguments to the Employee like below
class Manager: Employee(name: String, salary: Long)
But wait from where we will get these inputs to the parent class? We will pass it through Manager's primary constructor.
class Manager(name: String, salary: Long): Employee(name: String, salary: Long)
Ofcourse, Manager should have their team right? We will pass them too.
class Manager(name: String, salary: Long, team: List<Employee>): Employee(name: String, salary: Long)
But the compiler still errors. Kotlin makes all the classes final by default.
If you want to inherit from a class, you should open
it with an open
keyword. That is as follows.
open class Employee(name: String, salary: Long)
class Manager(name: String, salary: Long, team: List<Employee>): Employee(name: String, salary: Long)
Now the compiler is happy. 😃
Kotlin also provides
sealed classes
read more about here.
data classes
Redundant and verbose Plain Old Java Objects
take some rest. Kotlin's data class makes it concise and eliminates any boiler plate code. No getters
and setters
are needed.
data class User(val name: String, val age: Int)
The compiler will create everything automatically.
var and val
In Kotlin, immutable types are built in the library. The classes that you define are final, the methods are final, static too.
To define an immutable value we use the keyword val
:
val message = "Hello"
Once defined val
is immutable and you cannot change it.
However, being a friendly language, Kotlin also provides you with var
.
The values defined with var
are mutable.
var message = "Don't use me, it is a shame!!"
Null and !!, ?., ?:
Kotlin forces you to write null safety code. It tries to eliminate NPE at the compile time.
Kotlin compiles down to Java and then to JVM byte code. Java has Null Pointer Exception, so there can be instances in which you will get NPE.
var message = "Hello"
message = null
The above code will have a compilation error. But null
is everywhere right.
In Kotlin, we can define an Optional type using String?
that can accept null
value. That is
var message: String? ="Hello"
message = null
The above code compiles. But when you use the above code elsewhere like
message.toUpperCase()
The compiler yells at you. That is Kotlin compiler will prevent you from invoking a possible null value at the compile time.
We can shut down the compiler by using any of the following syntax:
!!
or ?.
or ?:
The first (!!
) is called not-null assertion operator
or dirty operator
. The double-bang tells the compiler, shut up and compile I know what I am doing.
message!!.toUpperCase()
This will not prevent runtime Null Pointer Exception.
The ?.
is called Safe-call operator
. This operator tells the compiler, call the function if the value is not null.
message?.toUpperCase()
The ?.
is the sugar-syntax for
if (message != null) {
message.toUpperCase()
}
The ?:
is called elvis operator
, it tells the compiler do this when not null else do the other thing.
message.toUpperCase() ?: "GIVE ME MESSAGE"
The ?:
is the sugar-syntax for
if (message != null) {
message.toUpperCase()
} else {
"GIVE ME MESSAGE"
}
Functions
lambda
Kotlin also makes it easy to switch to functional context when needed. The functions are first-class citizen in Kotlin.
What does it mean
first-class functions
? The functions can be used as an expression or data structure or passed around via arguments and return types.
val evenNumbers = 0..100.toList().filter { number -> number % 2 == 0 }
0..100.toList()
creates a list of 0 to 100. Then we apply filter
function (we don't need to stream
them).
The filter function takes in a lambda function. We can use both ()
and {}
syntax.
Then inside the lambda function we do the check for filtering even numbers and get the result. The final value returned is a list of numbers (and you don't need to collect
them)
Well Kotlin makes it
simple and concise right. The number above is redundant, we can eliminate that with it
.
val evenNumbers = 0..100.toList().filter { it % 2 == 0 }
The it
represents the current value from the list.
We can chain different functions together to make further transformations.
While Java is a more verbose, Kotlin makes it simple and concise.
Extension
One of the standout feature of Kotlin is extension
functions. Suppose you have a type like Color
which takes in a hex String and converts that into RGB value. You can create an extension
function like below on some primitive types like String
.
fun String.rgb() = this.removePrefix("#").windowed(2, 2).map { Integer.valueOf(it, 16) } // returns a list
println("#FFFFFF".rgb()) // 255, 255, 255
So what happens here,
We define a rgb
extension function and attach it to the String
base class. Now the extension function has this
which represents the value on which we are calling the rgb
method. Then we remove the prefix using removePrefix
function. Once removed we window the string into 2 character chunks and on the result we convert the hexadecimal string into Integer. Finally return a list of numbers.
The code is elegant and also fits naturally as a function defined in String.
Note: we can also override the existing functionality with
extension
functions. So use it with care.
Inline
We can add inline
keyword in a function definition. This will make the function inline at the call sites.
While inlining increases performance of the application, But beware that more you inline the functions the more the memory overhead will be.
Pattern matching a.k.a Type Checking
When I first learnt about pattern matching in Rust
that was a mind blowing experience. I always wanted to have that feature in the JVM world. But at that time I couldn't find or know any.
With Kotlin we can have pattern matching to check the types. This reduces a ton of casting, boilerplate code. There is no need to cast an Object manually. The compiler does that for you more efficiently.
when (message) {
is Int -> println("$message is a number")
is String -> println("$message is a string")
is Any -> println("${message} is any")
}
So what happens here. The is
keyword is used for pattern matching. It matches the type of message.
When the message type is Int
, the Int
segment is called.
When the message type is String
, the String
segment is called.
When the message type is Any
, the Any
segment is called.
This feature is upcoming in Java - Check out my post on Java 13 here
Type casting
We can cast the type of an object using as
keyword.
We can cast a type into another type like the following:
(1..10).toList().map { it as Any }
Any
type is the base type and it is defined as the root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
So we are type casting the Integer
into Any
using as
keyword.
Similarly we can also cast using as?
, this will provide a safe-nullable
type casting.
Check out this post on how to generate a Full Stack application with Kotlin, React and Spring Boot using KHipster here.
Wondering what is KHipster - check out here.