404 Not Found

404 Not Found


nginx

Kotlin Classes and Objects Explained

Kotlin's class design philosophy: primary constructor + properties in one line, init blocks replacing Java's constructor body — Charlie defines the Order class in 3 lines that are more powerful than Java's 30-line equivalent.

1. What You'll Learn


2. An Architect's Real Story

(1) Pain Point: Java Constructor Hell

In Java, Charlie's Order class needed 3 constructors (no-arg, required-fields, all-fields), plus getters/setters/validation logic — 80+ lines for a single domain class.

(2) Kotlin's Primary Constructor Solution

KOTLIN
// Java: 80+ lines for one domain class
// Kotlin: 3 lines with validation
class Order(val id: String, var status: String, val total: BigDecimal) {
    init { require(total >= BigDecimal.ZERO) { "Total must be non-negative" } }
}

Primary constructor + properties + init block combined — 80 lines of Java → 3 lines of Kotlin.


3. Primary Constructor and Properties

(1) Primary Constructor in One Line

KOTLIN
// Primary constructor with properties in one line
class Order(val id: String, var status: String, val total: Double)

// Equivalent Java would be 20+ lines

(2) Constructor Parameters vs Properties

KOTLIN
// val/var in constructor = property
class Order(val id: String,    // read-only property
            var status: String, // mutable property
            total: Double)      // just constructor param, not a property

// Usage
val order = Order("ORD-001", "PENDING", 299.99)
println(order.id)      // OK - val property
order.status = "PAID"  // OK - var property
// order.total         // ERROR - not a property

(3) Primary Constructor vs Secondary Constructor

KOTLIN
class Order(val id: String, var status: String, val total: Double) {
    // Secondary constructor MUST delegate to primary
    constructor(id: String) : this(id, "PENDING", 0.0)

    // Another secondary constructor
    constructor(id: String, total: Double) : this(id, "PENDING", total)
}

val order1 = Order("ORD-001")               // Secondary
val order2 = Order("ORD-002", 299.99)        // Secondary
val order3 = Order("ORD-003", "CONFIRMED", 1_500.00)  // Primary

(4) Constructor Comparison

Dimension Java Kotlin
Define properties + constructor Separate Primary constructor in one line
Multiple constructors Independent definitions Secondary must delegate to primary
Param to property Manual assignment val/var automatic
Default values Method overloading Default parameters

4. init Block

The init block runs immediately after the primary constructor — used for validation and initialization logic.

(1) Basic Usage

KOTLIN
class Order(val id: String, var status: String, val total: Double) {
    init {
        require(total >= 0.0) { "Total must be non-negative, got $total" }
        require(id.startsWith("ORD-")) { "Order ID must start with ORD-" }
    }

    // Multiple init blocks execute in order
    init {
        println("Order $id created with total \$$total USD")
    }
}

(2) init Block Execution Order

KOTLIN
class Example {
    val a = println("1: property initialization")

    init {
        println("2: first init block")
    }

    val b = println("3: property initialization")

    init {
        println("4: second init block")
    }
}
// Output: 1, 2, 3, 4 (declaration order)

5. Visibility Modifiers

(1) Four Visibility Levels

KOTLIN
class OrderProcessor {
    // public (default): visible everywhere
    fun process(order: Order) { ... }

    // private: visible inside this class only
    private fun validate(order: Order) { ... }

    // protected: visible in this class and subclasses
    protected fun calculateTax(order: Order) { ... }

    // internal: visible within the same module
    internal fun report() { ... }
}

(2) Visibility Comparison Table

Modifier Inside Class Subclass Same Module Global
public
internal
protected
private

(3) Java vs Kotlin Visibility Differences

Dimension Java Kotlin
Default visibility package-private public
Module visibility None internal
Package visibility package-private None (use internal instead)
Top-level declarations public only public / internal / private

6. Deferred Initialization

(1) lateinit var

KOTLIN
class OrderService {
    // lateinit: promise to initialize before use
    lateinit var repository: OrderRepository

    fun init(repo: OrderRepository) {
        repository = repo
    }

    fun process(order: Order) {
        // Access before init throws UninitializedPropertyAccessException
        repository.save(order)
    }
}

(2) by lazy

KOTLIN
class OrderProcessor {
    // lazy: thread-safe, initialized on first access
    val cache: OrderCache by lazy {
        println("Initializing cache...")
        OrderCache(maxSize = 10_000)
    }

    // lazy with custom lock mode
    val heavyResource by lazy(LazyThreadSafetyMode.PUBLICATION) {
        loadHeavyResource()
    }
}

(3) lateinit vs by lazy Comparison

Dimension lateinit by lazy
Type var (mutable) val (read-only)
Initialization timing Manual assignment On first access
Thread safety No Yes (default)
Nullability Non-null declared Non-null declared
Access before init Runtime exception Won't happen
Use case DI framework injection Expensive computed properties

7. Class Relationship Diagram

100%
classDiagram
    class Order {
        +val id: String
        +var status: String
        +val total: Double
        +val items: List~OrderItem~
        +fun addItem(item: OrderItem)
    }
    class OrderItem {
        +val sku: String
        +val quantity: Int
        +val unitPrice: Double
        +fun subtotal: Double
    }
    class Customer {
        +val id: String
        +val name: String
        +val email: String?
        +val address: Address?
    }
    class Address {
        +val street: String
        +val city: String
        +val country: String
    }
    Order --> OrderItem : contains
    Order --> Customer : belongs to
    Customer --> Address : has

8. Complete Example: OrderProcessor Domain Model

KOTLIN
// ============================================
// OrderProcessor - Domain Model
// Feature: Order, OrderItem, Customer with init validation
// ============================================

import java.math.BigDecimal
import java.math.RoundingMode

class Address(val street: String, val city: String, val country: String) {
    override fun toString(): String = "$street, $city, $country"
}

class Customer(val id: String, val name: String, val email: String?) {
    var address: Address? = null

    fun getDisplayEmail(): String = email ?: "no-email"

    override fun toString(): String = "Customer($id, $name, ${getDisplayEmail()})"
}

class OrderItem(val sku: String, val quantity: Int, val unitPrice: BigDecimal) {
    init {
        require(quantity > 0) { "Quantity must be positive, got $quantity" }
        require(unitPrice >= BigDecimal.ZERO) { "Price must be non-negative" }
    }

    val subtotal: BigDecimal
        get() = unitPrice.multiply(BigDecimal(quantity)).setScale(2, RoundingMode.HALF_UP)
}

class Order(
    val id: String,
    var status: String,
    private val _items: MutableList<OrderItem> = mutableListOf()
) {
    init {
        require(id.startsWith("ORD-")) { "Order ID must start with ORD-" }
    }

    val items: List<OrderItem> get() = _items.toList()

    val total: BigDecimal
        get() = _items.fold(BigDecimal.ZERO) { acc, item -> acc.add(item.subtotal) }

    fun addItem(item: OrderItem) {
        _items.add(item)
    }

    val itemCount: Int get() = _items.size

    // Lazy computed tax
    val tax by lazy {
        total.multiply(BigDecimal("0.08")).setScale(2, RoundingMode.HALF_UP)
    }

    override fun toString(): String = "Order($id, $status, ${itemCount} items, \$$total USD)"
}

fun main() {
    val customer = Customer("CUST-001", "Alice", "alice@example.com").also {
        it.address = Address("123 Main St", "New York", "US")
    }
    println(customer)
    println("Address: ${customer.address}")

    val order = Order("ORD-001", "PENDING")
    order.addItem(OrderItem("SKU-WIDGET", 3, BigDecimal("9.99")))
    order.addItem(OrderItem("SKU-GADGET", 1, BigDecimal("149.99")))
    order.addItem(OrderItem("SKU-DOOHICKEY", 5, BigDecimal("4.50")))

    println(order)
    println("Subtotal: \$$${order.total} USD")
    println("Tax: \$$${order.tax} USD")
    println("Grand Total: \$$${order.total.add(order.tax)} USD")

    order.status = "CONFIRMED"
    println("Status updated: ${order.status}")
}

Output:

TEXT
Customer(CUST-001, Alice, alice@example.com)
Address: 123 Main St, New York, US
Order(ORD-001, PENDING, 3 items, $199.42 USD)
Subtotal: $$199.42 USD
Tax: $$15.95 USD
Grand Total: $$215.37 USD
Status updated: CONFIRMED

❓ FAQ

Q Can primary and secondary constructors coexist?
A Yes, but secondary constructors must delegate to the primary using this(...). Default parameters are preferred over secondary constructors.
Q Can I have multiple init blocks?
A Yes, multiple init blocks execute in declaration order. It's recommended to merge them into one to avoid confusion.
Q Can lateinit be used for primitive types?
A No. lateinit only works with non-primitive types (object types), because primitives have default values. Use by lazy or nullable types + defaults for Int/Double etc.
Q Is by lazy initialization thread-safe?
A Yes by default (SYNCHRONIZED mode). If you're sure of single-thread access, use LazyThreadSafetyMode.NONE for better performance.
Q How does internal visibility work in Maven/Gradle multi-module projects?
A internal restricts visibility within the same Gradle module or Maven module. Different modules — even within the same project — cannot access internal members.
Q Why doesn't Kotlin have Java's package-private?
A Kotlin uses internal (module-level visibility) instead of package-private. Package-level visibility is often misused in Java; module-level better fits modern project structures.

📖 Summary


📝 Exercises

  1. Beginner (⭐): Define a Product class (id: String, name: String, price: Double) with an init block validating price >= 0. Hint: require(price >= 0)
  2. Intermediate (⭐⭐): Design a Service class using lateinit to inject Repository and by lazy to initialize a cache. Hint: lateinit + by lazy
  3. Challenge (⭐⭐⭐): Implement a complete Order domain model with Order/OrderItem/Customer, all validation in init blocks, and defensive copying for items. Hint: see Section 8's complete example

← Previous | Next →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏