Classes & Objects
Learn Kotlin class declarations, primary and secondary constructors, inheritance, interfaces, and the object and companion object keywords.
On this page 14 sections
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
finalby default — attempting to override a non-openmember is a compile error. - Looking for
static— Kotlin has no such keyword; use acompanion objector a top-level function instead. - Overusing
objectfor things that should just be an ordinary class — singletons are appropriate for genuinely shared, global state. - Putting mutable state in
valconstructor properties expecting immutability —valonly 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
opendeliberately, 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 objectfor 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
opento explicitly allow inheritance or overriding. - Interfaces can provide default method implementations and a class can implement many of them.
- There is no
statickeyword — usecompanion objectfor class-level members and factory functions. objectgives 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.