Master Kotlin Today: Your All-Inclusive Guide to Modern Development
Your Pathway from Beginner to Expert: Understanding Kotlin Concepts Clearly
Table of contents
Lecture 1 - Introduction
Introduction to Programming -
Like any other programming language, let us first have a look at the common statements that we write in a Kotlin Program.
fun main() {
print("Enter your name: ")
var name : String = readln()
print("Hello, $name")
}
Here as we explore the different terms. We see:
fun
is used to declare a functionmain()
function is where our program starts its execution fromprint()
is a special type of function provided by Kotlin and a string literal is passed as an argumentvar
represents a variable that is used to store valueswe have the datatype that represent the type of value, assignment operators, inbuilt function to take the input
and lastly, we have a string template.
So these concepts such as - functions, variable, data type, operators, keywords, classes is the vocabulary of our programming language. We will use these concepts to write functional programs with some meaningful operation. We will study these topics in detail, in the later course of the lecture.
With advancement in everything, we also have advancement in the type and the format of programming :
Old Fashioned - Procedural
programs are written as a sequence of instruction, one after another.
Functional
to reduce repetition of lines, we started grouping related instructions into functions for readability and reusability.
Object Oriented Programming
this involves combining data and behaviour(methods/functions) into a single entity called class.
So, in an overall sense all these data types (String, Int, etc.) we saw were nothing but Classes and methods implemented over them were nothing but the methods that are written in the particular class.
Why Kotlin?
Kotlin is a modern, statically typed programming language developed by Jet Brains. It is designed to interoperate seamlessly with Java and is fully supported by the JVM
.(We will see about statically typed programming language and JVM
in the later sections).
Kotlin combines both Functional & Object Oriented Way of Programming and is concise, expressive, etc.
Kotlin simplifies the development of cross platform projects from Server Side Applications, Mobile, Web To Desktop.
Packages & Imports in Kotlin
Packages are basically just a namespace that we give to our source code file. They don't have any physical significance, but are very helpful to keep the code organised and locatable. Through packages and import statements we can use our particular package content in other files.
package vg.firstcode
fun sum() { /*..*/ }
class user { /*..*/ }
So, in the above example, the full name of our function is vg.firstcode.sum
and the full name of our class is vg.firstcode.user
A number of packages are imported into our Kotlin file by default and some are imported depending on the purpose for which we are using Kotlin.
We can also import packages based on our usage -
import vg.firstcode.user //Importing a Single Content
import vg.firstcode.* //Everything in package accessible
Starting Point of Kotlin Program
fun main() {
println("Hello world!")
}
We previously saw the terms that made up a basic Kotlin Program, so lets understand how does this human-readable code is finally understood by the machine.
Firstly, the kotlinc compiler converts the given Kotlin Code (.kt file)
into Byte code (.class)
. This compilation time is the compile time, and is used to check for issues or errors in the code.
Secondly the JVM interpreter converts the provided Byte Code (.class)
into Machine Code (01s)
. And that is how the code is executed. This is the runtime, where issues like null pointer exception, division by zero, file missing, etc. are encountered if present.
Variables
A variable can be imagined as a container of data, we create these containers / variables to hold data. A variable in Kotlin can be declared using the keyword val
or var
.
val
keyword is used to declare variables that are immutable, i.e. once the value is assigned to them it can't be changed.
Whereas, var
is used when the value can be changed.
val x : Int = 5
x += 1 //Error
var y : Int = 5
y = 7 //Success
Kotlin also supports type inference, by that we mean that is automatically identifies the data types of a declared variable. (This refers to the statically typed programming language feature of Kotlin)
val x = 5 //no need to mention data type
val y : Int //need to mention as initialized later
y = 5
Data Types
Data Type refers to the type of data we are handling. In Kotlin, data types are broadly classified into primitive
and non-primitive types
.
Primitive data types represent basic, low-level values like numbers, characters, and boolean values.
Byte
ranges from -128 to 127, Short
ranges from -32768 to 32767, Int
from -2^31 to 2^31-1.. Long
for even larger numbers.... By default the compiler infers the data type as Int, if its not exceeding the range of it, and other smallest data type if the value exceeds.
For Decimal Numbers, default type is Double
, and need to written with f or F for Float
.
Numbers can be converted to other types using functions such as : toByte()
, toShort()
, to Int()
, toLong()
, toFloat()
, toDouble()
If value of a character is digit : DigitToInt()
can convert it into Int. Non-primitive types are more complex data types that include objects, collections, and user-defined classes.
These Data Types are nothing but classes which are already included in every Kotlin file, through the default packages that are imported. When we create a variable of a particular data type, in theoretical terms it is the creation of a object of that particular class. And the different functionalities that we use on those objects are the member functions of that particular class.
Other Types are also present such as -
Any
- is the root of Kotlin, meaning it is the ultimate super class (parent) of all the classes in Kotlin. Every Class that we use in Kotlin, whether its built-in or user defined, is a subclass of Any.Functions Provided :
equals()
: determines whether two objects are equal, i.e. pointing to the same location in memory.hashCode()
: provide a hashcode for the object, generated using the memory location of the object. Therefore should be equal for object that stand true with the equals() function. Used in Hash-based collections like set, map, etc.toString()
: returns a string representation of an object.
var age1 : Any = 18
var age2 : Int = 18
var age3 : Long = 18
println(age1.equals(age2)) //true -> Any is resolved to Int @runtime
println(age2.equals(age3)) //false -> Int & Long
println(age1.equals(age3)) //false -> Any(Int) & Long
age1 = 18L
println(age1.equals(age2)) //false -> Long & Int
println(age2.equals(age3)) //false -> Int & Long
println(age1.equals(age3)) //true -> Any is resolved to Long @runtime
Nothing
- represents no value or nothing, generally used with functions that don't return normally meaning, it throws an exception, enters infinite loop, or other non-terminating process. It is a subtype of all other types, that means even if our function returns any type of value, it can end by throwing an exception, without returning any value.
fun calculateDivision(num: Int, den: Int): Int {
if (den == 0) throw IllegalStateException("Zero Divison Error")
else return num / den
}
fun main() {
try{
val result = calculateDivision(10, 0)
println(result)
}
catch(e: Exception){ print(e.message) }
}
//Output : Zero Division Error
Unit
- represents the presence of a result. Unit can be taken as place holder for something, without returning anything. By default, return type of a function is Unit.
fun sayHi() : Unit { print("Hello") }
Null Pointer Exception
Variables without a value are often assigned null value, if we try to perform operations or call methods on this null object, we lead ourselves into NPE
.
Scenario : Accessing a variable before initialising it, or explicitly declaring a non-nullable type variable null.
val age : Int //declaration
print(age) //COMPILE TIME ERROR : must be initialized
age = 10 //won't execute
var name : String = null // String can't hold null
Solution -> Using Nullable Type Variable using the ?
operator.
val age : Int?
print(age) //prints null
age = 10
Using safe calls ?:
to avoid calling methods on null, Give Default values using Elvis Operator(?:
)
var name : String? = null //Int? can hold null value
print(name?.length) //prints null : no error
print(name ?: "Default Name - Bot") //prints defau..
Check for Null Explicitly
if(name!=null) print(name.length)
Input - Output
print()
and println()
are the two functions provided by Kotlin that print their argument on the standard output. println()
, apart from printing also moves the cursor to the next line.
Common Escape Sequences such as \n
, \t
, \\
etc. are also used.
readln()
function is used to read the given input. Supports type inference, by taking the input specifically as a string.
val myInput = readln() //default takes input as String
println(myInput)
val myAge = readln().toInt() //converts input to String
//~ toBoolean(), toFloat(), toDouble()... etc
//When multiple space seperated inputs are given
val salaries = readln().split(' ').map { it.toInt() }
Operators
Operators are special symbols that perform operation on values/variables.
- Arithmetic Operators : for basic mathematical operations
val a = 10
val b = 5
println(a + b) // Addition: 15
println(a - b) // Subtraction: 5
println(a * b) // Multiplication: 50
println(a / b) // Division: 2
println(a % b) // Modulus (remainder): 0
Assignment Operators : for assigning values to variable
Arithmetic Assignment involve both assignment & arithmetics
var x = 10
x += 5 // Equivalent to x = x + 5
x -= 3 // Equivalent to x = x - 3
x *= 2 // Equivalent to x = x * 2
x /= 4 // Equivalent to x = x / 4
x %= 2 // Equivalent to x = x % 2
- Comparison Operators : compare two value & returns boolean
val a = 10
val b = 5
println(a > b) // Greater than: true
println(a < b) // Less than: false
println(a >= b) // Greater than or equal to: true
println(a <= b) // Less than or equal to: false
println(a == b) // Equality check: false
println(a != b) // Not equal: true
- Logical Operators : combines boolean expression
val x = true
val y = false
println(x && y) // Logical AND: false
println(x || y) // Logical OR: true
println(!x) // Logical NOT: false
- Bitwise Operators : performs binary representation of numbers
val a = 5 // Binary: 0101
val b = 3 // Binary: 0011
println(a and b) // Binary AND: 1 (0001)
println(a or b) // Binary OR: 7 (0111)
println(a xor b) // Binary XOR: 6 (0110)
println(a.inv()) // Binary NOT: -6
println(a shl 1) // Left shift: 10 (1010)
println(a shr 1) // Right shift: 2 (0010)
- Unary Operators : works with single operand
val a = 5
println(-a) // Unary minus: -5
println(+a) // Unary plus: 5
println(!true) // Logical NOT: false
- Increment & Decrement Operators : increment & decrement value.
var a = 10
a++ // Post-increment: a becomes 11
++a // Pre-increment: a becomes 12
a-- // Post-decrement: a becomes 11
--a // Pre-decrement: a becomes 10
- Range Operators : create range of values
val range = 1..5 // Range from 1 to 5
println(3 in range) // true
println(6 !in range) // true
- Type Check Operator : to check or cast type
val obj: Any = "Kotlin"
if (obj is String) println(obj.length) // Type check: true
if (obj !is Int) println("Not an Int") // true
- Elvis Operator, Safe Call Operator, etc.
Conditional Statements
if - else if - else
block statements are executed based on the conditions that is found true, only that particular block gets executed.
var max : Int
val a = 10
val b = 7
if(a > b) max = a
else max = b
//else block is necessary if used as an expression.
val maxValue = if(a > b) a else b
when
runs code whose condition is satisfied by the argument. when can be used with or without subject, (else block is necessary if - when is executed without a subject)
val text = when (x) { //using when - as an expression
1 -> "x == 1"
2 -> "x == 2"
else -> "x is neither 1 nor 2"
}
when{ //as statement & without subject
x == 1, x == 2 -> print("x == 1 or x == 2")
x in 3..10 -> print("x belongs to 3 to 10")
x !in 10..20 -> print("x is from 10 to 20") //~ can be used for collections
else -> print("x is neither 1 nor 2")
}
Loops
The three common looping statements used in Kotlin are while, do-while and for.
for loop
is used to iterate through things that provides an iterator, meaning it has a member function iterator(), this iterator function returns object of an Iterator<> which is an interface (interface is ~ to an incomplete class that means, it has incomplete functions, which needs to be completed before use)This Iterator<> provides us with member functions like next() and hasNext() that help us move through it.
for (i in 1..3) print(i) //Iterating over range - 1 2 3
for (i in 6 downTo 0 step 2) print(i) //6420
val array = arrayOf(1,2,3,4,5,6)
for (i in array) print(i) //Iterating over collection
for (i in array.indices) print(array[i]) //Iterating over collection using indices
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
} //libray function - withIndex()
while
loop checks the condition and runs the body till the condition is satisfied.do-while
loop runs the body and then checks the condition to run it again till the condition is satisfied.
while (x > 0) {
x-- //runs till x > 0
}
do {
val y = retrieveData()
} while (y != null) //runs once, and then runs till true
some jump expressions are also provided to us by Kotlin, which help us skip through some statements.
return
: returns from the closest enclosing functionbreak
: terminates the closest enclosing loopcontinue
: proceeds to next iteration of loop
Important Keywords
Keywords are reserved words that have predefined meaning. Eg: var, val, fun, class, if, else..
as
: Used for Type-Casting
val obj: Any = "Kotlin"
val str: String = obj as String
val str: String? = obj as? String //safe-check
in
: Used to check if value is in range, collection, etc.
val range = 1..10
print(5 in range)
print(15 !in range)
for(item in range) print(item)
is
: Used to check if an object is of a specific type
val obj : Any = "Hi"
if(obj is String) print("Yes")
else if(obj !is String) print("No")
Arrays in Kotlin
Array is a data structure(data is structure is nothing but a way of holding data) that can hold multiple values of the same type or its subtype. However, the number of elements we want the array to store should be mentioned beforehand, and is fixed.
//creating array using function
var arrWords : Array<String> = arrayOf("One", "Two", "Three")
var arrNums = arrayOf(1,2,3,4,5)
//creating array using constructor
//need to specify size of the array and a function that returns value to array(based on the index)
val arrZero = Array<Int>(3) {0} // [0, 0, 0]
val arrIndexSquare = Array(5){I -> I*I} //[0, 1, 4, 9, 16]
We can also create multi-dimensional arrays
val oneDArray = arrayOf(1,2,3)
val twoDArray = Array(2) { Array<Int>(2) { 0 } }
val threeDArray = Array(3) { Array(3) { Array<Int>(3) { 0 } } }
Accessing Elements in Array:
print(oneDArray[0]) //0
val indexZeroValue = oneDArray.get(0)
print(twoDArray[0][0]) //0
Modifying Values in Array:
oneDArray[0] = 9
oneDArray.set(0,9) //(index, value)
Iterating over an Array:
for(i in oneDArray) print(i)
for(i in 0 until oneDArray.size) print(oneDArray[i])
for((i, e) in arr.withIndex()) print("Value at $i is $e")
Converting Array to Collection
//toList
val myList = oneDArray.toList()
//toSet
val mySet = oneDArray.toSet()
//toMap(only an array of pair can be converted to map where the first instance of pair becomes the key in the map)
val pairArray = arrayOf("A" to 0, "B" to 1)
val myMap = pairArray.toMap()
Strings in Kotlin
Strings are nothing but a collection of characters, and is enclosed in double quotes(" "
), characters in string can be accessed the same way we accessed array as they also follow the same indexing operation.
Strings are immutable. Once a string is initialised, its value can't be changed. All operations that transform the strings will return their results in a new String object, leaving the original string unchanged
val str = "abc 123"
print(str[0]) //a
print(str.uppercase()) //creates and prints a new string object
print(str + 4 + " def") //abc 1234 def (string concatenation)
Strings in Kotlin can also be multiline which are defined using triple quotes, and can also contain escaped characters
val str = """hi
bye"""
val str2 = "hi \t bye"
String Templates are string literals that contain an expression(that is something that needs to be evaluated, and then concatenated with the string), kotlin calls the .toString()
function on the evaluated result which is then concatenated with the string.
val str = "abc"
print("size of str is ${str.length} and value of str is $str")
Collection in Kotlin
Collections refers to a group of variable number of items(not fixed size) of the same type and its subtype, Using Collections has some benefits over Arrays as
Collection can be made read only, which gives us more control over it
easier to add/remove elements in collections, as array are fixed in size
Collection in Kotlin can be both read-only or mutable. Mutability extends the read only features with operations such as adding, removing, updating elements.
Assigning a mutable collection to val protects the reference (you can't reassign it), but you can still modify the collection's contents (e.g., add or remove elements).
Assigning it to var both modifying the contents and reassigning the reference to another collection.
val numbers = mutableListOf(1, 2, 3) // 'numbers' is a val reference
numbers.add(4) // Adding an element is allowed
println(numbers) // Output: [1, 2, 3, 4]
numbers = mutableListOf(5, 6) // Error: Val cannot be reassignedType of
//However both the scenarios are possible with Var
List
: Ordered Collection that allows access to element by indices. Elements can occur more than once in a list. Type - Mutable & Immutable(Read-Only).Two List are considered equal if they have the same size and equal objects at the same position.
val nums : List<Int> = listOf(1,2,3)
val numbers = listOf("One", "Two", "Three")
nums.indexOf(3) //2
nums.contains(8) //false
val numsM : List<Int> = mutableListOf(1,2,3)
val numbersM = mutableListOf("One", "Two", "Three")
numsM.add(4)
numsM[1] = 8
numsM.remove(1)
numsM.removeAt(2) //index
//[8, 3]
numsM.indexOf(3) //1
numsM.contains(8) //true
//Operations in List :
val nums1 : List<Int> = listOf(1,2,3)
val nums2 : List<Int> = listOf(4,5)
nums1.addAll(nums2)
// Concatenate two lists
val combinedList = nums1 + nums2 // [1, 2, 3, 4, 5]
// Subtract elements
val subtractedList = nums1 - listOf(2, 3) // [1]
// Iteration
nums1.forEach { println(it) } // Prints: 1, 2, 3
Set
: Collection of Unique Elements, with undefined order. Two Sets are considered equal if they have the same size and each element of a set has an equal element in the other set.
val nums: Set<Int> = setOf(1, 2, 3)
val words = setOf("One", "Two", "Three")
nums.contains(3) // true
nums.size // 3
nums.isEmpty() // false
val numsM: MutableSet<Int> = mutableSetOf(1, 2, 3)
val wordsM = mutableSetOf("One", "Two", "Three")
numsM.add(4) // Adds 4 to the set
numsM.add(2) // No effect as 2 is already in the set
numsM.remove(1) // Removes 1 from the set
numsM.contains(3) // true
println(numsM) // Output: [2, 3, 4]
//Operations on Set :
val set1: Set<Int> = setOf(1, 2, 3)
val set2: Set<Int> = setOf(3, 4, 5)
// Union: Combines all elements from both sets
val unionSet = set1.union(set2) // [1, 2, 3, 4, 5]
// Intersection: Elements common to both sets
val intersectSet = set1.intersect(set2) // [3]
// Subtract: Elements in set1 but not in set2
val subtractSet = set1.subtract(set2) // [1, 2]
Map
: Collection of Key-Value Pairs where each key is unique and has exactly one value. Values can be duplicated
val immutableMap: Map<Int, String> = mapOf(1 to "One", 2 to "Two", 3 to "Three")
println(immutableMap[1]) // Output: One (Access value by key)
println(immutableMap[4]) // Output: null (Key doesn't exist)
println(immutableMap.keys) // Output: [1, 2, 3]
println(immutableMap.values) // Output: [One, Two, Three]
println(immutableMap.containsKey(2)) // Output: true
println(immutableMap.containsValue("Four")) // Output: false
val mutableMap: MutableMap<Int, String> = mutableMapOf(1 to "One", 2 to "Two", 3 to "Three")
// Add a new key-value pair
mutableMap[4] = "Four"
println(mutableMap) // Output: {1=One, 2=Two, 3=Three, 4=Four}
// Update an existing key-value pair
mutableMap[2] = "Second"
println(mutableMap) // Output: {1=One, 2=Second, 3=Three, 4=Four}
// Remove a key-value pair
mutableMap.remove(3)
println(mutableMap) // Output: {1=One, 2=Second, 4=Four}
// Check if key or value exists
println(mutableMap.containsKey(1)) // Output: true
println(mutableMap.containsValue("Three")) // Output: false
Special Functions for Collections
filter
: special function used to filter elements from a collection based on the given condition.
val nums = listOf(1, 2, 3, 4, 5)
val list1 = nums.filter(fun isOdd(a: Int) : Boolean { return a%2 != 0 })
val list2 = nums.filter(::isOdd)
//fun isOdd(a: Int) : Boolean { return a%2 != 0 }
val list3 = nums.filter{ it%2 != 0)
map
: special function used to transform a collection based on a given function
val nums = listOf(1, 2, 3, 4, 5)
val list1 = nums.map { it * it } //maps each element with its square
for each
: used to perform an action on each element of collection
val numbers = listOf(1, 2, 3, 4, 5)
numbers.forEach { println(it) }
numbers.forEachIndexed {index, value -> print($index, $value)