Classes and Objects
When to Use
- When you need to represent real-world entities in your code
- When you want to organize related data and functions together
- When you need to create multiple objects with similar properties
- When you want to reuse code across your program
| Component |
What It Does |
Example |
| Class Declaration |
Defines a new type of object |
class Dog { } |
| Properties |
Stores data in the class |
var name: String = "Unknown" |
| Methods |
Defines actions the class can perform |
fun bark() { println("Woof!") } |
| Object Creation |
Creates an instance of the class |
val myDog = Dog() |
Practical Examples
// ===========================================
// BASIC CLASS WITH PROPERTIES AND METHODS
// ===========================================
// Example 1: Simple Dog class
// This demonstrates the basic structure of a class
class Dog {
// Properties - data that describes the dog
var name: String = "Unknown"
var age: Int = 0
var breed: String = "Mixed"
var isHungry: Boolean = true
// Methods - actions the dog can perform
fun bark() {
println("$name says: Woof! Woof!")
}
fun eat() {
if (isHungry) {
println("$name is eating...")
isHungry = false
println("$name is no longer hungry!")
} else {
println("$name is not hungry right now.")
}
}
fun sleep() {
println("$name is sleeping... Zzz...")
isHungry = true // Dog gets hungry while sleeping
}
fun celebrateBirthday() {
age++
println("Happy Birthday, $name! You are now $age years old!")
}
fun displayInfo() {
println("""
Dog Information:
Name: $name
Age: $age
Breed: $breed
Hungry: $isHungry
""".trimIndent())
}
}
// Example 2: Bank Account class
// This shows a more practical business application
class BankAccount {
// Properties
var accountNumber: String = ""
var accountHolder: String = ""
var balance: Double = 0.0
var isActive: Boolean = true
// Methods
fun deposit(amount: Double) {
if (amount > 0 && isActive) {
balance += amount
println("Deposited $${String.format("%.2f", amount)} to account $accountNumber")
println("New balance: $${String.format("%.2f", balance)}")
} else {
println("Invalid deposit amount or account inactive")
}
}
fun withdraw(amount: Double): Boolean {
if (amount > 0 && amount <= balance && isActive) {
balance -= amount
println("Withdrew $${String.format("%.2f", amount)} from account $accountNumber")
println("New balance: $${String.format("%.2f", balance)}")
return true
} else {
println("Insufficient funds, invalid amount, or account inactive")
return false
}
}
fun checkBalance() {
println("Account $accountNumber balance: $${String.format("%.2f", balance)}")
}
fun deactivate() {
isActive = false
println("Account $accountNumber has been deactivated")
}
}
// ===========================================
// CREATING AND USING OBJECTS
// ===========================================
println("=== CREATING AND USING DOG OBJECTS ===")
// Create a new Dog object (instance of the Dog class)
val myDog = Dog()
// Set the dog's properties
myDog.name = "Buddy"
myDog.age = 3
myDog.breed = "Golden Retriever"
// Use the dog's methods
myDog.displayInfo()
myDog.bark()
myDog.eat()
myDog.sleep()
myDog.celebrateBirthday()
myDog.displayInfo()
println("\n=== CREATING MULTIPLE DOG OBJECTS ===")
// Create another dog object
val anotherDog = Dog()
anotherDog.name = "Max"
anotherDog.age = 5
anotherDog.breed = "Labrador"
// Each dog object has its own state
println("First dog: ${myDog.name}, Age: ${myDog.age}")
println("Second dog: ${anotherDog.name}, Age: ${anotherDog.age}")
// Dogs can have different states
myDog.eat() // Buddy eats
anotherDog.eat() // Max eats
myDog.eat() // Buddy is not hungry anymore
println("\n=== CREATING AND USING BANK ACCOUNTS ===")
// Create a bank account
val account1 = BankAccount()
account1.accountNumber = "ACC001"
account1.accountHolder = "John Doe"
account1.balance = 1000.0
// Use the bank account
account1.checkBalance()
account1.deposit(500.0)
account1.withdraw(200.0)
account1.checkBalance()
// Create another account
val account2 = BankAccount()
account2.accountNumber = "ACC002"
account2.accountHolder = "Jane Smith"
account2.balance = 2500.0
// Each account has its own state
println("\nAccount Summary:")
println("Account 1: $${String.format("%.2f", account1.balance)}")
println("Account 2: $${String.format("%.2f", account2.balance)}")
Constructors
When to Use
- When you need to initialize objects with specific values
- When you want to ensure objects are created with valid data
- When you need different ways to create objects
| Type |
What It Does |
When to Use It |
| Primary Constructor |
Main way to initialize objects |
For basic initialization |
| Secondary Constructor |
Alternative way to initialize objects |
When you need multiple initialization options |
| Initializer Block |
Runs code during initialization |
When you need to perform setup tasks |
Practical Examples
// ===========================================
// PRIMARY CONSTRUCTOR EXAMPLES
// ===========================================
// Example 1: Class with primary constructor
// This is the most common way to create classes in Kotlin
class Person(val name: String, var age: Int) {
// The constructor parameters automatically create properties
// 'val name' creates an immutable property
// 'var age' creates a mutable property
// You can add additional properties
var email: String = ""
var isActive: Boolean = true
// Initializer block - runs when object is created
init {
println("Creating person: $name, age $age")
// You can add validation logic
if (age < 0) {
throw IllegalArgumentException("Age cannot be negative")
}
if (name.isEmpty()) {
throw IllegalArgumentException("Name cannot be empty")
}
}
// Methods
fun introduce() {
println("Hi, I'm $name and I'm $age years old.")
}
fun haveBirthday() {
age++
println("Happy Birthday, $name! You are now $age years old!")
}
fun updateEmail(newEmail: String) {
email = newEmail
println("$name's email updated to: $email")
}
}
// Example 2: Class with default parameters
// This makes the class more flexible
class Car(
val brand: String,
val model: String,
var year: Int = 2024,
var color: String = "White",
var mileage: Double = 0.0
) {
init {
println("Creating car: $year $brand $model in $color")
}
fun start() {
println("Starting $brand $model...")
}
fun drive(distance: Double) {
mileage += distance
println("Drove $distance miles. Total mileage: $mileage")
}
fun displayInfo() {
println("""
Car Information:
Brand: $brand
Model: $model
Year: $year
Color: $color
Mileage: $mileage miles
""".trimIndent())
}
}
// ===========================================
// SECONDARY CONSTRUCTOR EXAMPLES
// ===========================================
// Example 3: Class with secondary constructor
// This provides alternative ways to create objects
class BankAccount(val accountNumber: String, var balance: Double) {
var accountHolder: String = "Unknown"
var accountType: String = "Checking"
var isActive: Boolean = true
// Primary constructor
init {
println("Account $accountNumber created with balance $${String.format("%.2f", balance)}")
}
// Secondary constructor - calls primary constructor with 'this'
constructor(number: String, balance: Double, holder: String) : this(number, balance) {
accountHolder = holder
println("Account created for $holder")
}
// Another secondary constructor
constructor(number: String, balance: Double, holder: String, type: String) : this(number, balance, holder) {
accountType = type
println("Account type set to: $type")
}
// Methods
fun displayAccountInfo() {
println("""
Account Information:
Number: $accountNumber
Holder: $accountHolder
Type: $accountType
Balance: $${String.format("%.2f", balance)}
Active: $isActive
""".trimIndent())
}
fun deposit(amount: Double) {
if (amount > 0 && isActive) {
balance += amount
println("Deposited $${String.format("%.2f", amount)}")
}
}
fun withdraw(amount: Double): Boolean {
if (amount > 0 && amount <= balance && isActive) {
balance -= amount
println("Withdrew $${String.format("%.2f", amount)}")
return true
}
return false
}
}
// ===========================================
// USING CONSTRUCTORS
// ===========================================
println("=== USING PRIMARY CONSTRUCTORS ===")
// Create Person objects using primary constructor
val person1 = Person("Alice", 25)
val person2 = Person("Bob", 30)
person1.introduce()
person2.introduce()
person1.haveBirthday()
person1.updateEmail("alice@example.com")
println("\n=== USING CAR CONSTRUCTORS WITH DEFAULTS ===")
// Create cars with different numbers of parameters
val car1 = Car("Toyota", "Camry") // Uses defaults for year, color, mileage
val car2 = Car("Honda", "Civic", 2023, "Blue") // Override some defaults
val car3 = Car("Ford", "Mustang", 2022, "Red", 15000.0) // Override all defaults
car1.displayInfo()
car2.displayInfo()
car3.displayInfo()
car1.start()
car1.drive(50.0)
car1.displayInfo()
println("\n=== USING SECONDARY CONSTRUCTORS ===")
// Create bank accounts using different constructors
val account1 = BankAccount("ACC001", 1000.0) // Primary constructor
val account2 = BankAccount("ACC002", 2000.0, "John Doe") // Secondary constructor
val account3 = BankAccount("ACC003", 3000.0, "Jane Smith", "Savings") // Another secondary constructor
account1.displayAccountInfo()
account2.displayAccountInfo()
account3.displayAccountInfo()
// Test account operations
account1.deposit(500.0)
account2.withdraw(300.0)
account3.deposit(1000.0)
println("\nAfter transactions:")
account1.displayAccountInfo()
account2.displayAccountInfo()
account3.displayAccountInfo()
Properties and Accessors
When to Use
- Use properties to store data in your classes
- Use custom accessors when you need to control how properties are accessed
- Use private properties when you want to restrict access
| Feature |
What It Does |
When to Use It |
| Basic Property |
Stores a value |
For simple data storage |
| Custom Getter |
Computes a value when accessed |
When the value needs to be calculated |
| Custom Setter |
Validates or transforms values |
When you need to control how values are set |
| Private Property |
Restricts access to the property |
When you want to protect data |
Practical Examples
// ===========================================
// BASIC PROPERTIES AND ACCESSORS
// ===========================================
// Example 1: Class with basic properties
// This shows the standard way to define properties
class Student(
val studentId: String, // Immutable property (read-only)
var name: String, // Mutable property (read-write)
var age: Int // Mutable property (read-write)
) {
// Additional properties
var email: String = ""
var major: String = "Undeclared"
var gpa: Double = 0.0
// Method to display student information
fun displayInfo() {
println("""
Student Information:
ID: $studentId
Name: $name
Age: $age
Email: $email
Major: $major
GPA: $gpa
""".trimIndent())
}
}
// Example 2: Class with custom getters and setters
// This demonstrates how to control property access
class BankAccount(val accountNumber: Int, private var accountBalance: Double) {
// Public property with custom getter
val balance: Double
get() {
println("Balance accessed for account $accountNumber")
return accountBalance
}
// Public property with custom getter and setter
var balanceLessFees: Double
get() = accountBalance - 25.0 // $25 monthly fee
set(value) {
// Validate the new value
if (value >= 0) {
accountBalance = value + 25.0 // Add fee back to get actual balance
println("Balance updated to $${String.format("%.2f", value)} (plus fees)")
} else {
println("Error: Balance cannot be negative")
}
}
// Private property for internal use
private val monthlyFee: Double = 25.0
// Method to get account type
val accountType: String
get() = when {
accountBalance >= 10000 -> "Premium"
accountBalance >= 5000 -> "Standard"
else -> "Basic"
}
// Method to display account information
fun displayAccountInfo() {
println("""
Account Information:
Number: $accountNumber
Balance: $${String.format("%.2f", balance)}
Balance after fees: $${String.format("%.2f", balanceLessFees)}
Account Type: $accountType
Monthly Fee: $${String.format("%.2f", monthlyFee)}
""".trimIndent())
}
// Method to deposit money
fun deposit(amount: Double) {
if (amount > 0) {
accountBalance += amount
println("Deposited $${String.format("%.2f", amount)}")
println("New balance: $${String.format("%.2f", balance)}")
} else {
println("Invalid deposit amount")
}
}
// Method to withdraw money
fun withdraw(amount: Double): Boolean {
if (amount > 0 && amount <= balance) {
accountBalance -= amount
println("Withdrew $${String.format("%.2f", amount)}")
println("New balance: $${String.format("%.2f", balance)}")
return true
} else {
println("Invalid withdrawal amount or insufficient funds")
return false
}
}
}
// Example 3: Class with computed properties
// This shows properties that calculate values on demand
class Rectangle(val width: Double, val height: Double) {
// Computed property for area
val area: Double
get() = width * height
// Computed property for perimeter
val perimeter: Double
get() = 2 * (width + height)
// Computed property for diagonal
val diagonal: Double
get() = kotlin.math.sqrt(width * width + height * height)
// Computed property for shape description
val shapeDescription: String
get() = when {
width == height -> "Square"
width > height -> "Landscape Rectangle"
else -> "Portrait Rectangle"
}
// Method to display rectangle information
fun displayInfo() {
println("""
Rectangle Information:
Width: $width
Height: $height
Area: $area
Perimeter: $perimeter
Diagonal: ${String.format("%.2f", diagonal)}
Shape: $shapeDescription
""".trimIndent())
}
}
// Example 4: Class with validation in setters
// This demonstrates how to validate data when setting properties
class Person(var name: String, var age: Int) {
// Custom setter with validation for name
var fullName: String = name
set(value) {
if (value.isNotBlank() && value.length >= 2) {
field = value.trim() // 'field' refers to the backing field
println("Name updated to: '$field'")
} else {
println("Error: Name must be at least 2 characters long")
}
}
// Custom setter with validation for age
var personAge: Int = age
set(value) {
if (value >= 0 && value <= 150) {
field = value
println("Age updated to: $field")
} else {
println("Error: Age must be between 0 and 150")
}
}
// Computed property for age group
val ageGroup: String
get() = when {
personAge < 13 -> "Child"
personAge < 20 -> "Teenager"
personAge < 65 -> "Adult"
else -> "Senior"
}
// Computed property for voting eligibility
val canVote: Boolean
get() = personAge >= 18
// Method to display person information
fun displayInfo() {
println("""
Person Information:
Name: '$fullName'
Age: $personAge
Age Group: $ageGroup
Can Vote: $canVote
""".trimIndent())
}
}
// ===========================================
// USING PROPERTIES AND ACCESSORS
// ===========================================
println("=== USING BASIC PROPERTIES ===")
// Create and use Student objects
val student1 = Student("S001", "Alice Johnson", 20)
student1.email = "alice@university.edu"
student1.major = "Computer Science"
student1.gpa = 3.8
student1.displayInfo()
println("\n=== USING CUSTOM ACCESSORS ===")
// Create and use BankAccount objects
val account1 = BankAccount(1001, 5000.0)
account1.displayAccountInfo()
// Test custom getters and setters
println("\nAccessing balance: ${account1.balance}")
account1.balanceLessFees = 2000.0 // This will update the actual balance
account1.displayAccountInfo()
// Test deposit and withdrawal
account1.deposit(1000.0)
account1.withdraw(500.0)
account1.displayAccountInfo()
println("\n=== USING COMPUTED PROPERTIES ===")
// Create and use Rectangle objects
val rect1 = Rectangle(5.0, 3.0)
val rect2 = Rectangle(4.0, 4.0)
rect1.displayInfo()
println()
rect2.displayInfo()
println("\n=== USING VALIDATION IN SETTERS ===")
// Create and use Person objects
val person1 = Person("John", 25)
person1.displayInfo()
// Test validation in setters
person1.fullName = "John Doe" // Valid name
person1.fullName = "A" // Invalid name (too short)
person1.fullName = " " // Invalid name (blank)
person1.personAge = 30 // Valid age
person1.personAge = -5 // Invalid age (negative)
person1.personAge = 200 // Invalid age (too high)
person1.displayInfo()
Companion Objects
When to Use
- When you need to create static members in your class
- When you want to create factory methods
- When you need to track class-level information
| Feature |
What It Does |
When to Use It |
| Companion Object |
Holds static members |
For class-level functionality |
| Factory Method |
Creates objects in a controlled way |
When you need special object creation |
| Static Property |
Shares data across all instances |
When you need to track class-level data |
Practical Examples
// ===========================================
// COMPANION OBJECT EXAMPLES
// ===========================================
// Example 1: Basic companion object
// This demonstrates the fundamental concept
class Car {
var brand: String = "Unknown"
var model: String = "Unknown"
var year: Int = 2024
companion object {
// Static property - shared across all Car instances
var totalCars: Int = 0
// Static method - can be called without creating an instance
fun getTotalCars(): Int {
return totalCars
}
// Factory method - creates cars in a controlled way
fun createCar(brand: String, model: String, year: Int): Car {
val car = Car()
car.brand = brand
car.model = model
car.year = year
totalCars++ // Increment the counter
return car
}
// Static method to reset counter
fun resetCounter() {
totalCars = 0
println("Car counter reset to 0")
}
}
// Instance method
fun displayInfo() {
println("$year $brand $model")
}
}
// Example 2: Companion object with constants and utilities
// This shows more practical usage patterns
class MathUtils {
companion object {
// Mathematical constants
const val PI = 3.14159265359
const val E = 2.71828182846
const val GOLDEN_RATIO = 1.61803398875
// Utility functions
fun factorial(n: Int): Long {
return if (n <= 1) 1 else n * factorial(n - 1)
}
fun isPrime(n: Int): Boolean {
if (n < 2) return false
for (i in 2..kotlin.math.sqrt(n.toDouble()).toInt()) {
if (n % i == 0) return false
}
return true
}
fun fibonacci(n: Int): Long {
return if (n <= 1) n.toLong() else fibonacci(n - 1) + fibonacci(n - 2)
}
fun roundToDecimal(value: Double, decimals: Int): Double {
val factor = kotlin.math.pow(10.0, decimals.toDouble())
return kotlin.math.round(value * factor) / factor
}
}
}
// Example 3: Companion object for configuration and settings
// This demonstrates managing application-wide settings
class AppConfig {
companion object {
// Application settings
var debugMode: Boolean = false
var maxUsers: Int = 100
var sessionTimeout: Int = 3600 // seconds
var databaseUrl: String = "localhost:5432"
// Configuration methods
fun enableDebugMode() {
debugMode = true
println("Debug mode enabled")
}
fun disableDebugMode() {
debugMode = false
println("Debug mode disabled")
}
fun updateMaxUsers(newMax: Int) {
if (newMax > 0) {
maxUsers = newMax
println("Maximum users updated to: $maxUsers")
} else {
println("Error: Maximum users must be positive")
}
}
fun displayConfig() {
println("""
Application Configuration:
Debug Mode: $debugMode
Max Users: $maxUsers
Session Timeout: ${sessionTimeout} seconds
Database URL: $databaseUrl
""".trimIndent())
}
}
}
// Example 4: Companion object for validation and formatting
// This shows utility functions that don't need instance data
class StringUtils {
companion object {
// Validation functions
fun isValidEmail(email: String): Boolean {
return email.contains("@") &&
email.contains(".") &&
email.indexOf("@") < email.lastIndexOf(".")
}
fun isValidPhoneNumber(phone: String): Boolean {
val digits = phone.filter { it.isDigit() }
return digits.length in 10..11
}
fun isValidPassword(password: String): Boolean {
return password.length >= 8 &&
password.any { it.isUpperCase() } &&
password.any { it.isDigit() }
}
// Formatting functions
fun formatPhoneNumber(phone: String): String {
val digits = phone.filter { it.isDigit() }
return when {
digits.length == 10 -> "(${digits.substring(0, 3)}) ${digits.substring(3, 6)}-${digits.substring(6)}"
digits.length == 11 && digits.startsWith("1") -> "1 (${digits.substring(1, 4)}) ${digits.substring(4, 7)}-${digits.substring(7)}"
else -> phone
}
}
fun formatCurrency(amount: Double): String {
return "$${String.format("%.2f", amount)}"
}
fun capitalizeWords(text: String): String {
return text.split(" ").joinToString(" ") { word ->
word.lowercase().replaceFirstChar { it.uppercase() }
}
}
}
}
// ===========================================
// USING COMPANION OBJECTS
// ===========================================
println("=== USING CAR COMPANION OBJECT ===")
// Use companion object methods without creating instances
println("Total cars before creation: ${Car.getTotalCars()}")
// Create cars using the factory method
val car1 = Car.createCar("Toyota", "Camry", 2024)
val car2 = Car.createCar("Honda", "Civic", 2023)
val car3 = Car.createCar("Ford", "Mustang", 2022)
// Display car information
car1.displayInfo()
car2.displayInfo()
car3.displayInfo()
// Check total cars
println("Total cars after creation: ${Car.getTotalCars()}")
// Reset counter
Car.resetCounter()
println("Total cars after reset: ${Car.getTotalCars()}")
println("\n=== USING MATH UTILS COMPANION OBJECT ===")
// Use mathematical constants and utilities
println("PI: ${MathUtils.PI}")
println("E: ${MathUtils.E}")
println("Golden Ratio: ${MathUtils.GOLDEN_RATIO}")
println("Factorial of 5: ${MathUtils.factorial(5)}")
println("Is 17 prime? ${MathUtils.isPrime(17)}")
println("Fibonacci number 10: ${MathUtils.fibonacci(10)}")
println("3.14159 rounded to 2 decimals: ${MathUtils.roundToDecimal(3.14159, 2)}")
println("\n=== USING APP CONFIG COMPANION OBJECT ===")
// Display initial configuration
AppConfig.displayConfig()
// Modify configuration
AppConfig.enableDebugMode()
AppConfig.updateMaxUsers(200)
AppConfig.sessionTimeout = 7200
// Display updated configuration
AppConfig.displayConfig()
println("\n=== USING STRING UTILS COMPANION OBJECT ===")
// Test validation functions
val testEmails = listOf("user@example.com", "invalid-email", "test@", "@domain.com")
val testPhones = listOf("555-123-4567", "1234567890", "1-800-555-0123", "invalid")
val testPasswords = listOf("StrongPass123", "weak", "12345678", "NoNumbers")
println("Email Validation:")
for (email in testEmails) {
println(" '$email': ${StringUtils.isValidEmail(email)}")
}
println("\nPhone Validation:")
for (phone in testPhones) {
println(" '$phone': ${StringUtils.isValidPhoneNumber(phone)}")
}
println("\nPassword Validation:")
for (password in testPasswords) {
println(" '$password': ${StringUtils.isValidPassword(password)}")
}
// Test formatting functions
println("\nFormatting Examples:")
println("Phone: ${StringUtils.formatPhoneNumber("5551234567")}")
println("Currency: ${StringUtils.formatCurrency(1234.56)}")
println("Capitalized: ${StringUtils.capitalizeWords("hello world kotlin programming")}")