On this page23 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Is Method Overriding?
- 2. The @override Annotation
- 3. Which Version Runs?
- 4. Overriding a Getter
- 5. Overriding toString()
- 6. Overriding ==
- 7. Why == Alone Breaks Sets and Maps
- 8. Overriding hashCode
- 9. Signature Rules
- 10. Calling the Parent's Version
- 11. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 60 — Method Overriding
| Category | 10 — Advanced OOP |
| Lecture | 060 of 140 |
| Estimated video length | 10 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
A child class inherits every method of its parent, exactly as written. Overriding lets the child replace a method with its own version — including
toString,==andhashCode.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.
Prerequisites
Students should already understand, from Lectures 001–059:
- classes, objects, constructors, and getters (Lectures 053–056)
extends, parent and child classes, and super parameters (Lecture 059)==versusidentical()(Lecture 018) andoperatormethods (Lecture 021)ischecks andObject(Lectures 010–011)- sets and maps (Lectures 047–049)
Connection With Previous Lectures
In Lecture 059, a Dog inherited everything from Animal. But what if the inherited
behavior is wrong for the child?
class Animal {
void speak() {
print('...');
}
}
class Dog extends Animal {}
void main() {
Dog().speak();
}...A dog should bark. Other inherited methods are quietly wrong too: print(student) shows
Instance of 'Student', and two identical students are not ==. Lecture 018 promised
we would teach our own classes what == means. This is that lecture.
Learning Objectives
By the end of this lecture you will be able to:
- Understand what method overriding is and why it exists
- Override methods and getters in a child class
- Use the
@overrideannotation and explain how it catches typos - Predict which version of a method runs
- Override
toString()to give objects readable output - Override
==andhashCodetogether, and explain why both are needed - Recognize the errors Dart gives for an override with the wrong signature
1. What Is Method Overriding?
Overriding means a child class writes its own version of a method it inherited. Same name, same parameters — new body.
Analogy — you inherit your parent's recipe book. You keep most recipes, but on one page you paste your own version of the curry. Anyone who opens your book gets your curry.
class Animal {
void speak() {
print('...');
}
}
class Dog extends Animal {
@override
void speak() {
print('Woof!');
}
}
void main() {
Animal().speak();
Dog().speak();
}...
Woof!The parent keeps its version. Only Dog objects use the new one.
2. The @override Annotation
@override is a label that says "I mean to replace an inherited member". The code runs
without it, but with it Dart checks your intention. Watch a typo:
class Animal {
void speak() {
print('...');
}
}
class Dog extends Animal {
@override
void speek() {
print('Woof!');
}
}
void main() {
Dog().speak();
}...It prints the parent's text: speek is a new method, not an override. dart analyze
(and your editor) catches it only because of @override:
warning - bin/main.dart:9:8 - The method doesn't override an inherited method. Try updating
this class to match the superclass, or removing the override annotation.
- override_on_non_overriding_memberWithout the annotation there is no warning at all.
3. Which Version Runs?
The object decides, not the variable's type. A child that does not override keeps the inherited version.
class Animal {
void speak() {
print('...');
}
}
class Dog extends Animal {
@override
void speak() {
print('Woof!');
}
}
class Fish extends Animal {}
void main() {
Animal pet = Dog();
pet.speak();
Fish().speak();
}Woof!
...pet is declared as an Animal, but the object inside is a Dog, so Dog's version runs.
We will build on this idea when we cover Polymorphism (Lecture 065).
4. Overriding a Getter
Getters (Lecture 056) are overridden the same way.
class Account {
final String owner;
Account(this.owner);
String get type => 'Basic account';
}
class SavingsAccount extends Account {
SavingsAccount(super.owner);
@override
String get type => 'Savings account';
}
void main() {
print(Account('Ali').type);
print(SavingsAccount('Sara').type);
}Basic account
Savings account5. Overriding toString()
Every class extends Object, even without extends, and inherits its toString(), which
print and interpolation call. The inherited version is not helpful:
class Student {
final String name;
final int marks;
Student(this.name, this.marks);
}
void main() {
print(Student('Ali', 85));
}Instance of 'Student'Override it to describe the object:
class Student {
final String name;
final int marks;
Student(this.name, this.marks);
@override
String toString() => 'Student($name, $marks)';
}
void main() {
var ali = Student('Ali', 85);
print(ali);
print('Top: $ali');
print([ali, Student('Sara', 92)]);
}Student(Ali, 85)
Top: Student(Ali, 85)
[Student(Ali, 85), Student(Sara, 92)]Lists print their elements with toString() too.
6. Overriding ==
The == inherited from Object asks "are these the same object?" — exactly like
identical() from Lecture 018. Two separate objects are never equal:
class Point {
final int x;
final int y;
Point(this.x, this.y);
}
void main() {
print(Point(1, 2) == Point(1, 2));
}falseTo compare by values, override operator ==. Its parameter must be Object — that is
the signature it inherits — so check the type with is first:
class Point {
final int x;
final int y;
Point(this.x, this.y);
@override
bool operator ==(Object other) =>
other is Point && x == other.x && y == other.y;
}
void main() {
print(Point(1, 2) == Point(1, 2));
print(Point(1, 2) == Point(2, 1));
}true
falseAfter other is Point, Dart promotes other to Point, so other.x is allowed.
7. Why == Alone Breaks Sets and Maps
That Point is only half finished. With the course's lint rules, dart analyze says:
info - bin/main.dart:7:17 - Missing a corresponding override of 'hashCode'. Try overriding
'hashCode' or removing '=='. - hash_and_equalsWhy? A set or map (Lectures 047–049) doesn't compare a value with every element. It first
asks for its hashCode — a number — and looks only among elements with that number.
Object's hashCode differs for every object:
class Student {
final String name;
final int roll;
Student(this.name, this.roll);
@override
bool operator ==(Object other) =>
other is Student && name == other.name && roll == other.roll;
}
void main() {
var a = Student('Ali', 7);
var b = Student('Ali', 7);
print(a == b);
print({a, b}.length);
print({a: 85}[b]);
print([a].contains(b));
}true
2
null
truea == b, yet the set keeps both and the map can't find b. The list works because it
uses only ==. The rule: equal objects must have equal hash
codes.
8. Overriding hashCode
hashCode is a getter. Build it from the same fields that == compares, using
Object.hash:
class Student {
final String name;
final int roll;
Student(this.name, this.roll);
@override
bool operator ==(Object other) =>
other is Student && name == other.name && roll == other.roll;
@override
int get hashCode => Object.hash(name, roll);
}
void main() {
var a = Student('Ali', 7);
var b = Student('Ali', 7);
print({a, b}.length);
print({a: 85}[b]);
}1
85Now the set keeps one student and the map finds the entry. Override == and hashCode
together, always, from final fields so the hash code never changes.
9. Signature Rules
An override must accept the same calls as the method it replaces. Adding a required parameter breaks that:
class Animal {
void speak() {
print('...');
}
}
class Dog extends Animal {
// @override
// void speak(int times) {} // ✗
}Error: The method 'Dog.speak' has more required arguments than those of overridden method
'Animal.speak'.The return type must match too (or be a subtype of it):
class Animal {
String sound() => '...';
}
class Dog extends Animal {
// @override
// int sound() => 1; // ✗
}Error: The return type of the method 'Dog.sound' is 'int', which does not match the return
type, 'String', of the overridden method, 'Animal.sound'.
Change to a subtype of 'String'.The easiest rule: copy the parent's first line exactly, then change only the body.
10. Calling the Parent's Version
Sometimes you want to add to the parent's method rather than replace it. Inside an
override, super.speak() runs the parent's version. We will study super fully in the
next lecture, Lecture 061 — super.
11. A Real-World Example
A shopping cart where duplicate products are ignored and the receipt reads well:
class Product {
final String name;
final int price;
Product(this.name, this.price);
String get label => name;
@override
String toString() => '$label: Rs $price';
@override
bool operator ==(Object other) =>
other is Product && name == other.name && price == other.price;
@override
int get hashCode => Object.hash(name, price);
}
class SaleProduct extends Product {
SaleProduct(super.name, super.price);
@override
String get label => '$name (SALE)';
}
void main() {
var cart = <Product>{
Product('Pen', 50),
SaleProduct('Bag', 900),
Product('Pen', 50),
};
for (var item in cart) {
print(item);
}
print('Items: ${cart.length}');
}Pen: Rs 50
Bag (SALE): Rs 900
Items: 2SaleProduct overrides only label, yet the parent's toString() uses the new label —
the object decides which version runs. The second Pen is equal to the first, so the set
keeps one.
Code Examples
Example 1 — Overriding a Method
class Vehicle {
void start() {
print('Starting...');
}
}
class Car extends Vehicle {
@override
void start() {
print('Engine on. Vroom!');
}
}
void main() {
Vehicle().start();
Car().start();
}Starting...
Engine on. Vroom!Example 2 — Two Children, Two Versions
class Animal {
String sound() => '...';
}
class Cat extends Animal {
@override
String sound() => 'Meow';
}
class Cow extends Animal {
@override
String sound() => 'Moo';
}
void main() {
var animals = [Cat(), Cow(), Animal()];
for (var a in animals) {
print(a.sound());
}
}Meow
Moo
...Example 3 — Overriding a Getter
class Shape {
String get name => 'Shape';
}
class Circle extends Shape {
@override
String get name => 'Circle';
}
void main() {
Shape s = Circle();
print(s.name);
}CircleExample 4 — toString() in Interpolation
class Book {
final String title;
final int pages;
Book(this.title, this.pages);
@override
String toString() => '$title ($pages pages)';
}
void main() {
var book = Book('Dart Basics', 120);
print('Reading: $book');
}Reading: Dart Basics (120 pages)Example 5 — == by Value
class Colour {
final String name;
Colour(this.name);
@override
bool operator ==(Object other) => other is Colour && name == other.name;
@override
int get hashCode => name.hashCode;
}
void main() {
print(Colour('red') == Colour('red'));
print(Colour('red') == Colour('blue'));
}true
falseWith only one field, that field's own hashCode is enough — no Object.hash needed.
Example 6 — Equal Objects in a Set
class Point {
final int x;
final int y;
Point(this.x, this.y);
@override
bool operator ==(Object other) =>
other is Point && x == other.x && y == other.y;
@override
int get hashCode => Object.hash(x, y);
}
void main() {
var visited = {Point(0, 0), Point(1, 2), Point(0, 0)};
print(visited.length);
print(visited.contains(Point(1, 2)));
}2
trueExample 7 — Student Marks
class Student {
final String name;
final int marks;
Student(this.name, this.marks);
String get grade => marks >= 80 ? 'A' : 'B';
@override
String toString() => '$name: $marks ($grade)';
}
class HonoursStudent extends Student {
HonoursStudent(super.name, super.marks);
@override
String get grade => marks >= 90 ? 'A+' : 'A';
}
void main() {
print(Student('Ali', 85));
print(HonoursStudent('Sara', 95));
}Ali: 85 (A)
Sara: 95 (A+)Example 8 — Shopping Total
class Item {
final String name;
final int price;
Item(this.name, this.price);
int get total => price;
}
class BulkItem extends Item {
final int quantity;
BulkItem(super.name, super.price, this.quantity);
@override
int get total => price * quantity;
}
void main() {
var cart = [Item('Notebook', 200), BulkItem('Pencil', 30, 10)];
var sum = 0;
for (var item in cart) {
print('${item.name}: ${item.total}');
sum += item.total;
}
print('Total: $sum');
}Notebook: 200
Pencil: 300
Total: 500Example 9 — Bank Balance
class Account {
final String owner;
int balance;
Account(this.owner, this.balance);
int get fee => 20;
void withdraw(int amount) {
balance -= amount + fee;
}
@override
String toString() => '$owner: Rs $balance';
}
class PremiumAccount extends Account {
PremiumAccount(super.owner, super.balance);
@override
int get fee => 0;
}
void main() {
var ali = Account('Ali', 1000);
var sara = PremiumAccount('Sara', 1000);
ali.withdraw(300);
sara.withdraw(300);
print(ali);
print(sara);
}Ali: Rs 680
Sara: Rs 700Example 10 — Temperature Readings as Map Keys
class City {
final String name;
final String country;
City(this.name, this.country);
@override
bool operator ==(Object other) =>
other is City && name == other.name && country == other.country;
@override
int get hashCode => Object.hash(name, country);
@override
String toString() => '$name, $country';
}
void main() {
var temperatures = {City('Lahore', 'PK'): 34.5};
temperatures[City('Karachi', 'PK')] = 31.0;
temperatures[City('Lahore', 'PK')] = 36.0;
for (var entry in temperatures.entries) {
print('${entry.key}: ${entry.value}°C');
}
}Lahore, PK: 36.0°C
Karachi, PK: 31.0°CThe second Lahore equals the first key, so it replaces the value.
Common Beginner Mistakes
1. A typo with no @override
A misspelled name silently adds a new method and the parent's still runs (Section 2).
2. Giving == a parameter of your own type
class Point {
final int x;
Point(this.x);
// @override
// bool operator ==(Point other) => x == other.x; // ✗
}Error: The parameter 'other' of the method 'Point.==' has type 'Point', which does not match
the corresponding type, 'Object', in the overridden method, 'Object.=='.The message also mentions a covariant keyword. You don't need it: take Object and check
with is.
3. Overriding == without hashCode
Comparisons look right, but sets keep duplicates and map lookups return null (Section 7).
4. Using different fields in == and hashCode
Equal objects can then get different hash codes. Use the same fields in both.
Best Practices
- Write
@overrideon every override — methods, getters,toString,==,hashCode. - Give classes you print a
toString()override. - Override
==andhashCodeas a pair, from the samefinalfields. - Build
hashCodewithObject.hash(...). - Start
==with anischeck on anObjectparameter. - Change only the body of an override, never its signature.
Mini Challenge
Create a class Temperature with a final double celsius. Override toString() to print
25.0°C, and override == and hashCode so equal readings count once. Put 25.0, 30.5
and 25.0 in a set and print the set and its length.
Your program should print exactly:
{25.0°C, 30.5°C}
2Try it yourself before opening the solution.
Show solution
class Temperature {
final double celsius;
Temperature(this.celsius);
@override
String toString() => '$celsius°C';
@override
bool operator ==(Object other) =>
other is Temperature && celsius == other.celsius;
@override
int get hashCode => celsius.hashCode;
}
void main() {
var readings = {Temperature(25.0), Temperature(30.5), Temperature(25.0)};
print(readings);
print(readings.length);
}{25.0°C, 30.5°C}
2Practice Questions
Work through these in order — they run from beginner to application to challenge.
Beginner
- Class
Birdhasvoid fly()printingFlying. CreatePenguin extends Birdthat overridesfly()to printPenguins can't fly. Callfly()on both. - Give class
Car(fieldsbrand,year) atoString()soprint(Car('Honda', 2020))showsHonda (2020). - Class
EmployeehasString get role => 'Employee'. CreateManageroverriding it to return'Manager'. Print the role of each. - Without running it: what does
print(Box(5) == Box(5));print ifBoxdoes not override==? Why?
Application
- Give class
Book(fieldisbn) an==andhashCodebased onisbn. PrintBook('111') == Book('111'), then the length of the set{Book('111'), Book('222'), Book('111')}. - Without running it: a class's
==comparesnameandroll, but itshashCodeisObject.hash(name, marks). What can go wrong when its objects go into a set? - Class
Tickethasint get price => 500.ChildTicketoverrides it to250. Store[Ticket(), ChildTicket(), ChildTicket()]and print the total price. - Class
Shapehasdouble get area => 0andtoString()returning'Area: $area'.Square(fieldside) overrides onlyarea. PrintSquare(3).
Challenge
- Class
Contact(name,phone) is equal when the phone matches, whatever the name. Override==,hashCodeandtoString()(name: phone). Build a map{Contact('Ali', '0300'): 'friend'}, then look upContact('Ali Khan', '0300'). - Class
Student(name,marks) hasbool get passed => marks >= 50.StrictStudentoverrides it to require70. PrintpassedforStudent('Ali', 60)andStrictStudent('Sara', 60), both stored in aList<Student>.
Practice Answers
Question 1 — Solution
class Bird {
void fly() {
print('Flying');
}
}
class Penguin extends Bird {
@override
void fly() {
print("Penguins can't fly");
}
}
void main() {
Bird().fly();
Penguin().fly();
}Flying
Penguins can't flyQuestion 2 — Solution
class Car {
final String brand;
final int year;
Car(this.brand, this.year);
@override
String toString() => '$brand ($year)';
}
void main() {
print(Car('Honda', 2020));
}Honda (2020)Question 3 — Solution
class Employee {
String get role => 'Employee';
}
class Manager extends Employee {
@override
String get role => 'Manager';
}
void main() {
print(Employee().role);
print(Manager().role);
}Employee
ManagerQuestion 4 — Solution
It prints false. Without an override, == comes from Object and checks whether both sides
are the same object. Box(5) twice creates two separate objects.
Question 5 — Solution
class Book {
final String isbn;
Book(this.isbn);
@override
bool operator ==(Object other) => other is Book && isbn == other.isbn;
@override
int get hashCode => isbn.hashCode;
}
void main() {
print(Book('111') == Book('111'));
print({Book('111'), Book('222'), Book('111')}.length);
}true
2Question 6 — Solution
Two students with the same name and roll but different marks are ==, yet get
different hash codes. The set looks in the wrong place and can keep both. hashCode must use
the same fields as ==.
Question 7 — Solution
class Ticket {
int get price => 500;
}
class ChildTicket extends Ticket {
@override
int get price => 250;
}
void main() {
var tickets = [Ticket(), ChildTicket(), ChildTicket()];
var total = 0;
for (var t in tickets) {
total += t.price;
}
print(total);
}1000Question 8 — Solution
class Shape {
double get area => 0;
@override
String toString() => 'Area: $area';
}
class Square extends Shape {
final double side;
Square(this.side);
@override
double get area => side * side;
}
void main() {
print(Square(3));
}Area: 9.0Question 9 — Solution
class Contact {
final String name;
final String phone;
Contact(this.name, this.phone);
@override
bool operator ==(Object other) => other is Contact && phone == other.phone;
@override
int get hashCode => phone.hashCode;
@override
String toString() => '$name: $phone';
}
void main() {
var groups = {Contact('Ali', '0300'): 'friend'};
print(groups[Contact('Ali Khan', '0300')]);
}friendQuestion 10 — Solution
class Student {
final String name;
final int marks;
Student(this.name, this.marks);
bool get passed => marks >= 50;
}
class StrictStudent extends Student {
StrictStudent(super.name, super.marks);
@override
bool get passed => marks >= 70;
}
void main() {
List<Student> students = [Student('Ali', 60), StrictStudent('Sara', 60)];
for (var s in students) {
print('${s.name}: ${s.passed}');
}
}Ali: true
Sara: falseQuick Revision
- What does overriding a method mean?
- What does
@overrideadd if the code runs without it? - Does the variable's type or the object decide which version runs?
- What does
printshow for an object whose class has notoString()override? - What does the inherited
==compare? - Why must
hashCodebe overridden together with==? - What type must the parameter of
operator ==have?
Key Takeaways
- Overriding replaces an inherited method or getter in a child class; the parent keeps its own version.
- Write
@overrideevery time — it turns a misspelled name into a warning. - The object decides which version runs, even through a parent-typed variable.
- Override
toString()soprintand interpolation show useful text. - The inherited
==checks identity; override it asbool operator ==(Object other)with anischeck to compare values. - Sets and maps use
hashCodefirst: equal objects must have equal hash codes. Override both, from the same fields, usingObject.hash. - An override keeps the parent's parameters and return type.
What's Next?
Lecture 61 — super. Overriding replaces a parent's method completely. Often you want to
keep the parent's work and add a little more. Next we will use super to call the parent's
methods, getters and constructors from inside the child.