This is part 1 of the Kotlin series. I am assuming you already know Java and I will be mostly talking about the new or differentiating features. I plan to share a lot of examples and code that will help you to understand the underlying concepts.
In this post I will be covering the concepts of val, var and const. After that we would look at the concept of Ranges and the mother of all types Any. Then I would touch upon a brief on types of collections in Kotlin. After that I would discuss about the new Kotlin when which is a replacement of case in Java but is more advanced and the new uses of for statement. Then I talk about the return and jump using labels. In the end I will cover in brief the concept of functions in Kotlin.
Defining Variables :- val vs var vs constant val
A val is a immutable variable which should always be initialised when defined and its value cannot be changed later in program. It is like a final variable in Java.
A var is a mutable variable in Kotlin which should be initialised when defined ( unless lateinit or by lazy -> we will discuss this later). The value of a var can be changed during the execution of program anytime.
Note: There is no performance impact using val or var. It’s just that using val tells the person reading code that it wont be changed in program in future and hence increases readability.
const val :
While discussing about var and val it also makes sense to talk about const val
const val is immutable like val but the difference between const val and val is that value for constant val needs to be assigned at compile time whereas val can take values at compile or runtime as well . Also, a const vla can only take primitive type or String as value. Also, const val can be declared only at the top level of a file or inside an object declaration (more about objects later)
Eg
val myVal = Address() => The value of function is returned at runtime
const val myConst = 7 => The value is assigned compile time
var myVar = 0 => the var needs to be assigned a value at declaration (there are some exceptions to this though)
Note: Unlike Java, Kotlin statements donot end with semicolon
Range in Kotlin
Kotlin has an inbuilt concept of range. A range defines a closed interval having its two endpoint values (start and end) which are both included in the range.
Integral type ranges have an extra feature: they can be iterated over. = Such ranges are generally used for iteration in the for loops
Eg,
if (i in 1..4) { // equivalent of 1 <= i && i <= 4
print(i)
}
for (i in 1..4) print(i)
Note: in or !in are used to check if an element is in or is not in the given range, respectively
To iterate numbers in reverse order, we use the down to function instead of ...
for (i in 4 downTo 1) print(i)
It is also possible to iterate over numbers with an arbitrary step (not necessarily 1). This is done via the step function.
for (i in 8 downTo 1 step 2) print(i)
To iterate a number range which does not include its end element, use the until function
for (i in 1 until 10) { // i in [1, 10), 10 is excluded
print(i)
}
Concept of Any
Just like Java, in Kotlin also there is a class which is superclass of all the classes in Kotlin. It is called Any. Any is a super type of all non null types, including primitive types, unlike Java
Any can’t hold the null value, if you need null to be part of your variable you can use the type Any?
Note: to String, equals and hashCode are inherited form Any, whereas wait and notify are part of Object (more on Objects later)
Collections in Kotlin
Arrays are of constant length so one uses lists in Kotlin.
Mutable and Immutable Collections
Kotlin, like java defines lists, maps and sets. These can be mutable or immutable.
Immutable Collections
Immutable collections can be declared as follows :-
val strings = listOf ("Anne", "Karen", "Peter") // List<String>
val map = mapOf ("a" to 1, "b" to 2, "c" to 3) // Map<String, Int>
val set = setof ("a", "b", "c") // Set<String>
You can neither change their size nor replace their elements.
Mutable Collections
Mutable collections can be defined as :-
val strings = mutableListOf("Anne", "Karen", "Peter")
val map = mutableMapOf("a" to 1, "b" to 2, "c" to 3)
val set = mutableSetOf("a", "b", "c")
Their size and elements both can change
if you want an empty collection, you need to either declare the resulting collection type explicitly, or supply the element type(s) to the function that constructs the collection:
val noInts: List = listOf()
val noStrings = listOf("a","b","c")
val emptyMap = mapOf()
Conditionals
when
The when expression replaces the switch statement in Java. One thing to note is you dont need to explicitly specify break to distinguish the two adjacent statements. Also, arrow (->) is used instead of colon (:).
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 satisfied branch becomes the value of the overall expression.
val result = when (operator) {
"+" -> a + b
"-" -> a - b
"*" -> a * b
"/" -> a / b
else -> "$operator operator is invalid operator."
}
If when is used as a statement, the values of individual branches are ignored. (Just like with if, each branch can be a block, and its value is the value of the last expression in the block.)
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else ->
{ // Note the block
print("x is neither 1 nor 2")
}
}
The else branch is the equivalent of default in case and 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 (as, for example, with enum class entries and sealed class subtypes).
Note: when does not use break statements unlike switch
If many cases should be handled in the same way, the branch conditions may be combined with a comma:
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
Note: In when, if there are multiple conditions specifying a criteria the preferred way is to use them together with or ( || ) instead of comma (, )
when (x) {
0 || 1 || 2 || 3 -> print("x == 0 or x == 1 or x == 2 0r x == 3")
else -> print("otherwise")
We 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")
}
We 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 possibility is to check 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.
when {
x.isOdd() -> print("x is odd")
y.isEven() -> print("y is even")
else -> print("x+y is even.")
}
In the below when expression else is not required because the cases are exhaustive
val check = true
val result = when(check) {
true -> println("it's true")
false -> println("it's false")
}
Note: The else statement is always required in when conditional block except for the case when all possible values are covered by the cases as above or when using Sealed class.
for
The for statement is similar to the one in Java in almost all aspects. Where it differs is :-
To iterate over a range of numbers, using a range expression
for (i in 1..3) {
println(i)
}
for (i in 6 downTo 0 step 2) {
println(i)
}
Also, iterating through the indices using withIndex function
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
Returns and Jumps
Like java, Kotlin has three structural jump expressions:
- return. By default returns from the nearest enclosing function or anonymous function
- break. Terminates the nearest enclosing loop.
- continue. Proceeds to the next step of the nearest enclosing loop.
Break and continue using labels
Any expression in Kotlin may be marked with a label. Labels have the form of an identifier followed by the @ sign, for example: abc@, fooBar@ are valid labels
we can qualify a break or continue with a label:
loop@ for (i in 1..100) {
for (j in 1..100) {
if (...) break@loop
}
}
Here loop is a label. breaking at loop will case the iteration to exit from the outer loop (and thus also exiting the inner loop)
Functions
Functions are declared with the keyword “fun”
The name of the parameter must come first followed by a colon followed by space(s) followed by the type.
fun happyBirthday(name: String, age: Int): String {
return "Happy ${age}th birthday, $name!"
}
Function parameters can have default values, which are used when you skip the corresponding argument. This reduces a number of overloads compared to other languages:
fun read(
b: Array,
off: Int = 0,
len: Int = b.size,
) { /…/ }
If a default parameter precedes a parameter with no default value, the default value can only be used by calling the function with named arguments
fun foo(
bar: Int = 0,
baz: Int,
) { /…/ }
foo(baz = 1) // The default value bar = 0 is used
You are allowed to make a oneliner function, where the body simply is the expression whose result is to be returned. In that case, the return type is inferred, and an equals sign is used to indicate that it’s a oneliner:
fun square(number: Int) = number * number
If the last argument after default parameters is a lambda you can pass it either as a named argument or outside the parenthesis
fun foo(
bar: Int = 0,
baz: Int = 1,
qux: () -> Unit,
) { /…/ }
foo(qux = { println("hello") }) // Uses both default values bar = 0 and baz = 1
foo { println("hello") } // Uses both default values bar = 0 and baz = 1
Leave a Reply