OOP and Classes
Why are Kotlin classes final by default?
To follow "design for inheritance or prohibit it" (Effective Java). Inheriting from a class that was not designed for it creates fragile code. You must mark a class and each overridable member with open; an override is itself open unless marked final override. Frameworks like Spring need open classes, so the kotlin-spring/all-open compiler plugin opens them automatically.
What is a data class and what does it generate?
Example
A data class generates equals(), hashCode(), toString(), copy() and componentN() functions (for destructuring) from the properties in the primary constructor. Properties declared in the body are ignored by these functions.
data class Product(val id: Int, val name: String, val price: Double)
fun main() {
val p1 = Product(1, "Headphones", 1999.0)
val p2 = p1.copy(price = 1499.0)
println(p1)
println(p2)
println(p1 == Product(1, "Headphones", 1999.0))
val (id, name, price) = p2
println("$id -> $name costs $price")
}Output
Product(id=1, name=Headphones, price=1999.0) Product(id=1, name=Headphones, price=1499.0) true 1 -> Headphones costs 1499.0
What is the order of initialization in a Kotlin class?
Example
Superclass constructor first, then the primary constructor of the class, then property initializers and init blocks in the order they appear, and finally the body of the secondary constructor.
open class Base(name: String) {
init { println("1. Base init: $name") }
}
class Child(name: String) : Base(name.uppercase()) {
val greeting = "Hello, $name".also { println("2. Child property: $it") }
init { println("3. Child init block") }
constructor() : this("guest") { println("4. Secondary constructor body") }
}
fun main() {
Child()
}Output
1. Base init: GUEST 2. Child property: Hello, guest 3. Child init block 4. Secondary constructor body
Sealed class vs enum class: when do you use each?
An enum has a fixed set of instances, each with the same shape (e.g. OrderStatus.PLACED, SHIPPED). A sealed class/interface has a fixed set of subclasses, each with its own data, and all subclasses must be in the same module and package. Both give exhaustive when. Sealed types are the standard way to model UI state and results in Android:
sealed interface UiState {
data object Loading : UiState
data class Success(val products: List<Product>) : UiState
data class Error(val message: String) : UiState
}
when (state) { // no else needed: compiler knows all cases
UiState.Loading -> ShowSpinner()
is UiState.Success -> ProductList(state.products)
is UiState.Error -> ErrorText(state.message)
}What is the difference between object, companion object and an object expression?
object Logger { }(object declaration): a thread-safe, lazily initialized singleton.companion object { }: a singleton tied to a class, used for factory methods and constants; members are called asClassName.member. Add@JvmStaticto make them real static methods for Java.object : OnClickListener { ... }(object expression): an anonymous class instance created each time, like Java anonymous classes.
Interface vs abstract class in Kotlin?
| Interface | Abstract class |
|---|---|
| Cannot hold state (properties have no backing field) | Can have state and constructors |
| A class can implement many | A class can extend only one |
| Can have default method implementations | Can have abstract and concrete members |
Use for capabilities: Clickable, Repository | Use for a shared base with common state: BaseViewModel |
What are the visibility modifiers in Kotlin?
| Modifier | Visible in |
|---|---|
public (default) | Everywhere |
internal | The same module (Gradle module), useful for library and feature-module APIs |
protected | The class and its subclasses (not the same package, unlike Java) |
private | Inside the class, or inside the file for top-level declarations |
How do extension functions work? Are they resolved dynamically?
Example
An extension is compiled to a static function that takes the receiver as its first parameter. It cannot access private members and is resolved statically by the declared type, not the runtime type. If a member function with the same signature exists, the member wins.
open class Shape
class Circle : Shape()
fun Shape.name() = "Shape"
fun Circle.name() = "Circle"
fun String.isValidPhone() = length == 10 && all { it.isDigit() }
fun main() {
val s: Shape = Circle()
println(s.name())
println(Circle().name())
println("9876543210".isValidPhone())
println("98765".isValidPhone())
}Output
Shape Circle true false
What is class delegation with the by keyword?
Example
class A(b: B) : B by b makes the compiler forward all B methods to b, so you override only what you need. It is "composition over inheritance" without boilerplate.
class LoggingList<T>(private val inner: MutableList<T> = mutableListOf()) : MutableList<T> by inner {
override fun add(element: T): Boolean {
println("Adding $element")
return inner.add(element)
}
}
fun main() {
val cart = LoggingList<String>()
cart.add("Shoes")
cart.add("Watch")
println("size = ${cart.size}, items = $cart")
}Output
Adding Shoes Adding Watch size = 2, items = LoggingList@5b2133b1