Program Structure
Overview
This chapter introduces the organizational structure of Kotlin programs, including packages, files, classes, objects, interfaces, and other core concepts. Understanding these structures helps you write well-organized, maintainable Kotlin code.
Package Structure
Package Declaration
kotlin
// File: src/main/kotlin/com/example/myapp/models/User.kt
package com.example.myapp.models
class User(val name: String, val email: String)Package Organization Principles
kotlin
// Organize package structure by functionality
com.example.myapp/
├── models/ // Data models
│ ├── User.kt
│ ├── Product.kt
│ └── Order.kt
├── services/ // Business logic
│ ├── UserService.kt
│ └── OrderService.kt
├── repositories/ // Data access
│ ├── UserRepository.kt
│ └── OrderRepository.kt
├── controllers/ // Controllers (web apps)
│ └── UserController.kt
└── utils/ // Utility classes
├── DateUtils.kt
└── StringUtils.ktTop-Level Declarations
kotlin
// File: MathUtils.kt
package com.example.utils
// Top-level function
fun calculateArea(radius: Double): Double = Math.PI * radius * radius
// Top-level property
const val PI_APPROXIMATION = 3.14159
// Top-level class
class Calculator {
fun add(a: Int, b: Int) = a + b
}
// Top-level object
object MathConstants {
const val E = 2.71828
const val GOLDEN_RATIO = 1.61803
}Class Structure
Basic Class Definition
kotlin
// Simple class
class Person {
// Properties
var name: String = ""
var age: Int = 0
// Methods
fun introduce() {
println("Hi, I'm $name, $age years old")
}
}
// Class with primary constructor
class Student(val name: String, val studentId: String) {
// Secondary constructor
constructor(name: String, studentId: String, grade: Int) : this(name, studentId) {
this.grade = grade
}
// Property
var grade: Int = 1
// Initializer block
init {
println("Student $name created with ID $studentId")
}
// Method
fun study(subject: String) {
println("$name is studying $subject")
}
}Class Visibility Modifiers
kotlin
// Default is public
class PublicClass
// Internal visibility (same module)
internal class InternalClass
// Private class (same file)
private class PrivateClass
class Example {
// Public property
val publicProperty = "public"
// Internal property
internal val internalProperty = "internal"
// Protected property (visible to subclasses)
protected val protectedProperty = "protected"
// Private property
private val privateProperty = "private"
// Public method
fun publicMethod() {}
// Private method
private fun privateMethod() {}
}Interfaces
Interface Definition and Implementation
kotlin
// Interface definition
interface Drawable {
// Abstract property
val color: String
// Abstract method
fun draw()
// Method with default implementation
fun getInfo(): String = "Drawable object with color $color"
// Property with default implementation
val isVisible: Boolean
get() = true
}
// Implementing an interface
class Circle(override val color: String, val radius: Double) : Drawable {
override fun draw() {
println("Drawing a $color circle with radius $radius")
}
override fun getInfo(): String {
return "Circle: ${super.getInfo()}, radius=$radius"
}
}
// Multiple interface implementation
interface Movable {
fun move(x: Int, y: Int)
}
class MovableCircle(
override val color: String,
val radius: Double
) : Drawable, Movable {
private var x = 0
private var y = 0
override fun draw() {
println("Drawing circle at ($x, $y)")
}
override fun move(x: Int, y: Int) {
this.x = x
this.y = y
}
}Functional Interfaces (SAM)
kotlin
// Functional interface
fun interface StringProcessor {
fun process(input: String): String
}
// Using lambda to implement
fun main() {
val upperCaseProcessor = StringProcessor { it.uppercase() }
val result = upperCaseProcessor.process("hello")
println(result) // HELLO
// Pass lambda directly
processString("world") { it.reversed() }
}
fun processString(input: String, processor: StringProcessor): String {
return processor.process(input)
}Object Declarations and Expressions
Object Declaration (Singleton)
kotlin
// Singleton object
object DatabaseManager {
private var isConnected = false
fun connect() {
if (!isConnected) {
println("Connecting to database...")
isConnected = true
}
}
fun disconnect() {
if (isConnected) {
println("Disconnecting from database...")
isConnected = false
}
}
fun isConnected() = isConnected
}
// Using singleton object
fun main() {
DatabaseManager.connect()
println("Connected: ${DatabaseManager.isConnected()}")
DatabaseManager.disconnect()
}Companion Objects
kotlin
class User private constructor(val name: String, val email: String) {
companion object Factory {
private var userCount = 0
// Factory method
fun createUser(name: String, email: String): User {
userCount++
return User(name, email)
}
fun createGuestUser(): User {
return createUser("Guest", "guest@example.com")
}
// Companion object property
fun getUserCount() = userCount
}
override fun toString() = "User(name='$name', email='$email')"
}
fun main() {
val user1 = User.createUser("Alice", "alice@example.com")
val user2 = User.createGuestUser()
println("Users created: ${User.getUserCount()}")
println(user1)
println(user2)
}Object Expressions (Anonymous Objects)
kotlin
interface ClickListener {
fun onClick()
fun onLongClick()
}
fun main() {
// Anonymous object implementing interface
val button = object : ClickListener {
override fun onClick() {
println("Button clicked")
}
override fun onLongClick() {
println("Button long clicked")
}
}
button.onClick()
button.onLongClick()
// Anonymous object extending a class
val customList = object : ArrayList<String>() {
override fun add(element: String): Boolean {
println("Adding element: $element")
return super.add(element)
}
}
customList.add("Hello")
customList.add("World")
}Enum Classes
Basic Enum
kotlin
enum class Direction {
NORTH, SOUTH, EAST, WEST
}
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF),
YELLOW(0xFFFF00);
fun getHexString(): String = "#${rgb.toString(16).padStart(6, '0')}"
}
fun main() {
// Using enum
val direction = Direction.NORTH
println("Direction: $direction")
// Enum properties and methods
val red = Color.RED
println("Red RGB: ${red.rgb}")
println("Red Hex: ${red.getHexString()}")
// Enum iteration
println("All colors:")
for (color in Color.values()) {
println("${color.name}: ${color.getHexString()}")
}
}Advanced Enum Features
kotlin
enum class Planet(val mass: Double, val radius: Double) {
MERCURY(3.303e+23, 2.4397e6),
VENUS(4.869e+24, 6.0518e6),
EARTH(5.976e+24, 6.37814e6),
MARS(6.421e+23, 3.3972e6);
// Methods in enum class
fun surfaceGravity(): Double {
val G = 6.67300E-11
return G * mass / (radius * radius)
}
fun surfaceWeight(otherMass: Double): Double {
return otherMass * surfaceGravity()
}
companion object {
fun findByMass(mass: Double): Planet? {
return values().find { it.mass == mass }
}
}
}
fun main() {
val earthWeight = 175.0
val mass = earthWeight / Planet.EARTH.surfaceGravity()
println("Weight on different planets:")
for (planet in Planet.values()) {
val weight = planet.surfaceWeight(mass)
println("${planet.name}: %.2f".format(weight))
}
}Sealed Classes
Sealed Class Definition
kotlin
// Sealed class represents a restricted class hierarchy
sealed class Result<out T>
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
object Loading : Result<Nothing>()
// Using sealed class
fun handleResult(result: Result<String>) {
when (result) {
is Success -> println("Success: ${result.data}")
is Error -> println("Error: ${result.exception.message}")
Loading -> println("Loading...")
// No else branch needed, compiler knows all possible subclasses
}
}
fun main() {
val results = listOf(
Success("Data loaded successfully"),
Error(RuntimeException("Network error")),
Loading
)
results.forEach { handleResult(it) }
}Sealed Interfaces
kotlin
sealed interface UIEvent
data class ButtonClick(val buttonId: String) : UIEvent
data class TextInput(val text: String) : UIEvent
object ScreenLoad : UIEvent
class UIEventHandler {
fun handle(event: UIEvent) {
when (event) {
is ButtonClick -> println("Button ${event.buttonId} clicked")
is TextInput -> println("Text entered: ${event.text}")
ScreenLoad -> println("Screen loaded")
}
}
}Data Classes
Data Class Definition
kotlin
// Data class automatically generates equals, hashCode, toString, copy, etc.
data class Person(
val name: String,
val age: Int,
val email: String
) {
// Data class can also have custom methods
fun isAdult(): Boolean = age >= 18
// Data class can have secondary constructors
constructor(name: String, age: Int) : this(name, age, "")
}
fun main() {
val person1 = Person("Alice", 25, "alice@example.com")
val person2 = Person("Alice", 25, "alice@example.com")
val person3 = person1.copy(age = 26) // Copy with modification
// Auto-generated methods
println("person1 == person2: ${person1 == person2}") // true
println("person1: $person1") // Auto-generated toString
// Destructuring declaration
val (name, age, email) = person1
println("Name: $name, Age: $age, Email: $email")
// Custom method
println("Is adult: ${person1.isAdult()}")
}Inline Classes (Value Classes)
kotlin
// Inline class provides type-safe wrapper with no runtime overhead
@JvmInline
value class UserId(val value: String)
@JvmInline
value class Email(val value: String) {
init {
require(value.contains("@")) { "Invalid email format" }
}
fun getDomain(): String = value.substringAfter("@")
}
fun processUser(userId: UserId, email: Email) {
println("Processing user ${userId.value} with email ${email.value}")
println("Email domain: ${email.getDomain()}")
}
fun main() {
val userId = UserId("user123")
val email = Email("user@example.com")
processUser(userId, email)
// Type safety: cannot mix up parameters
// processUser(email, userId) // Compilation error
}Nested and Inner Classes
Nested Classes
kotlin
class Outer {
private val outerProperty = "Outer property"
// Nested class (static)
class Nested {
fun doSomething() {
println("Nested class method")
// println(outerProperty) // Compilation error: cannot access outer class members
}
}
// Inner class
inner class Inner {
fun doSomething() {
println("Inner class method")
println("Accessing: $outerProperty") // Can access outer class members
}
}
}
fun main() {
// Create nested class instance
val nested = Outer.Nested()
nested.doSomething()
// Create inner class instance
val outer = Outer()
val inner = outer.Inner()
inner.doSomething()
}Extensions
Extension Functions
kotlin
// Add extension function to String class
fun String.isPalindrome(): Boolean {
val cleaned = this.lowercase().replace(" ", "")
return cleaned == cleaned.reversed()
}
// Add extension function to List
fun <T> List<T>.secondOrNull(): T? = if (size >= 2) this[1] else null
// Extension property
val String.wordCount: Int
get() = this.split("\\s+".toRegex()).size
fun main() {
val text = "A man a plan a canal Panama"
println("'$text' is palindrome: ${text.isPalindrome()}")
println("Word count: ${text.wordCount}")
val numbers = listOf(1, 2, 3, 4, 5)
println("Second element: ${numbers.secondOrNull()}")
}Extension Scope
kotlin
class Host(val hostname: String) {
fun printHostname() { print(hostname) }
}
class Connection(val host: Host, val port: Int) {
fun printPort() { print(port) }
fun Host.printConnectionString() {
printHostname() // Calls Host.printHostname()
print(":")
printPort() // Calls Connection.printPort()
}
fun connect() {
host.printConnectionString() // Call extension function
}
}
fun main() {
Connection(Host("kotl.in"), 443).connect()
}Delegation
Class Delegation
kotlin
interface Base {
fun print()
fun printMessage(message: String)
}
class BaseImpl(val x: Int) : Base {
override fun print() { println("BaseImpl: $x") }
override fun printMessage(message: String) { println("BaseImpl: $message") }
}
// Delegate to another object
class Derived(b: Base) : Base by b {
// Can override delegated methods
override fun printMessage(message: String) {
println("Derived: $message")
}
}
fun main() {
val base = BaseImpl(42)
val derived = Derived(base)
derived.print() // Delegates to BaseImpl
derived.printMessage("Hello") // Uses overridden method
}Best Practices
1. Package Organization
kotlin
// Organize packages by feature, not technical layer
// Good organization
com.example.ecommerce/
├── user/
├── product/
├── order/
└── payment/
// Avoid this organization
com.example.ecommerce/
├── controllers/
├── services/
├── repositories/
└── models/2. Class Design
kotlin
// Prefer data classes
data class User(val id: Long, val name: String, val email: String)
// Use sealed classes for state
sealed class LoadingState {
object Loading : LoadingState()
data class Success(val data: String) : LoadingState()
data class Error(val message: String) : LoadingState()
}
// Use object declarations for singletons
object ConfigManager {
fun getConfig(key: String): String? = TODO()
}3. Visibility
kotlin
class ApiClient {
// Expose necessary interfaces
fun fetchData(): String = processRequest()
// Hide implementation details
private fun processRequest(): String = TODO()
// Use internal to limit access within module
internal fun debugInfo(): String = TODO()
}Next Steps
After understanding program structure, let's dive into Kotlin's data type system.
Next Chapter: Data Types
Exercises
- Create a project structure with multiple packages
- Implement a shape drawing system using interfaces and implementation classes
- Design a network request result using sealed classes to represent different states
- Create a factory pattern example using companion objects
- Implement a utility library that enhances existing classes using extension functions