Unit
Unit
in Kotlin corresponds to the void in Java. Like void, Unit
is the return type of any function that does not return any meaningful value, and it is optional to mention the Unit
as the return type. But unlike void, Unit
is a real class (Singleton) with only one instance.Aug 25, 2017
In Kotlin the execution of the instructions begin with the main
method.
Similar to Java Kotlin uses the keyword print
and println
to print is arguments to the output.
Functions
Calling member functions uses the dot notation:
Stream().read() // create instance of class Stream and call read()
The function below has two parameters of type Int
and has a return type of Int
as well.
A function body can be an expression. Its return type is inferred. Like so:
You can also make functions that do not return any value
- A simple function that takes a parameter of type
String
and returnsUnit
(i.e., no return value). - A function that takes a second optional parameter with default value
Info
. The return type is omitted, meaning that it’s actuallyUnit
. - A function that returns an integer.
- A single-expression function that returns an integer (inferred).
- Calls the first function with the argument
Hello
. - Calls the function with two parameters, passing values for both of them.
- Calls the same function omitting the second one. The default value
Info
is used. - Calls the same function using named arguments and changing the order of the arguments.
9)Prints the result of the
sum
function call. - Prints the result of the
multiply
function call.
Infix Functions
What is infix function?
A function marked with the infix
keyword can also be called using the infix notation (omitting the dot and the parentheses for the call). Infix functions must meet the following requirements.
- Member functions and extensions with a single parameter can be turned into infix functions.
- They must have single parameter
- The parameter must not accept variable number of arguments and must have no default value
- Defines an infix extension function on
Int
. - Calls the
infix
function. - Creates a
Pair
by calling the infix functionto
from the standard library. - Here’s your own implementation of
to
creatively calledonto
. - Infix notation also works on members functions (methods).
- The containing class becomes the first parameter.
Operator Functions
Certain functions can be “upgraded” to operators, allowing their calls with the corresponding operator symbol.
- This takes the infix function from above one step further using the
operator
modifier. - The operator symbol for
times()
is*
so that you can call the function using2 * "Bye"
. - An operator function allows easy range access on strings.
- The
get()
operator enables bracket-access syntax.
Functions with vararg Parameters
Vararg
s allow you to pass any number of arguments by separating them with commas.
- The vararg modifier turns a parameter into a vararg.
- This allows calling printAll with any number of string arguments.
- Thanks to named parameters, you can even add another parameter of the same type after the vararg. This wouldn’t be allowed in Java because there’s no way to pass a value.
- Using named parameters, you can set a value to prefix separately from the vararg.
- At runtime, a vararg is just an array. To pass it along into a vararg parameter, 6) use the special spread operator * that lets you pass in *entries (a vararg of String) instead of entries (an Array
).
Variables
-
Declares a mutable variable and initializes it.
-
Declares an immutable variable and initializes it.
-
Declares an immutable variable and initializes it without specifying the type. The compiler infers the type Int.
Read-only local variables are defined using the keyword val
. They can be assigned a value only once.
Which is like Java’s final
keyword.
Variables that can be reassigned use the var
keyword.
You can declare variables globaly as well.
Creating Classes and Instances
The keyword class
is used to define a class like Java.
class House
The properties of a class can be listed within it’s declaration or body.
The default constructor with parameters listed in the class declaration is available automatically.
Inheritance between classes is declared by a colon (:
).
Classes are final by default; to make a class inheritable, mark it as open
.
- Declares a class named Customer without any properties or user-defined constructors. A non-parameterized default constructor is created by Kotlin automatically.
- Declares a class with two properties: immutable id and mutable email, and a constructor with two parameters id and email.
- Creates an instance of the class Customer via the default constructor. Note that there is no new keyword in Kotlin.
- Creates an instance of the class Contact using the constructor with two arguments.
- Accesses the property id.
- Updates the value of the property email.
String Templates
Conditional Expressions
If statement
In Kotlin, if
can also be used as an expression.
Branches of if branches can be blocks. In this case, the last expression is the value of a block:
If you’re using if
as an expression, for example, for returning its value or assigning it to a variable, the else branch is mandatory.
When expression
when defines a conditional expression with multiple branches. It is similar to the switch statement in C-like languages. Its simple form looks like this.
when
matches its argument against all branches sequentially until some branch condition is satisfied.
when
can be used either as an expression or as a statement. If it is used as an expression, the value of the first matching branch becomes the value of the overall expression. If it is used as a statement, the values of individual branches are ignored.
The else
branch is evaluated if none of the other branch conditions are satisfied.
If when
is used as an expression, the else
branch is mandatory, unless the compiler can prove that all possible cases are covered with branch conditions, for example, with enum
class entries and sealed class subtypes).
The way to define a common behavior for multiple cases is by using a comma ,
that allows you to combine the cases in a single line.
You can use arbitrary expressions (not only constants) as branch conditions
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
You can also check a value for being in or !in a range or a collection:
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
Another option is checking that a value is or !is of a particular type.
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
when
can also be used as a replacement for an if
-else
if
chain. If no argument is supplied, the branch conditions are simply boolean expressions, and a branch is executed when its condition is true:
when {
x.isOdd() -> print("x is odd")
y.isEven() -> print("y is even")
else -> print("x+y is odd")
}
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
For Loop
The syntax for the For Loop
for(item in colection) print(item)
The body of a for
can be a block
for (item: Int in ints){
//block of code
}
As mentioned before, for iterates through anything that provides an iterator.
This means that it:
has a member or an extension function iterator()
that returns Iterator<>
:
has a member or an extension function next()
has a member or an extension function hasNext()
that returns Boolean
.
All of these three functions need to be marked as operator
.
To iterate over a range of numbers, use a range expression:
for (i in 1..3) {
println(i)
}
for (i in 6 downTo 0 step 2) {
println(i)
}
val items = listOf("apple", "banana", "kiwifruit")
for (item in items) {
println(item)
}
//or
val items = listOf("apple", "banana", "kiwifruit")
for (index in items.indices) {
println("item at $index is ${items[index]}")
}
A for
loop over a range or an array is compiled to an index-based loop that does not create an iterator object.
If you want to iterate through an array or a list with an index, you can do it this way:
for (i in array.indices) {
println(array[i])
}
Alternatively, you can use the withIndex
library function:
fun main() {
val array = arrayOf("a", "b", "c")
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
}
How to iterate through a 2D array in Kotlin
for (row in Model.playerGrid) {
for(column in row){
print("[${column.player}]")
}
println()
}
output:
[ ][ ][ ]
[ ][ ][ ]
[ ][ ][ ]
While Loop and do Do While
while and do-while loops execute their body continuously while their condition is satisfied. The difference between them is the condition checking time:
while checks the condition and, if it’s satisfied, executes the body and then returns to the condition check.
do-while executes the body and then checks the condition. If it’s satisfied, the loop repeats. So, the body of do-while executes at least once regardless of the condition.
val items = listOf("apple", "banana", "kiwifruit")
var index = 0
while (index < items.size) {
println("item at $index is ${items[index]}")
index++
}
while (x > 0) {
x--
}
do {
val y = retrieveData()
} while (y != null) // y is visible here!
Iterators
Made for traversing a collection of elements.
iterators – objects that provide access to the elements sequentially without exposing the underlying structure of the collection.
You can define your own iterators in your classes by implementing the iterator
operator in them.
Once the iterator passes through the last element, it can no longer be used for retrieving elements; neither can it be reset to any previous position. To iterate through the collection again, create a new iterator.
val numbers = listOf("one", "two", "three", "four")
val numbersIterator = numbers.iterator()
while (numbersIterator.hasNext()) {
println(numbersIterator.next())
}
Ranges
Check if a number is within a range using the in
operator.
val x = 10
val y = 9
if (x in 1..y+1) {
println("fits in range")
}
Check if a number is out of range.
you can do NOT in as a means to check if something is out of range:
!in
val list = listOf("a", "b", "c")
if (-1 !in 0..list.lastIndex) {
println("-1 is out of range")
}
if (list.size !in list.indices) {
println("list size is out of valid list indices range, too")
}
Iterate over a range.
for (x in 1..5) {
print(x)
}
Or over a progression.
for (x in 1..10 step 2) {
print(x)
}
println()
for (x in 9 downTo 0 step 3) {
print(x)
}
Collections
How to iterate over a collection
val items = listOf("apple", "banana", "kiwifruit")
for(item in items){
println(item)
}
How to check if a collection contains an object using the in
operator
when {
"orange" in items -> println("juicy")
"apple" in items -> println("apple is also juicy")
}
Using lambda expressions to filter and map collections:
fun main() {
val fruits = listOf("banana", "avocado", "apple", "kiwifruit")
fruits
.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.uppercase() }
.forEach { println(it) }
}
Nullable values and null checks
A reference must be explicitly marked as nullable when null
value is possible. Nullable type names have ?
at the end.
Return null
if str
does not hold an integer:
fun parseInt(Str: String): Int?{
}
Use a function returning nullable value:
fun printProduct(arg1: String, arg2: String) {
val x = parseInt(arg1)
val y = parseInt(arg2)
// Using `x * y` yields error because they may hold nulls.
if (x != null && y != null) {
// x and y are automatically cast to non-nullable after null check
println(x * y)
}
else {
println("'$arg1' or '$arg2' is not a number")
}
}
or
if (x == null) {
println("Wrong number format in arg1: '$arg1'")
return
}
if (y == null) {
println("Wrong number format in arg2: '$arg2'")
return
}
// x and y are automatically cast to non-nullable after null check
println(x * y)
Type checks and automatic casts
The is
operator checks if an expression is an instance of a type. If an immutable local variable or property is checked for a specific type, there’s no need to cast it explicitly:
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// `obj` is automatically cast to `String` in this branch
return obj.length
}
// `obj` is still of type `Any` outside of the type-checked branch
return null
}
or
fun getStringLength(obj: Any): Int? {
if (obj !is String) return null
// `obj` is automatically cast to `String` in this branch
return obj.length
}
or
fun getStringLength(obj: Any): Int? {
// `obj` is automatically cast to `String` on the right-hand side of `&&`
if (obj is String && obj.length > 0) {
return obj.length
}
return null
}
Basic types in Kotlin
Arrays
Different ways to define and initialize an array in Kotlin
var x: IntArray = intArrayOf(1, 2, 3) // [1, 2, 3]
var y = IntArray(5) //has a size of 5, but all elements are 0
//[42, 42, 42, 42, 42]
val arr = IntArray(5) { 42 } // has a size of 5 with all elements of value 42
// e.g. initialise the values in the array using a lambda
// Array of int of size 5 with values [0, 1, 2, 3, 4] (values initialised to their index value)
var arr2 = IntArray(5) { it * 2 }
https://kotlinlang.org/docs/basic-types.html#numbers
Null in Kotlin
Variables cannot be assigned null
unless they are declared with the key symbol ?
at the end of the type.
Cannot be null
var cannotBeNull: Int = null
Will produce a compiler error
var canBeNull: Int? = 2
We are declaring a nullable Int
canBeNull = null
Sets null
value to the nullable variable
If a function has a parameter that is not nullable and you pass it a nullable variable it will produce a compiler error.
Generics in Kotlin
Generic classes
class MutableStack<E>(vararg items: E) { // 1
private val elements = items.toMutableList()
fun push(element: E) = elements.add(element) // 2
fun peek(): E = elements.last() // 3
fun pop(): E = elements.removeAt(elements.size - 1)
fun isEmpty() = elements.isEmpty()
fun size() = elements.size
override fun toString() = "MutableStack(${elements.joinToString()})"
}
- Defines a generic class
MutableStack<E>
whereE is called the generic type parameter. At use-site, it is assigned to a specific type such as
Intby declaring a
MutableStack`. - Inside the generic class,
E
can be used as a parameter like any other type - You can also use
E
as a return type
Generic Functions
You can also generify functions if their logic is independent of a specific type. For instance, you can write a utility function to create mutable stacks:
fun <E> mutableStackOf(vararg elements: E) = MutableStack(*elements)
fun main() {
val stack = mutableStackOf(0.62, 3.14, 2.7)
println(stack)
}
Note that the compiler can infer the generic type from the parameters of mutableStackOf
so that you don’t have to write mutableStackOf<Double>(...)
.
Inheritance in Kotlin
open class Dog { // 1 open fun sayHello() { // 2 println(“wow wow!“) } }
class Yorkshire : Dog() { // 3 override fun sayHello() { // 4 println(“wif wif!“) } }
fun main() { val dog: Dog = Yorkshire() dog.sayHello() }
- Kotlin classes are final by default. If you want to allow the class inheritance, mark the class with the
open
modifier. - Kotlin methods are also final by default. As with the classes, the
open
modifier allows overriding them. - A class inherits a superclass when you specify the
: SuperclassName()
after its name. The empty parentheses()
indicate an invocation of the superclass default constructor. - Overriding methods or attributes requires the
override
modifier.
Inheritance with Parameterized Constructor
open class Tiger(val origin: String) {
fun sayHello() {
println("A tiger from $origin says: grrhhh!")
}
}
class SiberianTiger : Tiger("Siberia") // 1
fun main() {
val tiger: Tiger = SiberianTiger()
tiger.sayHello()
}
- If you want to use a parameterized constructor of the superclass when creating a subclass, provide the constructor arguments in the subclass declaration.
Passing Constructor Arguments to Superclass
open class Lion(val name: String, val origin: String) {
fun sayHello() {
println("$name, the lion from $origin says: graoh!")
}
}
class Asiatic(name: String) : Lion(name = name, origin = "India") // 1
fun main() {
val lion: Lion = Asiatic("Rufo") // 2
lion.sayHello()
}
name
in theAsiatic
declaration is neither a var nor val: it’s a constructor argument, whose value is passed to thename
property of the superclassLion
.- Creates an
Asiatic
instance with the nameRufo
. The call invokes theLion
constructor with argumentsRufo
andIndia
.