What is the difference between a trait and an abstract class?
← Classes and OOP · Ref: Q110
In EK9, traits and abstract classes both define contracts, but differ in state, multiplicity, and purpose. Traits provide pure behaviour. Abstract classes provide shared state and partial implementation.
KEY DIFFERENCES
State: Abstract classes can hold properties. Traits cannot hold mutable state.
Multiple: A class can implement many traits but extend only one abstract class.
Purpose: Traits define what a type CAN DO. Abstract classes define what a type IS.
WHEN TO USE EACH
Use a trait when: you want behaviour shared across unrelated classes, you need multiple implementations, or you want delegation support.
Use an abstract class when: you need shared state (properties), you need constructors, or you have a genuine type hierarchy.
CAN YOU COMBINE THEM
Yes. A class can extend an abstract class AND implement traits:
Car extends Vehicle with trait of Insurable, Trackable
This combines the state from Vehicle with behaviours from Insurable and Trackable.
DECISION GUIDE
Ask: does the contract need state? If yes, abstract class. Does the contract need to be mixed into multiple unrelated types? If yes, trait.
See Q103 for abstract classes. See Q106 for traits. See Q119 for constructs overview.
Example
defines module qa.oop.traitvsabstract defines trait Insurable premium() as pure <- rtn as Float: 100.0 override operator ? as pure <- rtn as Boolean: true Trackable location() as abstract <- rtn as String? override operator ? as pure <- rtn as Boolean: true defines class Vehicle as abstract make <- String() year <- Integer() Vehicle() -> make as String year as Integer this.make: make this.year: year describe() <- rtn as String: `${make} (${year})` speed() as abstract <- rtn as Float? default operator ? Car extends Vehicle with trait of Insurable, Trackable topSpeed <- 0.0 Car() -> make as String year as Integer topSpeed as Float super(make, year) this.topSpeed: topSpeed override speed() <- rtn as Float: topSpeed override premium() as pure <- rtn as Float: 200.0 override location() <- rtn as String: "garage" default operator ? Truck extends Vehicle payload <- 0.0 Truck() -> make as String year as Integer payload as Float super(make, year) this.payload: payload override speed() <- rtn as Float: 80.0 default operator ? defines program TraitVsAbstractDemo() stdout <- Stdout() // === ABSTRACT CLASS: shared state === car <- Car("Toyota", 2024, 130.0) stdout.println(`Car: ${car.describe()}, speed: ${car.speed()}`) // === TRAIT: behaviour contract === stdout.println(`Premium: ${car.premium()}`) stdout.println(`Location: ${car.location()}`) // === Abstract without trait === truck <- Truck("Ford", 2023, 5000.0) stdout.println(`Truck: ${truck.describe()}, speed: ${truck.speed()}`)
Common mistakes
E05120 — When implementing an abstract method from Vehicle, both Car and Truck must use 'override speed()'. Omitting 'override' triggers E05120. See ek9 -h E05120 for details.
Incorrect:
speed()
Correct:
override speed()
E05120 — When overriding a default trait method from Insurable, the 'override' keyword is mandatory. Car must use 'override premium()' to provide its custom implementation. See ek9 -h E05120 for details.
Incorrect:
premium() as pure
Correct:
override premium() as pure
E50060 — EK9 has no toString() method. Use the $ prefix operator or string interpolation for string conversion. See ek9 -h E50060 for details.
Incorrect:
stdout.println(car.toString())
Correct:
stdout.println(`Car: ${car.describe()}, speed: ${car.speed()}`)
E50060 — The method is speed(), not getSpeed(). EK9 uses descriptive method names without Java-style 'get' prefix. See ek9 -h E50060 for details.
Incorrect:
stdout.println(truck.getSpeed())
Correct:
stdout.println(`Truck: ${truck.describe()}, speed: ${truck.speed()}`)
Other ways to ask this
- When should I use a trait vs an abstract class in EK9?
- How do traits compare to abstract classes in EK9?
- Should I use a trait or an abstract base class?
Coming from another language?
Java: interfaces (no state) vs abstract classes (state + partial impl), single inheritance for classes. Python: ABCs for abstract, mixins for trait-like behaviour, multiple inheritance allowed. Rust: only traits exist, no abstract classes, traits can have default methods. Go: only interfaces, no abstract classes, no state in interfaces. Kotlin: interfaces with properties (no backing fields) vs abstract classes, similar trade-offs. EK9: traits for behaviour contracts (no mutable state), abstract classes for shared state and hierarchy, combinable on same class.
Keywords: combine, behaviour, decision, class, multiple, object-oriented, abstract, state, oop, difference, trait, hierarchy