Skip to content
ioob.dev
Go back

Kotlin Beginner Part 4 — Classes and Objects

· 2 min read
Kotlin Series (4/12)
  1. Kotlin Beginner Part 1 — Variables and Types
  2. Kotlin Beginner Part 2 — Conditionals and Loops
  3. Kotlin Beginner Part 3 — Functions
  4. Kotlin Beginner Part 4 — Classes and Objects
  5. Kotlin Beginner Part 5 — Collections and Lambdas
  6. Kotlin Part 6 — Null Safety Advanced
  7. Kotlin Part 7 — Generics
  8. Kotlin Part 8 — sealed class and enum
  9. Kotlin Part 9 — Coroutines Basics
  10. Kotlin Part 10 — Coroutines Advanced
  11. Kotlin Part 11 — DSL and Advanced Functions
  12. Kotlin Part 12 — Practical Patterns
Table of contents

Table of contents

Class Declaration

Kotlin class declarations are much more concise than Java. The constructor can be written directly in the class header.

class User(val name: String, var age: Int)

This single line does the same job as the following Java code.

public class User {
    private final String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

Declaring with val generates only a getter; var generates both getter and setter. These are called properties — a concept that combines Java’s fields with getters/setters.

val user = User("Kim", 25)
println(user.name)   // Calls the getter
user.age = 26        // Calls the setter

data class

One of the features you’ll use most in practice. When creating DTOs or value objects in Java, you had to write equals(), hashCode(), toString(), and copy() boilerplate manually or use Lombok. In Kotlin, the single word data handles it all.

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

Declaring it this way automatically generates the following.

val p1 = Product(1, "Keyboard", 50000)
val p2 = Product(1, "Keyboard", 50000)

println(p1)           // Product(id=1, name=Keyboard, price=50000)
println(p1 == p2)     // true (content comparison)
println(p1.hashCode() == p2.hashCode())  // true

// copy — copy with only some fields changed
val p3 = p1.copy(price = 45000)
println(p3)           // Product(id=1, name=Keyboard, price=45000)

// Destructuring
val (id, name, price) = p1
println("$name: $price won")  // Keyboard: 50000 won

copy() is especially useful when working with immutable objects. You can specify only the fields you want to change without creating the entire object from scratch.

object — Singleton

In Java, creating a singleton requires a private constructor, a static instance, and thread safety considerations. In Kotlin, just use object.

object Database {
    val url = "jdbc:mysql://localhost:3306/mydb"

    fun connect() {
        println("Connected to $url")
    }
}

fun main() {
    Database.connect()  // Use directly without creating an instance
}

Declaring with object guarantees a singleton at the language level. You can access it directly without creating a separate instance.

companion object

This is the equivalent of Java’s static methods. Kotlin doesn’t have a static keyword; it uses companion object instead.

class User(val name: String) {
    companion object {
        fun create(name: String): User {
            println("Creating user: $name")
            return User(name)
        }
    }
}

fun main() {
    val user = User.create("Kim")  // Call directly on the class name
}

This is commonly used when implementing the factory method pattern.

Inheritance

Kotlin classes are final by default. To allow inheritance, you must explicitly mark them with open. This is an intentional design decision, aligned with “Effective Java“‘s recommendation to “prohibit inheritance for classes not designed for it.”

open class Animal(val name: String) {
    open fun sound(): String = "..."
}

class Dog(name: String) : Animal(name) {
    override fun sound(): String = "Woof"
}

class Cat(name: String) : Animal(name) {
    override fun sound(): String = "Meow"
}

fun main() {
    val animals = listOf(Dog("Buddy"), Cat("Whiskers"))
    for (animal in animals) {
        println("${animal.name}: ${animal.sound()}")
    }
}

Both the class to inherit from and the methods to override need open. override is equivalent to Java’s @Override, but in Kotlin it’s a required keyword.

The inheritance hierarchy from the above example becomes clear when drawn as a class diagram.

classDiagram
    class Animal {
        +String name
        +sound() String
    }
    class Dog {
        +sound() String
    }
    class Cat {
        +sound() String
    }
    Animal <|-- Dog
    Animal <|-- Cat
    note for Animal "open class<br/>open fun sound()"

Interfaces

Kotlin interfaces are similar to Java 8+ interfaces. They can have both abstract methods and default implementations.

interface Drawable {
    fun draw()  // Abstract method

    fun description(): String {  // Default implementation
        return "Shape"
    }
}

class Circle(val radius: Double) : Drawable {
    override fun draw() {
        println("Drawing a circle with radius $radius")
    }
}

A class can only inherit from one class, but it can implement multiple interfaces. This principle is the same as in Java.

interface Clickable {
    fun click()
}

interface Focusable {
    fun focus()
}

class Button : Clickable, Focusable {
    override fun click() = println("Clicked!")
    override fun focus() = println("Focused!")
}

The multiple implementation relationship looks like this in a diagram. A class allows single inheritance, but multiple interfaces can be combined.

classDiagram
    class Clickable {
        <<interface>>
        +click()
    }
    class Focusable {
        <<interface>>
        +focus()
    }
    class Button {
        +click()
        +focus()
    }
    Clickable <|.. Button
    Focusable <|.. Button

The next part covers Kotlin collections and lambdas. We’ll look at functional APIs like filter, map, forEach, and scope functions (let, apply, run).

-> Part 5: Collections and Lambdas


Related Posts

Share this post on:

Comments

Loading comments...


Previous Post
Kotlin Beginner Part 3 — Functions
Next Post
Kotlin Beginner Part 5 — Collections and Lambdas