Working with Kotlin Object-Oriented Programming
1. Creating Classes
| Feature | Description | Example |
|---|---|---|
| class declaration | Basic class definition using class keyword |
|
| primary constructor | Constructor defined in the class header | class Person(val name: String, var age: Int) |
| secondary constructor | Additional constructors using constructor |
|
| data class | Automatically generates equals(), hashCode(), and toString() |
data class User(val id: Int, val name: String) |
| sealed class | Restricts subclassing to a specific set of classes | |
Example: Basic Class Usage
class Student {
var name: String = ""
var grade: Int = 0
fun displayInfo() {
println("Name: $name, Grade: $grade")
}
}
val student = Student()
student.name = "Alice"
student.grade = 90
student.displayInfo()
2. Implementing Constructors
| Constructor Type | Description | Example |
|---|---|---|
| Primary Constructor | Defined in the class header. Can include properties and initialization code. | |
| Secondary Constructor | Defined using the constructor keyword. Must delegate to the primary constructor. |
|
| No-argument Constructor | Primary constructor with no parameters. Can be omitted if properties are initialized directly. | |
Example: Using Primary Constructor with Initialization
class Student(val id: Int, val name: String) {
init {
println("Student created with ID: $id")
}
}
val student = Student(101, "Alice")
Example: Secondary Constructor with Delegation
class Rectangle(val width: Double, val height: Double) {
constructor(side: Double) : this(side, side)
}
val square = Rectangle(5.0)
val rectangle = Rectangle(4.0, 6.0)
3. Working with Properties
| Property Type | Description | Syntax | Notes |
|---|---|---|---|
| Delegated Properties | Properties whose behavior is delegated to another object | val prop: Type by Delegate() |
Use by keyword to delegate property access |
| Mutable Properties | Properties that can be changed after initialization | var name: String = "default" |
Use var for mutable properties |
| Read-Only Properties | Properties that can only be read, not modified | val name: String = "default" |
Use val for immutable properties |
| Backing Fields | Hidden fields that store property values | private var _value: Int = 0 |
Use _value to store actual value |
| Computed Properties | Properties whose value is calculated on access | val fullName: String get() = "$firstName $lastName" |
Use get() to define computed logic |
Example: Delegated Properties
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("initial") { _, old, new ->
println("Name changed from $old to $new")
}
}
fun main() {
val user = User()
user.name = "Alice" // Prints: Name changed from initial to Alice
}
Example: Computed Properties
class Rectangle(val width: Double, val height: Double) {
val area: Double
get() = width * height
val perimeter: Double
get() = 2 * (width + height)
}
fun main() {
val rect = Rectangle(5.0, 3.0)
println("Area: ${rect.area}") // Area: 15.0
println("Perimeter: ${rect.perimeter}") // Perimeter: 16.0
}
Example: Mutable vs Read-Only Properties
class Person {
var age: Int = 25 // Mutable property
set(value) {
if (value >= 0) field = value
}
val fullName: String = "John Doe" // Read-only property
}
fun main() {
val person = Person()
person.age = 30 // Valid
// person.fullName = "Jane Doe" // Error: Val cannot be reassigned
}
Note: Kotlin's property system provides powerful features like delegation and computed properties that simplify common patterns in object-oriented programming.
4. Implementing Inheritance
| Feature | Description | Example |
|---|---|---|
| Inheritance in Kotlin | Allows a class to inherit properties and methods from another class. | class Animal { fun speak() { println("Animal speaks") } } |
| Extending a Class | Use open keyword to allow inheritance. |
open class Animal { ... } |
| Subclass Declaration | Use class SubClass : SuperClass |
class Dog : Animal() { ... } |
| Overriding Methods | Use override to redefine methods from parent class. |
override fun speak() { println("Dog barks") } |
| Calling Superclass Methods | Use super.methodName() |
override fun speak() { super.speak(); println("Dog barks") } |
Example: Basic Inheritance
open class Animal {
open fun speak() {
println("Animal speaks")
}
}
class Dog : Animal() {
override fun speak() {
super.speak()
println("Dog barks")
}
}
fun main() {
val dog = Dog()
dog.speak()
}
Note: In Kotlin, classes are final by default. Use
open to enable inheritance.
Warning: If a method is not marked
open, it cannot be overridden in subclasses.
5. Using Abstract Classes
| Feature | Description | Example |
|---|---|---|
| abstract class | Defines a class that cannot be instantiated directly and may contain abstract methods. | |
| abstract method | A method declared without implementation in an abstract class. | abstract fun makeSound() |
| concrete method | A method with implementation in an abstract class that can be inherited. | |
| inheritance from abstract class | Subclasses must implement all abstract methods. | |
Example: Implementing Abstract Class
abstract class Shape {
abstract fun area(): Double
open fun description(): String = "This is a shape"
}
class Circle(radius: Double) : Shape() {
override fun area(): Double = Math.PI * radius * radius
override fun description(): String = "This is a circle"
}
val circle = Circle(5.0)
println(circle.area()) // 78.53981633974483
println(circle.description()) // This is a circle
Note: Abstract classes can have constructors, properties, and methods with implementations.
6. Creating Interfaces
In Kotlin, interfaces are similar to Java interfaces but with some additional features like default implementations and extension functions.
| Feature | Description | Example |
|---|---|---|
| Basic Interface | Defines a contract with methods that implementing classes must provide. | interface Drawable {<br> fun draw()<br>} |
| Default Implementation | Allows methods to have a default implementation using fun. |
interface Logger {<br> fun log(message: String) {<br> println("Log: $message")<br> }<br>} |
| Abstract Methods | Methods without implementation that must be overridden. | interface Shape {<br> fun area(): Double<br>} |
| Properties in Interface | Interfaces can declare properties, which must be implemented by classes. | interface Vehicle {<br> val brand: String<br>} |
| Companion Object | Interfaces can have companion objects for static-like functionality. | interface Constants {<br> companion object {<br> const val MAX_SPEED = 200<br> }<br>} |
Example: Implementing an Interface
interface Drawable {<br> fun draw()<br>}<br><br>class Circle : Drawable {<br> override fun draw() {<br> println("Drawing a circle")<br> }<br>}
Note: A class can implement multiple interfaces using a comma-separated list.
Example: Multiple Interface Implementation
interface Flyable {<br> fun fly()<br>}<br><br>interface Swimmable {<br> fun swim()<br>}<br><br>class Duck : Flyable, Swimmable {<br> override fun fly() {<br> println("Duck flying")<br> }<br><br> override fun swim() {<br> println("Duck swimming")<br> }<br>}
Key Points About Interfaces
- Interfaces define contracts for classes to implement.
- They support default implementations and abstract methods.
- Classes can implement multiple interfaces.
- Properties in interfaces must be implemented by implementing classes.
7. Implementing Visibility Modifiers
| Modifier | Description | Access Scope | Usage Example |
|---|---|---|---|
| public | Default modifier; accessible from anywhere | Any class, package, or module | class MyClass { public fun doSomething() { } } |
| private | Accessible only within the same class | Same class only | class MyClass { private fun doSomething() { } } |
| protected | Accessible within the same class and subclasses | Same class and subclasses | open class Parent { protected fun doSomething() { } } |
| internal | Accessible within the same module | Same module only | class MyClass { internal fun doSomething() { } } |
Note: In Kotlin,
internal visibility is more restrictive than Java's default package-private access.Example: Using Visibility Modifiers
class Person {
private val id: String = "123"
internal var name: String = "John"
public fun getId(): String {
return id
}
}
class Employee : Person() {
fun printName() {
println(name) // Valid: internal property accessible in same module
// println(id) // Invalid: private property not accessible
}
}
8. Using Data Classes
| Feature | Description | Example |
|---|---|---|
| data class declaration | Declares a class that primarily holds data | data class Person(val name: String, val age: Int) |
| copy() | Creates a copy of the instance with specified fields changed | |
| componentN() | Enables destructuring declarations | val (name, age) = person |
| toString() | Automatically generated to show object state | Person(name=Alice, age=30) |
| equals() / hashCode() | Automatically generated for value-based comparison | person1 == person2 |
Example: Data Class Usage
data class Product(
val id: Int,
val name: String,
val price: Double
)
val product1 = Product(1, "Laptop", 999.99)
val product2 = product1.copy(name = "Gaming Laptop")
val (id, name, price) = product1
println(product1) // Product(id=1, name=Laptop, price=999.99)
println(product2) // Product(id=1, name=Gaming Laptop, price=999.99)
Note: Data classes are immutable by default when using
val properties.Warning: Do not use
var properties in data classes unless you intend to mutate state, as it can lead to unexpected behavior.9. Implementing Enum Classes
Enum classes in Kotlin are a special type of class used to define a fixed set of constants. They are similar to enums in other languages but offer more flexibility and features.
| Feature | Description | Example |
|---|---|---|
| Basic Enum Declaration | Defines a simple enum with constants. | enum class Color { RED, GREEN, BLUE } |
| Enum with Properties | Enums can have properties and methods. | enum class Direction(val degree: Int) { NORTH(0), EAST(90), SOUTH(180), WEST(270) } |
| Enum with Methods | Enums can include methods and override functions. | enum class Planet(val mass: Double, val radius: Double) { EARTH(5.972e24, 6371e3), MARS(6.39e23, 3389.5e3); fun surfaceGravity(): Double = 6.673e-11 * mass / (radius * radius) } |
| Using Enum Values | Accessing enum constants and their properties. | val color = Color.RED<br>val direction = Direction.NORTH<br>println(direction.degree) // Outputs: 0 |
Example: Using Enum in Switch-like Logic
fun describeColor(color: Color): String = when (color) {
Color.RED -> "Red is for danger"
Color.GREEN -> "Green is for go"
Color.BLUE -> "Blue is for calm"
}
Example: Enum with Constructor Parameters
enum class Size(val width: Int, val height: Int) {
SMALL(100, 100),
MEDIUM(200, 200),
LARGE(300, 300)
}
Note: Enum constants are implicitly
public, final, and static. You can't instantiate them directly.Warning: Unlike Java, Kotlin enums do not support
valueOf or values methods directly. Use enumValues<T>() and enumValueOf<T>() instead.Key Points About Kotlin Enums
- Enums are classes, not primitive types.
- They support properties and methods just like regular classes.
- Each enum constant is an instance of the enum class.
- Enums can be used in
whenexpressions for exhaustive matching.
10. Working with Sealed Classes
Sealed classes in Kotlin are a special kind of class that restricts the inheritance hierarchy to a specific set of subclasses. They are useful for modeling a restricted set of types, such as states in a state machine or different types of responses from an API.
| Feature | Description | Notes |
|---|---|---|
| Sealed Class Declaration | sealed class Shape |
Cannot be instantiated directly. Subclasses must be declared within the same file or in a sealed hierarchy. |
| Subclass Declaration | class Circle(radius: Double) : Shape() |
Subclasses must be declared in the same file or as nested classes within the sealed class. |
| Companion Object | companion object |
Can be used to define factory methods or utility functions for the sealed class. |
Example: Sealed Class Usage
sealed class Result<T> {
data class Success<T>(val value: T) : Result<T>()
data class Error(val message: String) : Result<T>()
}
fun processResult(result: Result<String>) {
when (result) {
is Result.Success -> println("Success: ${result.value}")
is Result.Error -> println("Error: ${result.message}")
}
}
Note: The
when expression must handle all possible subclasses of a sealed class, or use an else clause to handle any unexpected cases.
Key Points About Sealed Classes
- They are not open by default, meaning they cannot be extended outside their defining scope.
- They are not instantiable; only their subclasses can be instantiated.
- They are particularly useful for modeling a closed set of types, such as state machines or API responses.
- They support
whenexpressions with exhaustive checks, ensuring all cases are handled.
11. Using Nested and Inner Classes
| Class Type | Description | Key Features |
|---|---|---|
| Nested Class | A class defined inside another class without referencing the outer class instance | Declared with inner keyword, can access outer class members |
| Inner Class | A class defined inside another class that has access to the outer class instance | Uses inner keyword, can access outer class members directly |
Example: Nested and Inner Classes
class OuterClass {
private val outerProperty = "I'm from outer"
class NestedClass {
fun display() {
println("Nested class accessing: $outerProperty") // ❌ Not possible
}
}
inner class InnerClass {
fun display() {
println("Inner class accessing: $outerProperty") // ✅ Possible
}
}
}
fun main() {
val outer = OuterClass()
val nested = OuterClass.NestedClass()
val inner = outer.InnerClass()
nested.display() // Output: Nested class accessing: null
inner.display() // Output: Inner class accessing: I'm from outer
}
Note: Nested classes do not have access to the outer class instance, whereas inner classes do.
12. Implementing Delegation
Delegation in Kotlin allows you to delegate the implementation of an interface or a class to another object. This is achieved using the by keyword.
| Feature | Description | Example |
|---|---|---|
| Delegation Syntax | Uses by keyword to delegate implementation |
class MyClass<T>(delegate: T) : T by delegate |
| Interface Delegation | Implementing an interface by delegating to another object | interface MyInterface { fun doSomething() }<br>class MyClass(delegate: MyInterface) : MyInterface by delegate |
| Property Delegation | Using by with property delegates |
class MyClass {<br> var prop: String by Delegate() |
Example: Interface Delegation
interface Drawable {<br> fun draw(): Unit<br>}<br><br>class Canvas : Drawable {<br> override fun draw() {<br> println("Drawing on canvas")<br> }<br>}<br><br>class Shape(delegatedDrawable: Drawable) : Drawable by delegatedDrawable<br><br>fun main() {<br> val shape = Shape(Canvas())<br> shape.draw() // Output: Drawing on canvas<br>}
Example: Property Delegation
import kotlin.properties.ReadWriteProperty<br>import kotlin.reflect.KProperty<br><br>class Delegate<T>(private var value: T) : ReadWriteProperty<Any?, T> {<br> override fun getValue(thisRef: Any?, property: KProperty<*>): T = value<br> override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {<br> this.value = value<br> }<br>}<br><br>class MyClass {<br> var prop: String by Delegate("initial")<br>}<br><br>fun main() {<br> val obj = MyClass()<br> println(obj.prop) // Output: initial<br> obj.prop = "updated"<br> println(obj.prop) // Output: updated<br>}
Note: Delegation in Kotlin is a powerful feature that allows for clean and readable code by offloading responsibilities to other objects.