JSTAcademy
0 XP
Dashboard
Crash Courses
Kotlin: val/var, Data Classes, Null Safety & Extensions
20 min
PhD+195 XP
Crash Courses · PhD

Kotlin: val/var, Data Classes, Null Safety & Extensions

Learn Kotlin from a Java perspective — understand why it is preferred for Android and modern backends, and write idiomatic Kotlin in 60 minutes.
20 min read+195 XP on completionCert: Java / Kotlin Crash Course
Tap any word in the text below to start reading from there.

Kotlin: val/var, Data Classes, Null Safety & Extensions

Why Kotlin?

Kotlin is officially preferred by Google for Android development and is widely used in backend services (Ktor, Spring Boot). It compiles to JVM bytecode, so everything you know about Java applies. Kotlin eliminates Java boilerplate while adding powerful features.

---

Variables: val and var

val name = "Jordan"     // immutable (final in Java) — cannot reassign
var count = 0           // mutable — can reassign
count = count + 1       // OK
// name = "Alice"       // COMPILE ERROR: val cannot be reassigned

// Type can be declared explicitly:
val price: Double = 29.99
var inventory: Int = 100

// Type inference means you rarely need to write the type
val greeting = "Hello"  // Kotlin infers String

---

Data Classes

In Java, a simple value class requires a constructor, getters, equals(), hashCode(), and toString(). In Kotlin:

data class Product(
    val id: Int,
    val name: String,
    val price: Double,
    val category: String
)

// All this is auto-generated:
val p1 = Product(1, "Laptop", 1299.0, "Electronics")
val p2 = Product(1, "Laptop", 1299.0, "Electronics")

println(p1)           // Product(id=1, name=Laptop, price=1299.0, category=Electronics)
println(p1 == p2)     // true  (structural equality via equals())
println(p1 === p2)    // false (referential equality — different objects)

// copy() creates a modified copy without mutating original:
val discounted = p1.copy(price = 999.0)
println(discounted)   // Product(id=1, name=Laptop, price=999.0, category=Electronics)

---

Null Safety

Kotlin's type system distinguishes nullable from non-null:

var nonNull: String = "Hello"
// nonNull = null  // COMPILE ERROR

var nullable: String? = "Hello"
nullable = null  // OK

// Safe call operator (?.)
println(nullable?.length)    // null (doesn't throw)
println(nullable?.uppercase()) // null

// Elvis operator (?:) — provide a default
val length = nullable?.length ?: 0  // 0 if nullable is null

// Non-null assertion (!!) — use only when you're certain; throws NPE if null
val forcedLength = nullable!!.length  // throws KotlinNullPointerException if null

// let for null-safe operations:
nullable?.let {
    println("Not null: $it")  // only runs if nullable != null
}

---

Extension Functions

Add functions to any class without inheriting from it:

// Add isPalindrome() to String
fun String.isPalindrome(): Boolean = this == this.reversed()

// Add a toCurrency() to Double
fun Double.toCurrency(symbol: String = "$"): String =
    "$symbol[formatted]"

// Extension on a data class
fun Product.isExpensive(): Boolean = price > 500.0

// Usage:
println("racecar".isPalindrome())  // true
println("hello".isPalindrome())    // false
println(1299.0.toCurrency())       // $1299.00
println(1299.0.toCurrency("£"))    // £1299.00

val laptop = Product(1, "Laptop", 1299.0, "Electronics")
println(laptop.isExpensive())      // true

---

The when Expression

// Replaces switch — cleaner, returns a value
fun classify(n: Int): String = when {
    n < 0    -> "negative"
    n == 0   -> "zero"
    n < 10   -> "small"
    n < 100  -> "medium"
    else     -> "large"
}

// Match against specific values:
fun dayType(day: String): String = when (day) {
    "Saturday", "Sunday" -> "weekend"
    "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" -> "weekday"
    else -> "unknown"
}

// Match types (smart cast):
fun describe(obj: Any): String = when (obj) {
    is String -> "String of length ${obj.length}"  // obj is auto-cast to String
    is Int    -> "Integer: $obj"
    is Double -> "Double: (formatted)"
    is List<*> -> "List with ${obj.size} elements"
    else      -> "Unknown: ${obj::class.simpleName}"
}

---

Scope Functions

// apply — configures an object and returns it (builder pattern)
data class Config(var host: String = "", var port: Int = 8080, var debug: Boolean = false)

val config = Config().apply {
    host  = "api.jsupremetech.online"
    port  = 443
    debug = false
}

// let — transform a nullable value
val message: String? = "  Hello, World!  "
val processed = message?.let { it.trim().uppercase() }  // "HELLO, WORLD!"

// also — perform side effect, return original object
val numbers = mutableListOf(3, 1, 4, 1, 5)
    .also { println("Original: $it") }
    .apply { sort() }
    .also { println("Sorted: $it") }

---

Higher-Order Functions and Lambdas

val products = listOf(
    Product(1, "Laptop",  1299.0, "Electronics"),
    Product(2, "Desk",     450.0, "Furniture"),
    Product(3, "Monitor",  599.0, "Electronics"),
)

// Kotlin collections mirror Java Streams but with simpler syntax:
val electronicNames = products
    .filter { it.category == "Electronics" }
    .sortedByDescending { it.price }
    .map { it.name }

println(electronicNames)  // [Laptop, Monitor]

val avgPrice = products.map { it.price }.average()
println("Avg: (formatted)")
0%