Announced as Google's preferred "Kotlin-First" language for Android at Google I/O 2019, Kotlin (developed by JetBrains) solves major Java verbosity and runtime hazards:
NullPointerException at compile time through its native type system.val (Immutable Reference)
Read-only reference (similar to final in Java). Once assigned, it cannot be reassigned:
val appName: String = "DevAscent"
appName = "Other" // COMPILER ERROR!
Best Practice: Always prefer val by default to guarantee thread safety and prevent accidental state mutation.
var (Mutable Variable)A mutable variable that can be reassigned to new values of the same declared type:
var downloadProgress: Int = 0
downloadProgress = 45 // Valid!
const val: Compile-time constants known before execution (must be top-level or in a companion object):
const val API_TIMEOUT = 5000L
In Kotlin code, there is no syntactical distinction between primitive types and object wrappers. You work uniformly with: Int, Double, Float, Long, Short, Byte, Boolean, and Char. Under the hood, the Kotlin compiler automatically optimizes these into fast raw JVM primitives (int, double) unless nullable references are used.
Type Inference: The compiler automatically deduces data types: val score = 95 (inferred as Int).
==): Calls equals() under the hood, safely handling nulls!===): Checks if both references point to the exact same memory address on the Heap.1..10 (inclusive), 1 until 10 (excludes 10), 10 downTo 1 step 2.if (x in 1..100)."User: $username"."Total: ${price * quantity}".""") preserving formatting without escape sequences:
val sql = """SELECT * FROM users""".trimIndent()
if as an Expression & when Pattern MatchingIn Kotlin, control flow structures are expressions rather than mere statements, meaning they return a calculated result directly.
if Expression (Replaces Ternary)Kotlin has no condition ? a : b ternary operator because if returns a value directly:
val max = if (a > b) {
println("Choosing a")
a // Last expression is returned
} else {
b
}
when Expression (Supercharged Switch)Replaces Java's switch with rich pattern matching, type checks, and ranges:
val status = when (statusCode) {
200, 201 -> "Success"
in 400..499 -> "Client Error"
is String -> "Parsed Message"
else -> "Unknown Gateway Status"
}
Functions in Kotlin do not need to be wrapped inside ceremonial classes; they can exist as top-level functions:
fun multiply(a: Int, b: Int) = a * bfun connect(host: String, port: Int = 8080, timeout: Int = 3000)
connect(port = 9000, host = "localhost")button.setOnClickListener { view -> handleClick() }
it Parameter: If a lambda has only one parameter, it can be referenced implicitly as it:
names.filter { it.startsWith("A") }
In Java, any object reference can hold null, causing frequent NullPointerException crashes in mobile production.
Kotlin separates the type system into Non-Nullable (default) and Nullable:
var name: String = "Alice" // CANNOT BE NULL! name = null throws compile error!var city: String? = null // Explicitly allows null via the ? suffix| Operator | Name | Operational Semantics | Example Syntax |
|---|---|---|---|
?. |
Safe Call Operator | Executes method only if reference is non-null; returns null otherwise without crashing |
val len = city?.length |
?: |
Elvis Operator | Provides a default fallback value if the left-hand expression evaluates to null |
val len = city?.length ?: 0 |
!! |
Not-Null Assertion | Forces runtime unwrap; throws NullPointerException immediately if reference is null |
val len = city!!.length // Avoid! |
as? |
Safe Cast Operator | Attempts downcasting; returns null if type mismatch occurs instead of ClassCastException |
val num = obj as? Int |
user?.profile?.address
?: "Default Address"
Kotlin splits collections into two distinct interface hierarchies:
List<out T>, Set<out T>, Map<K, out V>. Expose only read methods (size, get, contains). Created via: listOf(), setOf(), mapOf().MutableList<T>, MutableSet<T>, MutableMap<K, V>. Add mutation operations (add, remove, clear). Created via: mutableListOf(), mutableSetOf(), mutableMapOf().val numbers = listOf(1, 2, 3, 4, 5, 6)
// Functional filtering and mapping
val evenSquares = numbers
.filter { it % 2 == 0 } // [2, 4, 6]
.map { it * it } // [4, 16, 36]
// Grouping
val grouped = numbers.groupBy { if (it % 2 == 0) "EVEN" else "ODD" }
open Inheritance
In Kotlin, classes are final by default. To allow subclassing, a class must be explicitly marked with open:
open class Vehicle(val vin: String) {
open fun startEngine() = "Vroom"
}
class Car(vin: String, val doors: Int) : Vehicle(vin) {
override fun startEngine() = "Car engine online"
}
data class (Auto-Generated Boilerplate)
Declaring data class commands the compiler to automatically synthesize:
equals(), hashCode(), toString(), copy(), and destructuring declarations:
data class UserProfile(
val id: Long,
val username: String,
val isVerified: Boolean = false
)
// Copying with mutation
val u1 = UserProfile(101, "shivan")
val u2 = u1.copy(isVerified = true)
Kotlin allows you to extend existing classes (including system classes like String, View, or Context) with new methods without modifying their source code or inheriting them:
// Extension on standard String class
fun String.isValidEmail(): Boolean {
return this.contains("@") && this.contains(".")
}
// Usage:
val email = "student@devascent.me"
val valid = email.isValidEmail() // true
Operating system threads are expensive (~1MB stack, kernel context switching). Spawning hundreds of threads can crash an Android device with an OutOfMemoryError.
Coroutines are lightweight cooperative threads. Thousands of coroutines can execute on a single physical thread without blocking it.
suspend Functions: Methods that can be paused (suspended) and resumed later without blocking the underlying thread: suspend fun fetchOrders(): List<Order>Dispatchers.Main: Bound to the Android UI thread (safe for updating views).Dispatchers.IO: Optimized for disk I/O, database queries, and network API calls.Dispatchers.Default: Optimized for CPU-intensive computations (JSON parsing, sorting).launch (fire-and-forget job) and async (returns a Deferred<T> whose result is accessed via await()).
This verified program unites all Unit 2 concepts:
1. data class modeling immutable sensor telemetry with default parameters.
2. Extension function formatting byte memory metrics.
3. Null-safe Safe Call (?.) and Elvis (?:) fallback logic.
4. Functional collection operations (filter, map, groupBy) and when expression routing.
package com.devascent.kotlin
// 1. Data Class with Default Parameter
data class TelemetryPacket(
val deviceId: String,
val batteryLevel: Int,
val ipAddress: String?, // Nullable field
val allocatedRamMb: Long = 256L
)
// 2. Extension Function on Long
fun Long.formatAsGigabytes(): String {
val gb = this / 1024.0
return String.format("%.2f GB", gb)
}
// 3. Execution Controller
fun main() {
println("==================================================")
println(" DEVASCENT KOTLIN MOBILE TELEMETRY ENGINE ")
println("==================================================")
// A. Collection of Packets
val packets = listOf(
TelemetryPacket("DEV-01", 85, "192.168.1.10", 2048L),
TelemetryPacket("DEV-02", 12, null, 512L), // Null IP
TelemetryPacket("DEV-03", 48, "10.0.0.5", 4096L),
TelemetryPacket("DEV-04", 9, null, 1024L) // Null IP
)
// B. Null Safety & Elvis Operator
println("--- Device Network Registry (Null Safety & Elvis) ---")
for (pkt in packets) {
// Safe Call ?. with Elvis fallback ?:
val ip = pkt.ipAddress?.uppercase() ?: "<OFFLINE_NO_IP>"
val ramFormatted = pkt.allocatedRamMb.formatAsGigabytes()
println(" Device: ${pkt.deviceId} | IP: $ip | RAM: $ramFormatted")
}
// C. When Expression for Battery State Evaluation
println("\n--- Battery Health Diagnostics (when Expression) ---")
for (pkt in packets) {
val status = when (pkt.batteryLevel) {
in 80..100 -> "OPTIMAL CHARGE"
in 20..79 -> "NORMAL DISCHARGE"
else -> "CRITICAL LOW POWER WARNING!"
}
println(" [${pkt.deviceId}] (${pkt.batteryLevel}%): $status")
}
// D. Functional Collection Pipeline
val criticalDevices = packets
.filter { it.batteryLevel < 20 }
.map { it.deviceId }
println("\nCritical Dispatch Queue: $criticalDevices")
println("==================================================")
}
?, ?., and ?:?String) and nullable types (e.g., String?). The ? denotes a nullable type. The ?. (Safe Call) executes a method/property access only if the reference is not null, otherwise returning null without throwing an exception. The ?: (Elvis Operator) returns the left-hand expression if non-null, or evaluates and returns the right-hand fallback expression if the left-hand side is null.
data class in Kotlin, and what methods are generated automatically?data class is an optimized class whose primary purpose is holding immutable or mutable state. The Kotlin compiler automatically synthesizes: (1) equals() and hashCode() based on primary constructor properties, (2) a clean human-readable toString(), (3) a copy() method for non-destructive mutation, and (4) componentN() functions enabling destructuring declarations (val (id, name) = user).
final by default? How do you enable inheritance?final by default to prevent brittle base class vulnerabilities. To allow subclassing, the class must be explicitly prepended with the open keyword. Subclasses inherit via the colon syntax: class Child : Parent().
this) is passed as the first parameter. Because they are resolved statically, extension functions do not modify the original class bytecode.
suspend function, it pauses without blocking the underlying thread, releasing that thread to perform other tasks until the suspended computation completes.
val and const val in Kotlin?val defines a read-only variable whose value can be assigned at runtime (e.g., val currentTime = System.currentTimeMillis()). const val defines a true compile-time constant whose value must be known and evaluated during compilation (primitives or String literals only). const val must be declared at the top level or inside a companion object.
if (obj instanceof String) still requires an explicit cast: ((String) obj).length(). In Kotlin, the compiler's Smart Cast feature tracks conditions: once verified via if (obj is String), the compiler automatically casts obj to String within the scope of that branch without requiring manual downcasting syntax.
List, Set, Map) which expose only accessor methods and cannot be modified after instantiation, and mutable collections (MutableList, MutableSet, MutableMap) which allow additions, updates, and deletions. This separation enforces defensive programming and immutability by default.