Kotlin is a modern, statically typed programming language that makes development faster and more enjoyable. It is widely used for Android app development, web development, and server-side development. Kotlin supports object-oriented and functional programming paradigms, making it versatile for various types of projects. It’s capability of DSL (Domain Specific Language) creation makes it more powerful.

Few things I like about Kotlin are

  • Extension functions and higher-order functions make the code more readable and maintainable.
  • Kotlin’s null safety features reduce the risk of null pointer exceptions.

Few things I dislike about Kotlin are

  • The learning curve can be steep for beginners due to its rich feature set.
  • The compilation time can be slow for large projects.

Variables

Variables in Kotlin can be declared using var for mutable variables and val for read-only (immutable) variables. Kotlin supports type inference, allowing you not to explicitly declare the variable type in most cases.

Syntax and Example:

// Variable declaration ( mutable )
var name: String = "John Doe"
var age = 30 // Type is inferred to be Int

// Variable declaration ( immutable )
val name: String = "Jane Doe"
val age = 25 // Type is inferred to be Int

Control Statements

Kotlin’s control flow statements are similar to other languages, including if-else, when (switch-case), and loops (for, while).

Syntax and Example:

// If-else statement
val number = 10
if (number > 0) {
    println("Positive number")
} else {
    println("Negative number or zero")
}

// For loop
for (i in 1..5) {
    println(i)
}

// While loop
var i = 1
while (i <= 5) {
    println(i)
    i++
}

// When (switch-case) statement
val grade = 'A'
when (grade) {
    'A' -> println("Excellent")
    'B' -> println("Good")
    'C' -> println("Fair")
    else -> println("Fail")
}

Functions

Functions in Kotlin are defined using the fun keyword. They can have parameters and return types. Lambda expressions and higher-order functions are also supported. In kotlin, functions are first-class citizens, which means they can be passed as arguments to other functions, returned as values from other functions, and assigned to variables.

Syntax and Example:

// Function declaration
fun greet(name: String): String {
    return "Hello, $name!"
}

// Lambda expression
val sum: (Int, Int) -> Int = { a, b -> a + b }

// Extension function for a Class (MyClass)
fun MyClass.myExtensionFunction() {
    println("Hello, Extension!")
}

// function with multiple return values
fun getPerson(): Pair<String, Int> {
    return Pair("Alice", 25)
}
val (name, age) = getPerson()

// function with implicit return
fun implicit(): String {
    if(1 == 1) {
        "A" // This will be last expression encountered by function, it will be returned.
    } else {
        "B"
    }
}

// Higher-order function
fun operation(x: Int, y: Int, op: (Int, Int) -> Int): Int {
    return op(x, y)
}
val result = operation(10, 5, { a, b -> a + b } ) // result = 15
// or
val result = operation(10, 5) { a, b -> a + b } // result = 15

Kotlin allows you to have trailing lambdas, which means if the last parameter of a function is a lambda, you can pass it outside the parentheses.

fun operation(x: Int, y: Int, op: (Int, Int) -> Int): Int {
    return op(x, y)
}
val result = operation(10, 5) { a, b -> a + b } // result = 15

This is very useful for creating DSLs (Domain Specific Languages) in Kotlin.

Data Structures (Classes)

Kotlin uses classes and objects for data encapsulation. Data classes provide a concise way to create classes that are primarily used to hold data.

Syntax and Example:

// Class
class Person(var name: String, var age: Int)

// Data class
data class User(val name: String, val age: Int)

// Creating an instance of a class
val person = Person("Alice", 30)
// Accessing properties
println(person.name)

// Creating an instance of a data class
val user = User("Bob", 25)
// Accessing properties
println(user.name)

The difference between a class and a data class is that a data class automatically generates equals(), hashCode(), and toString() methods, and a copy() method for creating a copy of an object with modified fields.

Interfaces

Interfaces in Kotlin can contain abstract methods as well as method implementations. They are defined using the interface keyword.

Syntax and Example:

interface ClickListener {
    fun onClick(event: Event)
}

class Button : ClickListener {
    override fun onClick(event: Event) {
        println("Button clicked")
    }
}

Collection Manipulation

Kotlin provides a rich set of collection classes, including lists, sets, and maps. Collections can be mutable or immutable.

Syntax and Example:

// List
val numbers = listOf(1, 2, 3, 4, 5)
// Iterating through a list
for (number in numbers) {
    println(number)
}

// List functions
val first = numbers.first()
val last = numbers.last()
val evenNumbers = numbers.filter { it % 2 == 0 }
val mappedNumbers = numbers.map { it * 2 }
val sortedNumbers = numbers.sorted()

// Map
val ages = mapOf("Alice" to 25, "Bob" to 30)
// Iterating through a map
for ((name, age) in ages) {
    println("$name is $age years old")
}

// Mutable list
val mutableNumbers = mutableListOf(1, 2, 3, 4, 5)
mutableNumbers.add(6)
mutableNumbers.remove(3)

// Mutable map
val mutableAges = mutableMapOf("Alice" to 25, "Bob" to 30)
mutableAges["Charlie"] = 35
mutableAges.remove("Bob")

Error Handling

Kotlin handles errors using try-catch blocks and supports checked exceptions.

Syntax and Example:

// Try catch

// Throwing an exception
fun divide(a: Int, b: Int): Int {
    if (b == 0) {
        throw ArithmeticException("Division by zero")
    }
    return a / b
}
// Error handling
try {
    val result = divide(10, 0)
} catch (e: ArithmeticException) {
    println("ArithmeticException caught!")
}

// Result
sealed class Result<out T> {
    data class Success<out T>(val value: T) : Result<T>()
    data class Failure(val exception: Throwable) : Result<Nothing>()
}

fun riskyOperation(): Result<String> {
    if (/* something goes wrong */) {
        return Result.Failure(Exception("Uh-oh, something failed!"))
    } else {
        return Result.Success("Success!")
    }
}

fun main() {
    val result = riskyOperation()
    when (result) {
        is Result.Success -> println(result.value)
        is Result.Failure -> println("Error: ${result.exception.message}")
    }
}

// runCatching
fun riskyOperation2(): Int {
    // ... code that might throw an exception
}

fun main() {
    val result = runCatching { riskyOperation2() }
    result.onSuccess { value -> println(value)  }
          .onFailure { error -> println("Error: ${error.message}") }
}

Concurrency

Kotlin concurrency is primarily achieved through coroutines, which are light-weight threads.

Syntax and Example:

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        delay(1000L)
        println("World!")
    }
    println("Hello,")
}

Ecosystem

Installation

Kotlin runs on JVM so we need to install java and kotlin compiler.

SDK Man can be used to manage Java and Kotlin

Install java and kotlin

sdk install java && sdk install kotlin

Hello World

fun main() {
    println("Hello, World!")
}

compile and run

kotlinc Application.kt -include-runtime -d app.jar && \
java -jar app.jar

CLI

  • kotlinc <file-name.kt> -include-runtime -d <file-name.jar>: Compile Kotlin file to a jar file
  • java -jar <file-name.jar>: Run the compiled jar file
  • kotlinc-jvm: Start the Kotlin REPL (Read-Eval-Print Loop) for interactive programming session

Build Tools and Package Management

  • Gradle or Maven: Both are popular build tools and dependency management tools for Kotlin projects, similar to how they are used in Java projects.

I prefer gradle. SDKMAN can be used to install gradle as well.

sdk install gradle

to add a dependency in Gradle:

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-stdlib")
}

to add a dependency in Maven:

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib</artifactId>
    <version>1.5.21</version>
</dependency>
  • Ktor - Framework for building asynchronous servers and clients in connected systems
  • Jetpack Compose - Modern toolkit for building native UI for Android apps
  • Spring Boot - Makes it easy to create stand-alone, production-grade Spring based Applications
  • Exposed - Lightweight SQL library for Kotlin
  • Arrow - Functional programming in Kotlin
  • Kotlinx.coroutines - Library for managing background tasks with simplified code and reducing needs for callbacks
  • MockK - Mocking library for Kotlin

Kotlin’s ecosystem is tightly integrated with Java’s, allowing the use of all existing Java libraries and frameworks, while also providing a more concise and expressive syntax.

Special Features

  • Null Safety: Kotlin’s type system is designed to eliminate NullPointerExceptions from the code.
  • Extension Functions: Allow you to extend a class with new functionality without having to inherit from the class.
  • Coroutines: For asynchronous programming and more efficient handling of operations that can block, such as network calls or long computations.