Kotlin shares many similarities with Scala1, including its concise syntax, strong type system, and support for functional programming paradigm. Both languages are designed to be interoperable with Java, making them suitable for building large-scale applications.
While this post does not cover all aspects of Kotlin and Scala, it provides an overview of their similarities. So, if you know Scala, it is easy to learn Kotlin; or vice versa. This post also briefly touches on the features available only in Kotlin.
Main
Kotlin
fun main(args: Array<String>) {
println("Hello, Kotlin!")
}
Scala
def main(args: Array[String]): Unit = {
println("Hello, Scala!")
}
Variables
Kotlin
val name: String = "Rob" // val for immutable variable
var count = 30 // Type inferred, var for mutable variable
Scala
val name: String = "Rob"
var age = 30 // Type inferred
Conditionals
Kotlin
if (age >= 18) "Adult" else "Minor"
Scala
if age >= 18 then "Adult" else "Minor"
String Interpolation
Kotlin
val name = "Rob"
val age = 30
println("Hello, $name! You are $age years old.") // No s prefix needed for interpolation!
Scala
val name = "Rob"
val age = 30
println(s"Hello, $name! You are $age years old.")
Loops
Kotlin
for (i in 1..5) println(i) // 1 and 5 inclusive
for (i in 1..<5) println(i) // 1 to 4, or 1 until 5
for (i in 1..5 step 2) println(i)
var i = 1
while (i <= 5) {
println(i)
i += 1
}
Scala
for (i <- 1 to 5) println(i)
for (i <- 1 until 5) println(i)
for (i <- 1 to 5 by 2) println(i)
var i = 1
while i <= 5 do
println(i)
i += 1
Functions
Kotlin
fun greet(name: String): Unit {
println("Hello, $name!")
}
fun add(a: Int, b: Int): Int = a + b
fun factorial(n: Int): Int {
tailrec fun helper(n: Int, acc: Int): Int =
if (n <= 1) acc else helper(n - 1, n * acc)
return helper(n, 1)
}
Scala
def greet(name: String): Unit = {
println(s"Hello, $name!")
}
def add(a: Int, b: Int): Int = a + b
def factorial(n: Int): Int =
@tailrec
def helper(n: Int, acc: Int): Int =
if (n <= 1) acc else helper(n - 1, n * acc)
helper(n, 1)
Collections
Kotlin
// Immutable collections
val arr = arrayOf(1, 2, 3, 4, 5)
val list = listOf(1, 2, 3, 4, 5)
val set = setOf(1, 2, 3, 4, 5)
val map = mapOf(1 to "one", 2 to "two", 3 to "three")
// access items by index
arr(1)
list.get(4) // or list[4]
// access element by key in a map
val value: String? = map.get(1)
val value = map[1] // throws error if key not found
// iterating elements
for (e in arr) { // or list or set
println(e)
}
for ((k, v) in map) {
println("$k -> $v")
}
// Mutable Collections
val ma = mutableArrayOf(1, 2, 3, 4, 5)
val ml = mutableListOf(1, 2, 3, 4, 5)
val ms = mutableSetOf(1, 2, 3, 4, 5)
val mm = mutableMapOf(1 to "one", 2 to "two", 3 to "three")
Scala
// Immutable collections
val arr = Array(1, 2, 3, 4, 5)
val list = List(1, 2, 3, 4, 5)
val set = Set(1, 2, 3, 4, 5)
val map = Map(1 -> "one", 2 -> "two", 3 -> "three")
// access items by index
arr(1)
list(1)
// access element by key in a map
val value: Option[String] = map.get(1)
// iterating elements
for (e <- arr) { // or list or set
println(e)
}
for ((k, v) <- map) {
println("$k -> $v")
}
import scala.collection.mutable
val ma = mutable.ArrayBuffer(1, 2, 3, 4, 5)
val ml = mutable.ListBuffer(1, 2, 3, 4, 5)
val ms = mutable.Set(1, 2, 3, 4, 5)
val mm = mutable.Map(1 -> "one", 2 -> "two", 3 -> "three")
Classes
Kotlin
class Person(val name: String, val age: Int) { // Primary constructor
lateinit var address: String
init {
if (!this::address.isInitialized) {
address = "123 Main St"
}
}
constructor(name: String) : this(name, 0) // Secondary constructor with default age
// Auxiliary constructor
constructor(name: String, age: Int, city: String) : this(name, age) {
println("Name = $name, Age = $age, City = $city")
}
}
// elsewhere
val tom = Person("Tom", 23)
Scala
class Person(val name: String, val age: Int) { // Primary constructor
lazy val address = "123 Main St"
def this(name: String) = // Auxiliary constructor
this(name, 0) // Calls the primary constructor with a default age of 0
def this(name: String, age: Int, city: String) = // Auxiliary constructor
this(name, age) // Calls the primary constructor
println(s"Name = $name, Age = $age, City = $city")
}
// elsewhere
val tom = Person("Tom", 23)
In Kotlin, casses are closed for inheritance by default. Use open
modifier on the the class to be able to derive from a class.
Kotlin
open class Vehicle(val make: String) {
open fun drive() {
println("Driving a $make vehicle")
}
}
class Car(make: String) : Vehicle(make) {
override fun drive() {
println("Driving a $make car")
}
}
In Scala, classes are extensible by default. Use the final
modifier on the class to restrict extending it.
Scala
class Vehicle(val make: String) {
def drive(): Unit =
println(s"Driving a $make vehicle")
}
final class Car(make: String) extends Vehicle(make) {
override def drive(): Unit =
println(s"Driving a $make car")
}
Companion Objects
Kotlin
object Singleton { // standalone singleton object
fun greet(name: String) {
println("Hello, $name!")
}
}
class Person(...) {
companion object Person {
fun create(name: String): Person = Person(name, 24, "New York")
}
}
Scala
object Singleton: // standalone singleton object
def greet(name: String): Unit =
println("Hello, $name!")
class Person(...)
object Person:
def apply(name: String): Person = new Person(name, 24, "New York")
Data Classes
Kotlin
data class Person(val name: String, val age: Int)
Scala
case class Person(val name: String, val age: Int)
Sealed Hierarchies and ADTs
Kotlin
sealed interface Shape
data class Circle(val radius: Double) : Shape()
data class Rectangle(val width: Double, val height: Double) : Shape()
fun area(shape: Shape): Double =
when (shape) {
is Shape.Circle -> Math.PI * shape.radius * shape.radius
is Shape.Rectangle -> shape.width * shape.height
}
Scala
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
def area(shape: Shape): Double =
shape match
case Circle(radius) => Math.PI * radius * radius
case Rectangle(width, height) => width * height
Option / Nullable
Kotlin
val maybeName: String? = null // maybeName is nullable
val name: String = maybeName ?: "Unknown"
Scala
val nameOpt = Option(null)
val name = nameOpt.getOrElse("Unknown")
Pattern Matching
Kotlin
// Conditions in cases
val age = 30
val status =
when {
age >= 18 -> "Adult"
age >= 13 -> "Teenager"
else -> "Child"
}
// Case collapsing
fun isWeekend(day: String): Boolean =
when (day) {
"Saturday", "Sunday" -> true
else -> false
}
// Checking for types
fun check(e: Exception) =
when(e) {
is IllegalArgumentException -> "Invalid argument"
is IllegalStateException -> "Illegal state"
else -> "Unknown error"
}
Scala
// Conditions in cases
val age = 30
val status =
age match
case age if age >= 18 => "Adult"
case age if age >= 13 => "Teenager"
case _ => "Child"
}
// Case collapsing
def isWeekend(day: String): Boolean =
day match
case "Saturday" | "Sunday" => true
case _ => false
// Checking for types
def check(e: Exception): String =
e match
case _: IllegalArgumentException => "Invalid argument"
case _: IllegalStateException => "Illegal state"
case _ => "Unknown error"
Extension Methods
Kotlin
operator fun String.titleCase(s: String): String =
s.split(" ").map { it.capitalize() }.joinToString(" ") // it is similar to _ in Scala
Scala
extension (st: String.type) {
def titleCase(s: String): String =
st.split(" ").map(_.capitalize).mkString(" ")
}
etc
Name | Kotlin | Scala |
---|---|---|
Shift operators | shl, shlr | <<, >> |
Bitwise operators | or, and | |, & |
Only in Kotlin
Top level constants
const val PI = 3.14159
def main(args: Array<String>) {
println("Hello, Kotlin!")
}
do-while Loop
var i = 10
do {
println(i)
i -= 1
} while (i >= 1)
Getters and Setters
class Person(val name: String, val age: Int, val city: String) {
var address: String = ""
get() = field
set(value) {
field = value.uppercase()
}
}
// elsewhere
val person = Person("John", 24, "New York")
println(person.address) // ""
person.address = "123 Main St"
println(person.address) // "123 MAIN ST"
-
Version of Scala used in this post is v3.x with braceless syntax. ↩︎