Classes & Objects
Learn Kotlin class declarations, primary and secondary constructors, inheritance, interfaces, and the object and companion object keywords.
Introduction
Kotlin is a fully object-oriented language, but it trims away a lot of the ceremony Java requires to define a class. A class that would take a dozen lines in Java — fields, a constructor, getters, setters — often takes a single line in Kotlin.
This lesson covers declaring classes, the primary constructor, inheritance (which is opt-in via the `open` keyword), interfaces, visibility, and Kotlin's replacement for static members: the `object` and `companion object` keywords.
Declaring a Class
A class is declared with the `class` keyword. Creating an instance requires no `new` keyword, unlike Java — you simply call the class name like a function.
class Person { var name: String = "" var age: Int = 0}
fun main() { val p = Person() p.name = "Riya" p.age = 28 println("${p.name} is ${p.age} years old")}Click Run to see what this code prints.
Primary Constructor & Properties
Most Kotlin classes use a far more concise pattern: declaring properties directly in the primary constructor, right after the class name. Prefixing a constructor parameter with `val` or `var` turns it into a property automatically, generating the equivalent of a getter (and setter, for `var`) with no extra code.
class Person(val name: String, var age: Int)
fun main() { val p = Person("Riya", 28) println("${p.name} is ${p.age} years old")
p.age = 29 // var property — can be reassigned // p.name = "Other" // Compile error: name is a val println(p.age)}Click Run to see what this code prints.
Secondary Constructors & init Blocks
A class can define additional secondary constructors with `constructor`, each of which must ultimately delegate to the primary constructor. An `init` block runs as part of object construction, right after the primary constructor's properties are set — useful for validation logic.
class Rectangle(val width: Int, val height: Int) { val area: Int
init { require(width > 0 && height > 0) { "Dimensions must be positive" } area = width * height }
constructor(side: Int) : this(side, side) // secondary constructor for a square}
fun main() { val rect = Rectangle(4, 5) val square = Rectangle(3)
println("Rectangle area: ${rect.area}") println("Square area: ${square.area}")}Click Run to see what this code prints.
Inheritance with open
Kotlin classes are `final` (cannot be subclassed) by default — the opposite of Java. To allow a class (or a specific member) to be overridden, you must explicitly mark it `open`. This is a deliberate design decision: it forces you to consciously design a class for extension rather than allowing it accidentally.
open class Animal(val name: String) { open fun makeSound(): String = "..."}
class Dog(name: String) : Animal(name) { override fun makeSound(): String = "Woof!"}
fun main() { val animals: List<Animal> = listOf(Animal("Generic"), Dog("Rex")) for (a in animals) { println("${a.name}: ${a.makeSound()}") }}Click Run to see what this code prints.
Interfaces
Interfaces declare a contract of functions and properties a class must implement. Unlike a plain class hierarchy, a class can implement any number of interfaces, and Kotlin interfaces can even provide default method bodies.
interface Greeter { fun greet(): String = "Hello!" // default implementation}
class SpanishGreeter : Greeter { override fun greet(): String = "¡Hola!"}
fun main() { val g1: Greeter = SpanishGreeter() println(g1.greet())}Click Run to see what this code prints.
Visibility Modifiers
Kotlin has four visibility modifiers, applicable to classes and their members. `public` is the default and can be omitted.
| Modifier | Meaning |
|---|---|
| public (default) | Visible everywhere. |
| private | Visible only inside the same class/file. |
| protected | Visible inside the class and its subclasses. |
| internal | Visible anywhere inside the same module (e.g. the same Gradle project). |
object and Singletons
The `object` keyword declares a class with exactly one instance, created lazily the first time it is accessed — Kotlin's clean, built-in way of implementing the singleton pattern, which normally requires careful manual code in Java.
object AppConfig { var appName = "PrograMinds" fun printInfo() = println("App: $appName")}
fun main() { AppConfig.printInfo() // accessed directly, no instantiation needed AppConfig.appName = "Updated" AppConfig.printInfo()}Click Run to see what this code prints.
Companion Objects
Kotlin classes have no `static` keyword. Instead, a `companion object` declared inside a class holds members that belong to the class itself rather than to any instance — the closest equivalent to Java's static members, and also usable for factory-style construction functions.
class User private constructor(val name: String) { companion object { fun create(name: String): User { println("Creating user: $name") return User(name) } }}
fun main() { val user = User.create("Tariq") // called on the class itself, like a static method println(user.name)}Click Run to see what this code prints.
Common Mistakes
- Forgetting classes and functions are `final` by default — attempting to override a non-`open` member is a compile error.
- Looking for `static` — Kotlin has no such keyword; use a `companion object` or a top-level function instead.
- Overusing `object` for things that should just be an ordinary class — singletons are appropriate for genuinely shared, global state.
- Putting mutable state in `val` constructor properties expecting immutability — `val` only prevents reassignment of the reference, not mutation of a mutable object it points to.
Best Practices
- Use primary-constructor properties (`class Person(val name: String)`) instead of manually declaring fields and a separate constructor.
- Mark a class or member `open` deliberately, only when you intend for it to be extended or overridden.
- Prefer interfaces over base classes when you just need a shared contract, since a class can implement many interfaces but extend only one class.
- Reach for `companion object` for factory functions and constants tied to a class, and top-level functions for logic that isn't tied to any class at all.
Frequently Asked Questions
Unrestricted inheritance is a well-known source of fragile base class bugs. Kotlin requires you to opt in with `open`, which forces a deliberate decision about what is safe to extend.
Close, but not identical — a companion object is a real singleton object tied to the class, which means it can implement interfaces and be passed around as a value, something Java static members cannot do.
Yes. A Kotlin class can implement any number of interfaces but can only extend one open class, mirroring Java's single-inheritance model for classes.
Key Takeaways
- Primary-constructor properties (`class Person(val name: String)`) eliminate most manual field/constructor boilerplate.
- Classes and members are final by default in Kotlin; use `open` to explicitly allow inheritance or overriding.
- Interfaces can provide default method implementations and a class can implement many of them.
- There is no `static` keyword — use `companion object` for class-level members and factory functions.
- `object` gives you a built-in, thread-safe singleton with no extra boilerplate.
Summary
You now know how to model data and behavior with Kotlin classes, interfaces, inheritance, and singletons. Next, you'll tackle Kotlin's signature feature in full depth: null safety.