Kotlin Collections and OOP: List, Set, Map, Classes, Inheritance & Polymorphism

1. Collections in Kotlin

What are collections in Kotlin?

Collections are data structures to store and manipulate multiple items. Kotlin provides List, Set, and Map, with immutable and mutable variants.

Types:

Immutable vs. Mutable:

Key Features: Null safety (e.g., List<Int?> for nullable elements), rich standard library functions like map, filter, groupBy.

Use Case: Storing and processing data (e.g., user lists, configuration maps).

Example:

// Collections example
fun main() {
    // Immutable List
    val numbers: List<Int> = listOf(1, 2, 3, 2)
    println("List: $numbers")
    println("Filtered (even): ${numbers.filter { it % 2 == 0 }}")
    
    // Mutable Set
    val uniqueNames: MutableSet<String> = mutableSetOf("Krishna", "Ram", "Krishna")
    uniqueNames.add("Kristal")
    println("Set: $uniqueNames")
    
    // Mutable Map
    val salaries: MutableMap<String, Int> = mutableMapOf("Krishna" to 50000, "Ram" to 60000)
    salaries["Kristal"] = 70000
    println("Map: $salaries")
    println("Names with salary > 55000: ${salaries.filterValues { it > 55000 }}")
}
Output:
List: [1, 2, 3, 2]
Filtered (even): [2, 2]
Set: [Krishna, Ram, Kristal]
Map: {Krishna=50000, Ram=60000, Kristal=70000}
Names with salary > 55000: {Ram=60000, Kristal=70000}

Note: listOf creates an immutable list; filter extracts even numbers. mutableSetOf ensures unique elements; add modifies the set. mutableMapOf stores key-value pairs; filterValues filters by value.

2. Classes and Objects

What are classes and objects in Kotlin?

Class: A blueprint for objects, defining properties and functions. Syntax: class ClassName { ... }.

Object: An instance of a class, created using the class constructor. Creation: val obj = ClassName().

Use Case: Modeling entities like employees, vehicles, or configurations.

Example:

// Classes and objects example
class Employee(val name: String, val salary: Int) {
    fun displayInfo(): String {
        return "Name: $name, Salary: $$salary"
    }
}

fun main() {
    val emp1 = Employee("Krishna", 60000)
    val emp2 = Employee("Ram", 55000)
    println(emp1.displayInfo())
    println(emp2.displayInfo())
}
Output:
Name: Krishna, Salary: $60000
Name: Ram, Salary: $55000

Note: Employee class has properties name and salary, initialized in the primary constructor. displayInfo returns a formatted string. Objects emp1 and emp2 are instances with unique data.

3. init Blocks

What are init blocks in Kotlin?

init blocks are executed during object initialization, after the primary constructor. They are used for setup or validation logic.

Syntax: init { ... }.

Use Case: Initializing complex properties or enforcing constraints.

Example:

// init block example
class Car(val brand: String, val model: String) {
    var fullName: String
    
    init {
        fullName = "$brand $model"
        println("Initialized: $fullName")
    }
    
    fun carInfo(): String = "Car: $fullName"
}

fun main() {
    val car1 = Car("Toyota", "Camry")
    val car2 = Car("Honda", "Civic")
    println(car1.carInfo())
    println(car2.carInfo())
}
Output:
Initialized: Toyota Camry
Initialized: Honda Civic
Car: Toyota Camry
Car: Honda Civic

Note: init block sets fullName based on constructor parameters. Runs automatically during object creation.

4. Instance vs. Class (Companion) Variables

What are instance and class (companion) variables in Kotlin?

Instance Variables: Unique to each object, defined in the class body or constructor (e.g., val name in an Employee object).

Class (Companion) Variables: Shared across all instances, defined in a companion object. Syntax: companion object { val/var name }.

Use Case: Instance variables for object-specific data; companion variables for shared data.

Example:

// Instance vs. companion variables
class Employee(val name: String, val salary: Int) {
    companion object {
        const val COMPANY_NAME = "TechCorp"
    }
    
    fun info(): String = "$name at $COMPANY_NAME, Salary: $$salary"
}

fun main() {
    val emp1 = Employee("Krishna", 60000)
    val emp2 = Employee("Ram", 55000)
    println(emp1.info())
    println(emp2.info())
    println("Company: ${Employee.COMPANY_NAME}")
}
Output:
Krishna at TechCorp, Salary: $60000
Ram at TechCorp, Salary: $55000
Company: TechCorp

Note: name and salary are instance variables, unique per object. COMPANY_NAME is a companion variable, shared across all instances. const val ensures compile-time constant for efficiency.

5. Inheritance and Method Overriding

What are inheritance and method overriding in Kotlin?

Inheritance: A class (subclass) inherits properties and functions from another class (superclass). Syntax: open class Base { ... } and class Derived : Base() { ... }. Classes are final by default; open allows inheritance.

Method Overriding: Subclass provides a specific implementation of a superclass function. Syntax: override fun in subclass; superclass function must be open.

Use Case: Extending behavior (e.g., specialized employee types).

Example:

// Inheritance and method overriding
open class Person(val name: String) {
    open fun describe(): String = "Person: $name"
}

class Employee(name: String, val salary: Int) : Person(name) {
    override fun describe(): String = "Employee: $name, Salary: $$salary"
}

fun main() {
    val person = Person("Kristal")
    val employee = Employee("Krishna", 60000)
    println(person.describe())
    println(employee.describe())
}
Output:
Person: Kristal
Employee: Krishna, Salary: $60000

Note: Person is open to allow inheritance; describe is open for overriding. Employee inherits name and overrides describe for specific behavior. super.describe() can call the superclass method if needed.

6. Polymorphism and Encapsulation

What are polymorphism and encapsulation in Kotlin?

Polymorphism: Objects of different classes can be treated as instances of a common superclass, with methods behaving differently. Types: Compile-time (function overloading) and runtime (method overriding).

Encapsulation: Restricting access to properties and functions using visibility modifiers (private, protected, internal, public (default)). Private properties use private set or backing fields for controlled access.

Use Case: Polymorphism for flexible interfaces; encapsulation for data protection.

Example:

// Polymorphism and encapsulation
open class Employee(val name: String, private var salary: Int) {
    open fun describe(): String = "Employee: $name"
    
    fun getSalary(): Int = salary
}

class Manager(name: String, salary: Int) : Employee(name, salary) {
    override fun describe(): String = "Manager: $name"
}

fun main() {
    val employees: List<Employee> = listOf(Employee("Krishna", 60000), Manager("Ram", 80000))
    // Polymorphism
    employees.forEach { println(it.describe()) }
    
    // Encapsulation
    val emp = Employee("Kristal", 55000)
    println("Salary: ${emp.getSalary()}")
    // emp.salary = 1000 // Error: private property
}
Output:
Employee: Krishna
Manager: Ram
Salary: 55000

Note: Polymorphism: describe varies by class (Employee vs. Manager). Encapsulation: salary is private, accessed via getSalary. List<Employee> holds both types, demonstrating runtime polymorphism.

7. Class and Static (Companion) Methods

What are class and static (companion) methods in Kotlin?

Class Methods: Functions in a companion object, accessed via the class name. Syntax: companion object { fun method() }. Equivalent to Java's static methods but more flexible.

Static Behavior: Use @JvmStatic for true static methods in JVM interop.

Use Case: Utility functions or factory methods not tied to instances.

Example:

// Companion methods example
class Employee(val name: String) {
    companion object {
        const val COMPANY_NAME = "TechCorp"
        
        fun createFromString(data: String): Employee {
            return Employee(data.split(",")[0])
        }
        
        @JvmStatic
        fun isValidName(name: String): Boolean = name.length >= 2
    }
}

fun main() {
    val emp = Employee.createFromString("Krishna,60000")
    println("Employee: ${emp.name}, Company: ${Employee.COMPANY_NAME}")
    println("Is 'A' valid? ${Employee.isValidName("A")}")
    println("Is 'Ram' valid? ${Employee.isValidName("Ram")}")
}
Output:
Employee: Krishna, Company: TechCorp
Is 'A' valid? false
Is 'Ram' valid? true

Note: createFromString is a factory method in the companion object. isValidName mimics static behavior with @JvmStatic. Companion object members are accessed via Employee.

8. Special Methods (e.g., toString, componentN)

What are special methods in Kotlin?

Special methods customize object behavior, often in data classes or via operator overloading. Examples include toString() for string representation and componentN() for destructuring declarations.

Special Methods: toString(), componentN(), equals, hashCode, copy (in data classes).

Use Case: Enhancing object usability in printing, destructuring, or comparisons.

Example:

// Special methods example
data class Team(val name: String, val members: Int) {
    override fun toString(): String = "Team(name=$name, members=$members)"
}

fun main() {
    val team = Team("Coders", 3)
    // toString
    println(team)
    
    // Destructuring (componentN)
    val (name, members) = team
    println("Name: $name, Members: $members")
}
Output:
Team(name=Coders, members=3)
Name: Coders, Members: 3

Note: data class auto-generates toString, componentN, equals, etc. override fun toString() customizes the output format. Destructuring uses component1, component2 automatically.

9. Comprehensive Example Combining Collections and OOP

Example:

// Comprehensive collections and OOP example
open class Person(val name: String) {
    companion object {
        const val GROUP = "Community"
        
        fun createFromString(data: String): Person = Person(data)
    }
    
    open fun describe(): String = "Person: $name"
}

class Employee(name: String, private var salary: Int) : Person(name) {
    init {
        if (salary <= 0) throw IllegalArgumentException("Salary must be positive")
    }
    
    override fun describe(): String = "Employee: $name, Salary: $$salary"
    
    fun getSalary(): Int = salary
    
    companion object {
        fun isValidSalary(salary: Int): Boolean = salary > 0
    }
}

fun main() {
    // Collections
    val employees: MutableList<Employee> = mutableListOf(
        Employee("Krishna", 60000),
        Employee("Ram", 55000)
    )
    employees.add(Employee("Kristal", 70000))
    
    val salaryMap: Map<String, Int> = employees.associate { it.name to it.getSalary() }
    val highEarners: Set<String> = employees.filter { it.getSalary() > 60000 }.map { it.name }.toSet()
    
    // Polymorphism
    val people: List<Person> = listOf(Person("Dave"), employees[0], employees[1])
    println("Descriptions:")
    people.forEach { println(it.describe()) }
    
    // Collections output
    println("\nSalary Map: $salaryMap")
    println("High Earners: $highEarners")
    
    // Companion methods
    println("\nIs 0 valid salary? ${Employee.isValidSalary(0)}")
    println("Group: ${Person.GROUP}")
    
    // Special methods (data class)
    data class Team(val name: String, val size: Int)
    val team = Team("Devs", employees.size)
    val (teamName, teamSize) = team
    println("\nTeam: $team")
    println("Team Name: $teamName, Size: $teamSize")
}
Output:
Descriptions:
Person: Dave
Employee: Krishna, Salary: $60000
Employee: Ram, Salary: $55000

Salary Map: {Krishna=60000, Ram=55000, Kristal=70000}
High Earners: [Kristal]

Is 0 valid salary? false
Group: Community

Team: Team(name=Devs, size=3)
Team Name: Devs, Size: 3

Description: Collections: Uses MutableList for employees, Map for salaries, Set for high earners. Classes/Objects: Person and Employee with objects created. init Block: Validates salary in Employee. Instance/Companion Variables: name (instance), GROUP (companion). Inheritance/Overriding: Employee inherits Person, overrides describe. Polymorphism: List<Person> holds mixed types; describe varies. Encapsulation: salary is private, accessed via getSalary. Companion Methods: createFromString and isValidSalary. Special Methods: Team data class with toString and componentN.

10. Common Mistakes and Best Practices

Common Mistakes:

Best Practices: