Chapter 10: Classes & Objects – Basics
Classes & Objects – Basics — this is the chapter where we finally start building real-world things in Kotlin! 🎉
Kotlin’s class system is much cleaner, safer, and more concise than Java’s — you’ll write less boilerplate, get null safety, and enjoy primary constructors that make everything feel elegant.
Imagine we’re sitting together in a quiet Bandra café — I’ve got my laptop open, and I’m going to teach you classes like I’m explaining it to my younger brother who just discovered how powerful OOP can be when it’s done the Kotlin way.
We’ll go super slowly, with lots of real-life analogies, complete runnable programs, step-by-step explanations, tables, common mistakes with fixes, and plenty of examples you can copy-paste and run right now.
Let’s dive in!
1. Class Declaration – The Basics
Syntax (the simplest possible class):
|
0 1 2 3 4 5 6 |
class Person |
That’s it! No public, no braces, no semicolon — just class Person.
But usually we add properties (fields) and constructors:
|
0 1 2 3 4 5 6 |
class Person(val name: String, var age: Int) |
2. Primary Constructor – The Most Important Part
Kotlin has one primary constructor — it’s declared right after the class name in parentheses.
Features:
- Can have parameters with val or var → automatically becomes property
- Can have default values
- Can have visibility modifiers (private, protected, etc.)
Example 1 – Primary constructor with val/var
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Person(val name: String, var age: Int) { // Properties are already created by primary constructor! fun introduce() { println("नमस्ते! मी $name आहे, आणि माझं वय $age आहे.") } } fun main() { val p1 = Person("Webliance", 25) p1.introduce() // नमस्ते! मी Webliance आहे, आणि माझं वय 25 आहे. p1.age = 26 // age is var → can change p1.introduce() // Now age is 26 } |
Key points:
- val name → read-only property (getter auto-generated)
- var age → read-write property (getter + setter auto-generated)
3. Secondary Constructors
You can have zero or more secondary constructors — they must call the primary constructor (directly or indirectly).
Syntax:
|
0 1 2 3 4 5 6 7 8 |
constructor(parameters): this(other parameters) { // optional body } |
Example 2 – Secondary constructor
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Person(val name: String, var age: Int) { // Secondary constructor – calls primary constructor(name: String) : this(name, 18) { // default age 18 println("Secondary constructor called – default age set") } fun introduce() { println("नमस्ते! मी $name आहे, वय $age.") } } fun main() { val p1 = Person("Amit", 22) // Primary constructor val p2 = Person("Priya") // Secondary constructor p1.introduce() // नमस्ते! मी Amit आहे, वय 22. p2.introduce() // नमस्ते! मी Priya आहे, वय 18. } |
4. Properties (val/var) – The Heart of Kotlin Classes
Properties are class members that have:
- Backing field (optional)
- Getter (always)
- Setter (only for var)
Three ways to declare properties:
- In primary constructor (most common)
|
0 1 2 3 4 5 6 |
class Person(val name: String, var age: Int) |
- Inside class body (when you need custom getter/setter)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Person(val name: String) { var age: Int = 18 get() = field // default getter set(value) { if (value >= 0) field = value // custom setter with validation else println("वय नकारात्मक असू शकत नाही!") } } |
- Computed properties (no backing field)
|
0 1 2 3 4 5 6 7 8 9 |
class Person(val birthYear: Int) { val age: Int get() = java.time.Year.now().value - birthYear // computed every time } |
Example – Full class with properties
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Student(val rollNo: Int, val name: String) { var marks: Int = 0 set(value) { if (value in 0..100) field = value else println("मार्क्स 0 ते 100 च्या दरम्यान असावेत!") } val grade: Char get() = when (marks) { in 90..100 -> 'A' in 80..89 -> 'B' in 70..79 -> 'C' else -> 'F' } fun display() { println("Roll No: $rollNo, Name: $name, Marks: $marks, Grade: $grade") } } |
Usage:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
fun main() { val s = Student(101, "Webliance") s.marks = 92 s.display() // Roll No: 101, Name: Webliance, Marks: 92, Grade: A s.marks = 150 // Prints warning – marks not changed } |
5. init Blocks – Initialization Logic
init blocks run every time an object is created — right after primary constructor.
Most common uses:
- Validation
- Logging
- Complex initialization
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Person(val name: String, var age: Int) { init { println("Person object created for $name") if (age < 0) { println("Warning: Age cannot be negative! Setting to 0.") age = 0 } } init { println("Second init block – can have multiple!") } } |
Output when creating object:
|
0 1 2 3 4 5 6 7 |
Person object created for Amit Second init block – can have multiple! |
6. Member Functions & Fields – Putting It All Together
Complete real-world class example
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
class BankAccount(val accountNumber: String, var holderName: String) { var balance: Double = 0.0 private set // Only this class can change balance directly init { println("New account created: $accountNumber for $holderName") } fun deposit(amount: Double) { if (amount > 0) { balance += amount println("Deposited ₹$amount. New balance: ₹$balance") } else { println("Invalid deposit amount!") } } fun withdraw(amount: Double) { if (amount > 0 && amount <= balance) { balance -= amount println("Withdrew ₹$amount. New balance: ₹$balance") } else { println("Insufficient funds or invalid amount!") } } fun displayBalance() { println("Account: $accountNumber | Holder: $holderName | Balance: ₹$balance") } } |
Usage:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
fun main() { val acc = BankAccount("123456789", "Webliance") acc.deposit(10000) acc.withdraw(3000) acc.displayBalance() } |
Output:
|
0 1 2 3 4 5 6 7 8 9 |
New account created: 123456789 for Webliance Deposited ₹10000.0. New balance: ₹10000.0 Withdrew ₹3000.0. New balance: ₹7000.0 Account: 123456789 | Holder: Webliance | Balance: ₹7000.0 |
Quick Recap Table (Your Cheat Sheet)
| Feature | Kotlin Way (Best Practice) | Key Point |
|---|---|---|
| Primary constructor | class Person(val name: String, var age: Int) | Parameters become properties automatically |
| Secondary constructor | constructor(name: String) : this(name, 18) | Must call primary constructor |
| Properties in body | var age: Int = 18 + custom getter/setter | Use for validation/computation |
| Computed property | val age: Int get() = … | No backing field |
| init block | init { … } | Runs after primary constructor |
| Private setter | var balance: Double private set | Only class can change value |
Common Newbie Mistakes & Fixes
| Mistake | Problem | Fix |
|---|---|---|
| Writing public class Person | public not needed – default is public | Just write class Person |
| Declaring fields outside constructor | Verbose & error-prone | Use primary constructor parameters |
| Not using val/var in constructor | Parameter not a property | Always write val name: String or var |
| Multiple init blocks – wrong order | Logic runs in declaration order | Be careful – order matters |
| Forgetting this in secondary constructor | Compile error | Always call this(…) |
Homework for You (Let’s Make It Fun!)
- Basic Create a Book class with val title, val author, var price (with validation: price >= 0).
- Medium Add a secondary constructor to Book that takes only title and author (price = 500.0 default).
- Advanced Create a Rectangle class with val length, val width → computed properties area and perimeter.
- Fun Make a Person class with val name, var age, and init block that prints birthday message if age < 18.
- Challenge Create a BankAccount class with private balance, deposit(), withdraw() (with validation), and displayBalance().
You’ve just built your first real Kotlin classes — you’re now officially doing OOP the Kotlin way!
