Introduction to Kotlin for Java Developers

Nil Seri
12 min readDec 17, 2021

Kotlin Tutorial Notes for Java Developers

Photo by Nathan Dumlao on Unsplash

TL;DR

I finished “Kotlin for Java Developers” course on Udemy and decided to convert one of my projects written in Java to Kotlin. Here, I will share my notes I took during the course and some extra information I learnt during the code conversion.

Some Highlights:

  • Kotlin Compiler (kotlinc) converts .kt to bytecode as .class.
  • JRE + Kotlin Runtime Library is required at runtime and for distribution.
  • Everything is a class in Kotlin (no primitive types).
  • You can declare top level methods or constants (no need to belong to a class).
  • There is a single kotlin package and you do not have to import it or any other package else then your own implementations.
  • No semicolon!
  • Package names and path in your class’s first line does not need to match (i.e. your class’s physical location may be different).

Variable Declarations, Properties and Backing Fields

val” for immutable (just like Java’s final), “var” for mutable.
There is a detail for val variables.

“Student” class has such definition:

class Student(var name: String, val id: Int) {
}

If there is no “val” or “var” in front, then it is not a property but a parameter:

class Student(val firstName, isBoarding: Boolean = false) {
}

Here are two objects we created from “Student” class:

val student1 = Student("Bob", 12)
val student2 = Student("Laura", 14)

“get” and “set” is created for class fields by default in Kotlin. Actually, when you type “student1.firstName”, “student1.getName()” is called in the background.

You can also do this (getter and setter methods following right after field declaration):

class Student(val firstName, isBoarding: Boolean = false) {
var isBoarding = isBoarding
get() {
println("hello getter!")
return field
}
set(value) {
println("hello setter!")
field = value
}
}

You can’t do this:

student1 = student2

But you can do this (since property declaration is “var”):

student1.name = "Mark"

Access Modifiers

  • public, private, protected, internal (in a module)
  • Kotlin classes are public and final by default
  • Class name can be different from its file name (unlike Java)
  • Class can be private but everything is visible if they are in the same file even if you declare as private.
  • private -> compiled to -> package private
  • internal -> compiled to -> public
Kotlin in Action — Manning Publications

String Templates

You are able to use your variable value in a string with “$” sign.
To really use $ right next to a text in a string, you have to escape it “\$change”.
To really use $ right next to your variable’s value in a string “$$change”.
If there is no text right next to $ then you do not need to do anything.
You can use expressions just like “${x+y}”.

Raw Strings (Triple Quoted Strings)

There is no need to escape using “\” since we can now use triple quotes:

"""C:\"""

You can also write line by line (trimMargin’s default pipe is | but you can also provide another char):

"""a
b
c""".trimMargin()

Function Basics

Parameter Locations:

You can mix up parameters in Kotlin as long as you declare parameter name in front of its value.

Variable Arguments (VarArgs):

In Java, variable arguments are declared with a spread operator (Student… student) whereas “vararg” keyword is used in Kotlin:

fun printStudentNames(vararg students: Student) {
for (student in students) {
println(student.firstName)
}
}

You can pass an array to this method using Kotlin’s spread operator “*”:

printStudentNames(*students)

Single Expression Functions

fun myOperation(param1: Int, param2: Int, resultLabel: String) : String = "$resultLabel: ${param1 + param2 * 5}"

Even if this is a non void method, Kotlin knows the return type (since the function returns a single expression) so you can omit return type:

fun myOperation(param1: Int, param2: Int, resultLabel: String) = "$resultLabel: ${param1 + param2 * 5}"

Extension Functions

Static methods where you can declare outside the class of the caller variable (Decorator design pattern):

fun String.replaceLetterRWithT(): String =  this.replace('r', 't')

Notice how we gave the caller variable’s class as a prefix before function name. You can also pass function parameters.

Now, you are able to call it like:

"my car".replaceLetterRWithT()

Inline Functions

To be able to create a method that can be called as a lambda function, we use inline functions. They help to save memory at runtime since they are copied to the calling place by the compiler instead of creating an object.

Example taken from here:

inline fun higherOrderFunction(functionName: (name: String)->Unit, name: String){
println("In higher order function")
println("Calling received function...")
functionName(name)
}

fun main() {
higherOrderFunction({ name: String ->
println("Inside lambda function")
println("Say hello to $name")
}, "Ninja")
}

Companion Object

You can use companion objects to declare functions like static utility methods (you need them because Kotlin does not have “static”). You can use this for “Factory Pattern”.

class UtilClass {
companion object {
fun maskString(str: String): String{
...
}
}
}

Singleton

Declaring a singleton class in Kotlin is done by just using “object” keyword.

object Teacher {
...
}

Inheritance

Since everything is public and final in Kotlin by default, if you want to extend a class, you should declare it as “open”. If you want to override a function, you should declare it as “open”, too. If you do not want a function to be overridden, you should declare it as “final”.

open class Animal() {
open fun run()
}
class Rabbit(): Animal() {
override fun run()
}

You do not need to declare “open” for “abstract” classes and functions.

Checked / Unchecked Exception

Kotlin only throws unchecked exceptions (during runtime). Unlike Java, there are no checked exceptions so; no need for “throws” keyword at method declaration.

Ternary Operator ( condition ? then : else )

There is no ternary operator in Kotlin. Instead, there is an “if expression”.

val max = if (a > b) a else b

You can also both perform an operation and return a value:

val color = if (condition) {
println("hello1")
1
}
else {
println("hello2")
2
}
println(num)

When

“when” in Kotlin is just like “switch” in Java (and now much more alike if you checked its latest usage in Java 17). You can also check with ranges (in 100..199 -> println(“between”)) or conditions (isOdd(num) -> println(“I am odd”)) or smart casting (is String -> println(“I am a string”)).

val color = "blue"
when (color) {
"pink" -> println("flamingo")
"blue" -> println("car")
"yellow" -> println("sunflower")
else -> println("none of them")
}

For Loop

Our good old “for” loop format cannot be used:

for (int i=0; i<n; i++) {
...
}

Instead, you can declare ranges or:

for (i in 1..5) {
...
}
for (i in 1..20 step 5) {
...
}
for (i in 20 downTo 10) {
...
}
for (i in 20 downTo 10 step 5) {
...
}
for (i in 1 until 10) {
// excluding 10
}

ForEach

colors.forEach { println(it) }
colors.forEachIndexed { index, value -> println("$value-$index") }

Lambda Expressions

run { println("hi from lambda!" }
students.minBy { it.grade }
students.minBy(Student::grade)

Receivers

In the examples below, we omitted declaring a variable of type StringBuilder:

with function:

with(StringBuilder()) {
for (i in 1..100) {
append(i)
append("-")
}
}.toString()

apply function:

StringBuilder().apply() {
for (i in 1..100) {
append(i)
append("-")
}
}.toString()

also function:

“also” function is like “apply” function. The difference is that you use it inside the lambda to refer to the receiver object:

var m = 1 
m = m.also { it + 100 }.also { it + 200 }
println(m)
//prints 1
val student: Student = getStudent().also {
print(it.name)
print(it.grade)
}

Labels

To break out of or continue a whole for loop from an inner for loop (myLoop is the name we give, you can label as whatever you like):

myLoop@ for (i in 1..100) {
...
for (j in 1..100) {
for (k in 1..100) {
// break @myLoop
// continue@myLoop
}
}
}

When you return in a lambda, you return from the whole function. To only return from the lambda, you can use labels.
To return from a lambda (myLambda is the name we give, you can label as whatever you like):

students.forEach myLambda@ {
if (it.name == name) {
println("found you!")
return @myLambda
}
}

“static” and “new” Keywords

You cannot use “static” and “new” keywords. “val” can be used in variable declarations for the same purpose as “static”. You do not need to add “new” at the start of classes at object creations.

Referential and Structural Equity

Different from Java, “==” equals operator checks for structural equity (equals() in Java).
For referential equity check, you should use “===” in Kotlin (“!==” for not check)

Null References

To have a nullable variable, you should add “?”:

val str: string? = null

To use the variable, you should either perform if not null check (if (str != null)) or use “safe call” null protector operator.

Null Related Checks

?: Elvis Operator for Null Check — Assign a default value:

var direction = direction[coordinate] ?: Direction(0, 0)

? Null Protector:

This one is just like “Optional chaining (?.)” in Javascript. For a safe method call:

directions[coordinate]?.moveRight()

!! Not Null For Sure:

To tell the compiler, we are a 100% sure the value is not null (or we do it on purpose to have the exception thrown):

var direction = direction[coordinate]!!

“let” Function:

If variable is not null, let this function call go ahead:

str?.let { println(it) }

This is a lambda expression and “it” is used as “implicit name of a single parameter”.

“takeIf” and “takeUnless” Functions:

Instead of this:

return if( myVar.isCondition() ) doSthWith(myVar) else null

We can code as:

return doSthWith(myVar).takeIf { it.isCondition() }

“takeUnless” is just the opposite of “takeIf”.

Equity Check:

This will not throw an exception in Kotlin:

var str1: string? = null
var str2 = "hello"
println(str1 == str2)

Smart Casting

Instead of “instance of”, “is” is used in Kotlin (and “!is” for not check).

if (teacher is Employee)

After type check, you can cast just like:

var newEmployee = teacher

For safer nullable casting (when you do not perform type check before & in case it is not possible to cast):

val x: String? = y as? String

Since primitive types cannot be used in Kotlin, you cannot do this:

int myInt = 10;
long myLong = myInt;

Try/Catch Expressions

return try {
Integer.parseInt(str)
} catch (e: NumberFormatException) {
0
} finally {
println("fin")
}

Main Method

Java’s main method :

static void main(String[] args) {   
System.out.println("Hello World");
}

It has converted into :

fun main(args : Array<String>) {
println("Hello World")
}

Void vs. Unit

There is no such thing like void in Kotlin. A void method in Kotlin returns “Unit” (a singleton Unit instance).

Arrays

You can declare arrays with “arrayOf”. Inline array definition is possible with:

val myArr = arrayOf("a", "b", "c")
val myLongArr = arrayOf<Long>(1, 2, 3, 4)
val allZeroArr = Array(20){0}
val evenNumbersArr = Array(16){i -> i*2}
val mixedArr = arrayOf("hello", 12, BigDecimal(10.5), 'a')
val nullArr = arrayOfNulls<Int?>(5)

Lists

Lists are covariant, meaning they are of a generic class where subtyping is preserved.

Square Brackets: No need to use .get() for array lists, you can get the value at index with [i] just like arrays.

There are lists and mutable lists.

“listOf” returns an immutable list.

val emptyList = emptyList<String>()val notNullList = listofNotNull("hello", "world", null, "again") 
// if you print this array, you will not see "null" value

For mutable lists, you have to use Java’s array list :

arrayListOf(1, 2, 3)
mutableListOf<Int>(1, 2, 3)
listOf("1", "2", "3").toMutableList()

To convert a mutable list to immutable one (you remember Kotlin’s * operator):

listOf(*arrayOf("a", "b", "c"))
intArrayOf(1, 2, 3, 4, 5).toList()

Maps

mapOf(1 to Student("Alan")
2 to Student("Kim")
3 to Student("Michael"))
mutableMapOf<String, Student> ("Hi" to Student("Diana"))hashMapOf("hi" to "hello", "bye" to "goodbye").put("abbr." to "abbreviation")

Sets

setOf(10, 20, 30).plus(20) // no duplicates!
setOf(10, 20, 30).minus(40) // no exceptions
setOf(10, 20, 30).average()
setOf(10, 20, 30).drop(2) // removes first 3 elements
mutableSetOf(1, 2, 3).plus(10) // does nothing

Collections & Collection Functions

listOf("1", "2", "3").last() // get last value
listOf("1", "2", "3").asReversed()
listOf("1", "2", "3").getOrNull(5) // get from index 5
listOf("1", "2", "3").max() // get max value
val A = listOf("a", "b", "c")
val B = listOf(1, 2, 3, 4)
val resultPairs = A.zip(B)
// returns value pairs with the length of smallest array
A + B // returns ["a", "b", "c", 1, 2, 3, 4]
A.union(B) // no duplicates; duplicates are removed
A.distinct()
B.filter { it % 2 != 0 }
studentMap.filter { it.value.grade > 70 }

arrayOf(1, 2, 3).map { it + 10 }
studentMap.map { it.value.grade }
studentMap.all { it.value.grade > 70 }
// checks if all matches condition
studentMap.any { it.value.grade < 20 }
// checks if any matches condition
studentMap.count { it.value.grade > 50 }
// counts how many matches condition
studentMap.values.find { it.grade > 50 }
// finds first match and returns it
studentMap.values.groupBy { it.grade > 50 } // returns mapstudentMap.values.sortedBy { it.grade }studentMap.toSortedMap() // order by key

Sequences

Sequences are like streams for big data collections (evaluates one by one, returns sequence).

studentMap.asSequence().filter { it.value.grade > 50 }val sequence1 =listOf("blue", "red", "green", "grey").asSequence()
.filter { println("filtering $it"); it[0] == 'g' }
.map { println("mapping $it"); it.toUpperCase());

As it returns a sequence, a terminal function is needed in the chain or it does not run.

sequence1.toList()

Sequence finds the first and stops. If you remove asSequence(), it processes all items:

sequence1.find { it.endsWith('e') }

Destructuring

val pair = Pair(5, "five")
val(firstValue, secondValue) = pair
for ((key, value) in mutableMap) {
...
}

To use destructuring in your custom classes, you have to use component functions:

class Student(val name: String, val grade: Int) {
operator fun component1() = name
operator fun component2() = grade
}
val(name, grade) = student

Enums

enum class Color { BLACK, BLUE, PINK, RED }

Data Class

Data class in Kotlin is very similar to Record class in Java (Record classes first appeared in Java 14). Record classes are a bit more strict. Here is a nice comparison post for more details:

In Data classes, two string functions provided — equals/hashCode. There is an extra “copy” function to create a copy of data object:

student3 = student2.copy(firstName="Newton")

Data classes support destructuring. They cannot be abstract, sealed, inner.

Comma Usage:

The only usage will be Enum with companion object and in collection functions as you may have noticed.

in Collection Functions:

listOf("blue", "red", "green", "grey").asSequence()
.filter { println("filtering $it"); it[0] == 'g' }
.map { println("mapping $it");

in Enum:

enum class OrientationType(val key: Char) {

NORTH('N'),
SOUTH('S'),
EAST('E'),
WEST('W');

companion object {
fun getOrientationByKey(key: Char): OrientationType? = values().first { x -> x.key == key }
}
}

Implementation

Kotlin REPL (Read Eval and Print Loops)

From the IntelliJ IDEA’s official docs (you can find here):

Kotlin REPL allows you to execute code on the fly without having to create files or configure a runtime environment for it. Also, it accepts simplified syntax so that it is possible to execute code with less ceremony.

From the main menu, select Tools | Kotlin | Kotlin REPL.

Java Compliance and Interoperability

Arrays:

To call a method which takes in a primitive type parameter in Java from Kotlin, you should use “IntArray” instead of “arrayOf”. Another way is to call “.toIntArray()” method on your array variable (“toTypedArray()” is also possible).

Nullability:

You should add “@NotNull” in setter method in Java.

If you put “@Nullable” for a String value, Kotlin shows it as “String!”.

Kotlin Annotations:

If you add “@file:JvmName(“StaticStudent”)” at the top in your Kotlin class file, you can call top level functions as “StaticStudent.topLevelFunc()” (StaticStudent name is the one I chose, you can set a different value). If you don’t add it, Kotlin generates a class with the name “StudentKt” and you can call your function with “StudentKt.topLevelFunc()”.

To be able to use “student.grade” instead of “student.getGrade()” in Java, you should add “@JvmField” in front of the property name in Kotlin.

For companion objects, you should add “@JvmStatic” in front of “fun” keyword in you companion object to be able to call as a static method from Java.
For Singleton objects, Kotlin generates “INSTANCE” so you end up calling the method like “SingletonObj.INSTANCE.doSth()”. If you add “@JvmStatic”, you won’t be needing to add “INSTANCE” in your call.

For exceptions, you should add “@Throws(IOException::Class)” at the top of your function which throws an exception.

For default parameters of your class, you should add “@JvmOverloads” at the top of your class declaration function or Java sees all the properties as required even if they have a default value.

I did another post for implementation (Java vs. Kotlin including unit tests) since this took a bit too long 😊. If you would like to take a look:

Happy Coding!

--

--

Nil Seri

I would love to change the world, but they won’t give me the source code | coding 👩🏻‍💻 | coffee ☕️ | jazz 🎷 | anime 🐲 | books 📚 | drawing 🎨