By the end of this module, you will be able to:
extends keyword to create a subclasssuper to call a parent constructor and parent methods@Override annotation correctly and explain its purposeObject class as the root of all Java classesInheritance is a mechanism that lets one class acquire the fields and methods of another. It models an "is-a" relationship — if you can truthfully say "A is a B", inheritance is likely the right tool.
Some examples:
Dog is a Animal ✅SavingsAccount is a BankAccount ✅Car is a Vehicle ✅Car is a Engine ❌ — a car has an engine, not is oneThe last example is important. "Has-a" relationships should use composition (a field), not inheritance. Choosing the wrong one is one of the most common OOP design mistakes.
🔍 You saw inheritance in JavaScript with
class Dog extends Animal. Java works the same way conceptually — the syntax is nearly identical, but Java enforces stricter rules around types and access that make the structure more explicit.
Without inheritance, you'd have to repeat shared fields and methods in every class:
// Without inheritance — duplicated code
public class Dog {
String name;
int age;
public void eat() { System.out.println("Eating..."); }
public void bark() { System.out.println("Woof!"); }
}
public class Cat {
String name; // duplicated
int age; // duplicated
public void eat() { System.out.println("Eating..."); } // duplicated
public void meow() { System.out.println("Meow!"); }
}
With inheritance, shared code lives in one place:
// With inheritance — shared code defined once
public class Animal {
String name;
int age;
public void eat() { System.out.println("Eating..."); }
}
public class Dog extends Animal {
public void bark() { System.out.println("Woof!"); }
}
public class Cat extends Animal {
public void meow() { System.out.println("Meow!"); }
}
extends KeywordUse extends to declare that one class inherits from another:
public class SubClass extends SuperClass {
// SubClass now has everything SuperClass has, plus its own additions
}
The class being extended is called the superclass (parent). The class doing the extending is called the subclass (child).
public class Animal { // superclass
String name;
int age;
public void eat() {
System.out.println(name + " is eating.");
}
}
public class Dog extends Animal { // subclass
public void bark() {
System.out.println(name + " says: Woof!"); // name inherited from Animal
}
}
Dog dog = new Dog();
dog.name = "Rex"; // inherited field
dog.age = 3; // inherited field
dog.eat(); // inherited method → Rex is eating.
dog.bark(); // own method → Rex says: Woof!
⚠️ Java supports single inheritance only — a class can extend at most one other class. This is different from some other languages (like C++) that allow multiple inheritance. Java addresses this limitation with interfaces, which you'll cover in the next module.
Not everything from the superclass is automatically available in the subclass:
| Member | Inherited? | Notes |
|---|---|---|
public fields |
✅ Yes | Directly accessible |
protected fields |
✅ Yes | Accessible in subclass |
public methods |
✅ Yes | Can be called and overridden |
protected methods |
✅ Yes | Can be called and overridden |
private fields |
❌ No | Exist in memory but not directly accessible — use getters |
private methods |
❌ No | Not accessible in subclass |
| Constructors | ❌ No | Must be defined separately in each class |
public class Animal {
private String name; // private — not directly accessible in subclass
protected int age; // protected — accessible in subclass
public String getName() { return name; } // public getter — inherited ✅
public void setName(String name) { this.name = name; }
}
public class Dog extends Animal {
public void describe() {
// System.out.println(name); // ❌ private — not accessible
System.out.println(getName()); // ✅ use the inherited getter
System.out.println(age); // ✅ protected — directly accessible
}
}
💡 This is why encapsulation and inheritance work together — making fields
privateand exposing them throughpublic/protectedmethods keeps the superclass in control of its own data even as subclasses extend it.
super Keyword — Calling the Parent ConstructorSince constructors are not inherited, each subclass must define its own. But often a subclass constructor needs to initialise the fields defined in the superclass. The super() call delegates this to the parent constructor.
public class Animal {
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
public class Dog extends Animal {
private String breed;
public Dog(String name, int age, String breed) {
super(name, age); // calls Animal(String, int) — must be first line
this.breed = breed;
}
public String getBreed() { return breed; }
}
Dog dog = new Dog("Rex", 3, "Labrador");
System.out.println(dog.getName()); // Rex
System.out.println(dog.getAge()); // 3
System.out.println(dog.getBreed()); // Labrador
Rules for super():
super() explicitly, Java inserts a no-arg super() automatically — but only if the superclass has a no-arg constructor. If it doesn't, you'll get a compile error.🔍 In JavaScript, you also call
super()at the top of a subclass constructor before usingthis. Java enforces the same rule — the difference is Java will refuse to compile if you forget.
super for Calling Parent Methodssuper isn't only for constructors. You can also use it to call a method from the superclass when you've overridden it in the subclass and still want to use the parent's version:
public class Animal {
public void describe() {
System.out.println("I am an animal.");
}
}
public class Dog extends Animal {
@Override
public void describe() {
super.describe(); // calls Animal's describe()
System.out.println("I am a dog."); // adds Dog-specific behaviour
}
}
Dog dog = new Dog();
dog.describe();
// I am an animal.
// I am a dog.
This is useful when you want to extend the parent's behaviour rather than completely replace it.
Overriding means redefining a method from the superclass in the subclass with the same signature (same name, same parameters, same return type) but different behaviour.
public class Animal {
public String makeSound() {
return "Some generic sound";
}
}
public class Dog extends Animal {
@Override
public String makeSound() {
return "Woof!";
}
}
public class Cat extends Animal {
@Override
public String makeSound() {
return "Meow!";
}
}
Animal a = new Animal();
Animal d = new Dog();
Animal c = new Cat();
System.out.println(a.makeSound()); // Some generic sound
System.out.println(d.makeSound()); // Woof!
System.out.println(c.makeSound()); // Meow!
Notice that d and c are declared as type Animal, but they run the Dog and Cat versions of makeSound(). This is polymorphism — the ability to treat different objects through a common type, and have each respond in its own way. You'll go deeper into this in the next module.
Rules for overriding:
private method — it's not visible to the subclassfinal method — final on a method means "no subclass may override this"public with private)@Override Annotation@Override is placed above a method to tell the compiler: "I intend this method to override one from the superclass."
@Override
public String makeSound() {
return "Woof!";
}
It is not required — the override happens with or without it. But it gives you two important benefits:
@Override, Java would silently create a new method instead of overriding, which is a very hard bug to spot.// Without @Override — typo goes undetected
public String makesound() { // lowercase 's' — not an override, a new method
return "Woof!";
}
// With @Override — compiler catches the typo immediately
@Override
public String makesound() { // ❌ Compile error: method does not override
return "Woof!";
}
💡 Always use
@Override. It costs nothing and prevents a category of bugs that are otherwise very difficult to diagnose.
These two concepts sound similar but are completely different:
| --- | --- | --- |
// Overloading — same class, different parameters
public class Calculator {
public int add(int a, int b) { return a + b; } // version 1
public double add(double a, double b) { return a + b; } // version 2
public int add(int a, int b, int c) { return a + b + c; } // version 3
}
// Overriding — subclass, identical signature
public class Animal {
public String makeSound() { return "..."; }
}
public class Dog extends Animal {
@Override
public String makeSound() { return "Woof!"; } // replaces parent's version
}
A useful memory aid: overloading = more options in the same class. Overriding = replacing in a subclass.
Object ClassEvery class in Java — including every class you have written so far — implicitly extends Object. It is the root of the entire Java class hierarchy.
public class Animal { ... }
// is exactly equivalent to:
public class Animal extends Object { ... }
Because every class inherits from Object, every object in Java automatically has these methods:
| --- | --- |
You've already used toString() — when you call System.out.println(myObject), Java automatically calls myObject.toString().
The default implementations are rarely useful, which is why you override them:
public class Animal {
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
// Default Object.toString() would print: Animal@1b6d3586
// Our override prints something meaningful:
@Override
public String toString() {
return "Animal{name='" + name + "', age=" + age + "}";
}
// Default Object.equals() checks reference equality (same as ==)
// Our override checks field equality:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Animal)) return false;
Animal other = (Animal) o;
return age == other.age && name.equals(other.name);
}
}
Animal a1 = new Animal("Rex", 3);
Animal a2 = new Animal("Rex", 3);
System.out.println(a1); // Animal{name='Rex', age=3}
System.out.println(a1.equals(a2)); // true — same content
System.out.println(a1 == a2); // false — different objects in memory
💡
equals()andhashCode()always go together — if you override one, you should override the other. You'll explore why in depth when you cover Collections in a later week. For now, focus ontoString()and understanding the role ofObject.
For each pair, decide whether the relationship is "is-a" (inheritance) or "has-a" (composition). Justify each answer in one sentence.
ElectricCar and CarCar and EngineManager and EmployeeStudent and AddressLaptop and DeviceOrder and Product