What you'll learn
Real systems are full of things that are kinds of other things — a savings account is a bank account, a manager is an employee. Inheritance lets classes build on one another, and polymorphism lets you treat many types through one common interface. Together they make OOP powerful.
By the end you'll be able to:
- Share code between classes with
extends - Call parent constructors and methods with
super - Override inherited methods and mark them with
@Override - Use runtime polymorphism to write flexible code
- Check and convert types with
instanceofand casting - Design with abstract classes, and know when to prefer composition
Inheritance
Inheritance lets a subclass (child) reuse the fields and methods of a superclass (parent), then add or change behaviour. You declare it with extends. The subclass automatically gets everything non-private from the parent — no copy-pasting.
Animal
name · speak()
Each subclass inherits name & speak(), then specialises.
super & constructor chaining
The super keyword refers to the parent. super(...) calls the parent's constructor (it must be the first line of the child constructor), and super.method() calls the parent's version of a method. When you create a subclass object, constructors run top-down — parent first, then child. This is constructor chaining.
class Animal {
String name;
Animal(String name) { this.name = name; }
void speak() { System.out.println(name + " makes a sound"); }
}
class Dog extends Animal { // Dog inherits from Animal
Dog(String name) { super(name); } // call the parent constructor
@Override
void speak() { System.out.println(name + " barks"); }
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog("Rex");
d.speak(); // Dog's version runs
}
}Method overriding
A subclass can replace an inherited method by overriding it — same name, same parameters, new body. Always add the @Override annotation: it tells the compiler to verify you really are overriding something, catching typos before they become bugs.
Overriding vs overloading
Runtime polymorphism
Here's the magic. A parent-typed variable can hold any subclass object, and when you call an overridden method, Java runs the actual object's version — decided at runtime, not compile time. This is polymorphism ("many forms"), and it lets you write code against the general type while each object behaves specifically:
Animal[] animals = {
new Dog("Rex"),
new Cat("Milo"),
new Animal("Thing")
};
for (Animal a : animals) { // all treated as Animal...
a.speak(); // ...but each runs its OWN speak()
}Key idea
Animal subclass later and this loop keeps working unchanged. That flexibility is why polymorphism is so valued.instanceof & casting
Sometimes you need to know an object's real type or access subclass-specific members. The instanceof operator checks the type, and modern Java can bind a variable in the same step (pattern matching for instanceof), replacing a separate cast:
Animal a = new Dog("Rex");
if (a instanceof Dog d) { // pattern matching (Java 16+)
d.speak(); // d is already typed as Dog here
}
System.out.println(a instanceof Cat); // falseWatch out
(Cat) someDog) throws a ClassCastException at runtime. Always guard casts with instanceof first — or better, lean on polymorphism so you rarely need to cast at all.Abstract classes
Sometimes a base class represents a concept too general to instantiate — what is a plain "Shape" with no dimensions? An abstract class can't be instantiated and may declare abstract methods (no body) that every subclass is forced to implement, while still providing shared concrete code:
abstract class Shape {
abstract double area(); // no body: subclasses MUST implement
void describe() {
System.out.printf("Area = %.2f%n", area());
}
}
class Circle extends Shape {
double r;
Circle(double r) { this.r = r; }
@Override
double area() { return Math.PI * r * r; }
}
public class Main {
public static void main(String[] args) {
// Shape s = new Shape(); // ERROR: can't instantiate abstract
Shape s = new Circle(2);
s.describe();
}
}Composition vs inheritance
Inheritance is powerful but easy to overuse. A good check: does the relationship read as "is-a" or "has-a"? A Dog is-a Animal (inheritance fits). A Car has-a Engine — it's built from one, so composition (holding an Engine field) fits better.
Inheritance — "is-a"
Dog is-a Animal
Use when a subtype truly is a kind of the supertype.
Composition — "has-a"
Car has-a Engine
Use when an object is built from other objects. Often the better choice.
Tip
Recap & quick check
Key takeaways
- extends lets a subclass inherit and specialise a superclass (an 'is-a' relationship).
- super(...) calls the parent constructor; constructors chain parent-first.
- Overriding replaces an inherited method (mark it @Override); it's not overloading.
- Polymorphism: a parent-typed variable runs the actual object's overridden method at runtime.
- Abstract classes can't be instantiated and force subclasses to implement abstract methods; prefer composition when it's a 'has-a'.
Quick check
1. Which keyword makes one class inherit from another?
2. What does polymorphism let you do?
3. Why add @Override on an overriding method?
4. Can you create an object of an abstract class directly?
5. A Car built from an Engine object is an example of…
Excellent — you can now model rich hierarchies and write flexible, extensible code. Next up: Module 10 — Interfaces, Enums, Records & Nested Types.