Reading — step 1 of 7
Learn
Kotlin's inheritance and interface semantics are tightened versions of Java's — final by default, single base class, multiple interfaces, default-method support.
Open class for inheritance
Unlike Java, Kotlin classes are final (closed) by default. Mark open to allow subclassing:
open class Animal(val name: String) {
open fun speak(): String = "generic sound"
fun describe() = "I am $name" // final — can't override
}
class Dog(name: String, val breed: String) : Animal(name) {
override fun speak(): String = "woof"
}
val rex = Dog("Rex", "Husky")
rex.speak() // woof
rex.describe() // I am Rex
Notes:
openon the class allows it to be extendedopenon a method allows it to be overriddenoverrideis required when overriding (Kotlin enforces this — Java's @Override is optional, Kotlin's is mandatory): Animal(name)calls the parent's primary constructor — note the parens
abstract — must subclass
abstract class Shape {
abstract fun area(): Double // no body — must override
abstract val sides: Int
fun describe() = "$sides-sided shape with area $area"
}
class Circle(val radius: Double) : Shape() {
override fun area(): Double = 3.14 * radius * radius
override val sides: Int = 0
}
Abstract members are implicitly open. Abstract classes can't be instantiated directly.
Interfaces
Kotlin interfaces are like Java 8+ interfaces — methods can have default implementations:
interface Greeter {
val name: String
fun greet(): String = "Hi, $name" // default implementation
}
interface Loggable {
fun log() = println("[$this] logged")
}
class User(override val name: String) : Greeter, Loggable {
// greet() and log() are inherited
}
User("Ada").greet() // "Hi, Ada"
User("Ada").log() // "[...] logged"
A class can implement many interfaces. If two interfaces have conflicting default methods, the compiler errors and forces you to disambiguate:
interface A { fun foo() = "A" }
interface B { fun foo() = "B" }
class C : A, B {
override fun foo() = super<A>.foo() // pick A's
}
super<Type>.method qualifies which parent's method to call.
Property overriding
Properties from a superclass can be overridden:
open class Vehicle {
open val wheels: Int = 0
}
class Car : Vehicle() {
override val wheels: Int = 4
}
A val can be overridden as val, or as var (adding mutability). A var cannot be narrowed to val (would lose write access).
abstract vs interface — when to use which
- Interface — pure capability/contract, multiple inheritance allowed, no state (no init blocks, no class-managed properties beyond abstract ones)
- abstract class — partial implementation with state, single base, lifecycle hooks
Kotlin lets you mix interfaces freely; abstract classes are for genuine "is a kind of" hierarchies.
Visibility on overrides
An override can WIDEN visibility but not narrow it:
open class Base {
protected open fun helper() {}
}
class Sub : Base() {
public override fun helper() {} // OK — protected → public
}
Common mistakes
- Forgetting
open— class/method is final by default; subclass and override fail. - Forgetting
override— Kotlin requires it. Java made it optional. - Confusing
class Dog : Animalwithclass Dog : Animal()— without parens, you're declaring an interface implementation; with parens, calling the parent's constructor. - Diamond inheritance ambiguity — interface conflicts force
super<Type>.methoddisambiguation. The compiler tells you exactly what to do. - Trying to add state to an interface — interfaces can declare abstract properties but not store values. Use abstract class for stateful base.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…