Inheritance: Standing on the Shoulders of Giants
What is it? (The Analogy)
You know how in a family, children inherit traits from their parents? Maybe you got your mom's eye color and your dad's height. You did not have to "build" those traits from scratch -- you were BORN with them. But you also developed your OWN unique traits that your parents do not have: maybe you can juggle, or you speak a language they do not. You inherited the basics and added your own flavor on top.
Inheritance in Java works the same way. A subclass (child) automatically gets all the non-private fields and methods from its superclass (parent). The child does not have to rewrite all that code -- it just gets it for free. And then the child can add its OWN unique fields and methods, or even override inherited methods to do things differently. A German Shepherd IS-A Dog. A Dog IS-A Animal. Each level inherits from above and adds its own specialization.
Here is the real power: if you have code that works with the parent type, it AUTOMATICALLY works with all child types too. A method that accepts an \Animal\ can receive a \Dog\, a \Cat\, or a \Parrot\. This is the foundation of polymorphism (next lesson!), and it is what makes large-scale Java programs manageable.
Why do we need it?
Without inheritance, you end up with massive code duplication. Look at this nightmare:
1// WITHOUT inheritance -- copy-paste horror!
2public class Dog {
3 String name;
4 int age;
5 double weight;
6
7 void eat() { System.out.println(name + " is eating."); }
8 void sleep() { System.out.println(name + " is sleeping."); }
9 void bark() { System.out.println(name + " says: Woof woof!"); }
10}
11
12public class Cat {
13 String name; // DUPLICATED from Dog!
14 int age; // DUPLICATED!
15 double weight; // DUPLICATED!
16
17 void eat() { System.out.println(name + " is eating."); } // DUPLICATED!
18 void sleep() { System.out.println(name + " is sleeping."); } // DUPLICATED!
19 void purr() { System.out.println(name + " says: Purrrr..."); }
20}
21
22public class Parrot {
23 String name; // DUPLICATED AGAIN!
24 int age; // DUPLICATED AGAIN!
25 double weight; // DUPLICATED AGAIN!
26
27 void eat() { System.out.println(name + " is eating."); } // DUPLICATED AGAIN!
28 void sleep() { System.out.println(name + " is sleeping."); } // DUPLICATED AGAIN!
29 void talk() { System.out.println(name + " says: Polly wants a cracker!"); }
30}Three classes, and \name\, \age\, \weight\, \eat()\, \sleep()\ are copy-pasted in EVERY ONE. Now imagine 20 animal types. If you want to change how \eat()\ works, you have to change it in 20 places. Miss one? Bug. This is a maintenance disaster.
With inheritance, the shared code lives in ONE place:
1// WITH inheritance -- shared code in the parent!
2public class Animal {
3 String name;
4 int age;
5 double weight;
6
7 void eat() { System.out.println(name + " is eating."); }
8 void sleep() { System.out.println(name + " is sleeping."); }
9}
10
11public class Dog extends Animal {
12 void bark() { System.out.println(name + " says: Woof woof!"); }
13}
14
15public class Cat extends Animal {
16 void purr() { System.out.println(name + " says: Purrrr..."); }
17}Dog and Cat inherit \name\, \age\, \weight\, \eat()\, and \sleep()\ automatically. No duplication!
How it works -- Step by Step
- Identify common behavior -- Look at your classes and find the shared fields and methods.
- Create a superclass -- Put the common stuff in a parent class.
- **Use \
extends\** -- Child classes use \extends ParentClass\to inherit from the parent. - Add specialized behavior -- Each child adds its own unique methods and fields.
- Override methods if needed -- Use \
@Override\to change inherited behavior in the child. - **Use \
super\** -- Call the parent's constructor with \super(...)\or parent methods with \super.methodName()\.
What IS inherited:
- Public and protected methods
- Public and protected fields
- Default (package-private) methods/fields (if in same package)
What is NOT inherited:
- Private fields and methods (they EXIST in the object but cannot be accessed by the child)
- Constructors (they are NOT inherited, but can be called with \
super()\)
Let's Build It Together
Let's build a music instrument hierarchy!
1// The SUPERCLASS -- shared behavior for all instruments
2public class Instrument {
3 private String name;
4 private String brand;
5 private double price;
6
7 public Instrument(String name, String brand, double price) {
8 this.name = name;
9 this.brand = brand;
10 this.price = price;
11 }
12
13 // This method CAN be overridden by children
14 public void play() {
15 System.out.println("Playing " + name + "...");
16 }
17
18 public void tune() {
19 System.out.println("Tuning " + name + "...");
20 }
21
22 // Getters
23 public String getName() { return name; }
24 public String getBrand() { return brand; }
25 public double getPrice() { return price; }
26
27 @Override
28 public String toString() {
29 return brand + " " + name + " ($" + price + ")";
30 }
31}1// SUBCLASS #1 -- Guitar inherits EVERYTHING from Instrument
2public class Guitar extends Instrument {
3 private int numberOfStrings;
4 private String guitarType; // "Acoustic", "Electric", "Bass"
5
6 public Guitar(String brand, double price, int strings, String type) {
7 // MUST call parent constructor FIRST with super()
8 super("Guitar", brand, price);
9 this.numberOfStrings = strings;
10 this.guitarType = type;
11 }
12
13 // OVERRIDING the parent's play() method
14 @Override
15 public void play() {
16 // We can still call the parent version with super!
17 super.play(); // prints "Playing Guitar..."
18 System.out.println("*strums " + numberOfStrings + " strings on "
19 + guitarType + " guitar*");
20 }
21
22 // Guitar-specific method -- not in the parent
23 public void shred() {
24 System.out.println("EPIC GUITAR SOLO on the " + getBrand()
25 + " " + guitarType + "! ๐ธ");
26 }
27}1// SUBCLASS #2 -- Piano
2public class Piano extends Instrument {
3 private int numberOfKeys;
4 private boolean isGrand;
5
6 public Piano(String brand, double price, int keys, boolean isGrand) {
7 super("Piano", brand, price);
8 this.numberOfKeys = keys;
9 this.isGrand = isGrand;
10 }
11
12 @Override
13 public void play() {
14 String type = isGrand ? "grand" : "upright";
15 System.out.println("Playing a beautiful melody on the " + type
16 + " piano with " + numberOfKeys + " keys...");
17 }
18
19 public void sustain() {
20 System.out.println("*presses sustain pedal* Notes ring out beautifully...");
21 }
22}1// SUBCLASS #3 -- Drums
2public class Drums extends Instrument {
3 private int numberOfPieces;
4
5 public Drums(String brand, double price, int pieces) {
6 super("Drums", brand, price);
7 this.numberOfPieces = pieces;
8 }
9
10 @Override
11 public void play() {
12 System.out.println("BOOM! BAP! CRASH! Playing a " + numberOfPieces
13 + "-piece " + getBrand() + " drum kit!");
14 }
15
16 public void drumRoll() {
17 System.out.println("*brrrrrrrrrrrrr* DRUM ROLL!");
18 }
19}1public class MusicDemo {
2 public static void main(String[] args) {
3 Guitar guitar = new Guitar("Fender", 999.99, 6, "Electric");
4 Piano piano = new Piano("Steinway", 45000.00, 88, true);
5 Drums drums = new Drums("Pearl", 2500.00, 5);
6
7 // Inherited method -- works on all!
8 guitar.tune(); // "Tuning Guitar..."
9 piano.tune(); // "Tuning Piano..."
10 drums.tune(); // "Tuning Drums..."
11
12 // Overridden method -- each plays differently!
13 guitar.play(); // "Playing Guitar..." + strumming
14 piano.play(); // "Playing a beautiful melody..."
15 drums.play(); // "BOOM! BAP! CRASH!..."
16
17 // Child-specific methods
18 guitar.shred(); // EPIC GUITAR SOLO!
19 piano.sustain(); // *presses sustain pedal*
20 drums.drumRoll(); // *brrrrrrrrrrr*
21
22 // Inherited toString() works too
23 System.out.println(guitar); // "Fender Guitar ($999.99)"
24 }
25}Visual Mental Model
1 +-------------------+
2 | Instrument | <-- SUPERCLASS (parent)
3 |-------------------|
4 | - name |
5 | - brand | The shared "DNA"
6 | - price | that all children
7 |-------------------| inherit
8 | + play() |
9 | + tune() |
10 | + getName() |
11 +-------------------+
12 / | \
13 / | \ extends
14 / | \
15 +-----------+ +-----------+ +-----------+
16 | Guitar | | Piano | | Drums |
17 |-----------| |-----------| |-----------|
18 |- strings | |- keys | |- pieces | Each child
19 |- type | |- isGrand | | | adds its
20 |-----------| |-----------| |-----------| OWN fields
21 |+ play() * | |+ play() * | |+ play() * | and methods
22 |+ shred() | |+ sustain()| |+ drumRoll()|
23 +-----------+ +-----------+ +-----------+
24
25 * = overrides parent methodConstructor Chaining:
1 new Guitar("Fender", 999.99, 6, "Electric")
2 |
3 +---> Guitar constructor runs
4 |
5 +---> super("Guitar", "Fender", 999.99)
6 |
7 +---> Instrument constructor runs FIRST
8 | (sets name, brand, price)
9 |
10 +---> then Guitar constructor continues
11 (sets numberOfStrings, guitarType)Real-World Analogy Recap
Inheritance is like a family recipe that gets passed down through generations. Your grandmother made the original tomato sauce recipe (the superclass). Your mother inherited that recipe and added her own twist -- maybe extra garlic (that is overriding). You inherited your mother's version and added fresh basil (more overriding). The core recipe flows down, but each generation can customize it. You did not start from scratch -- you built on what came before.
Common Mistakes & Gotchas
- **Forgetting \
super()\in constructors**: If the parent has no no-arg constructor, you MUST explicitly call \super(...)\as the FIRST line of the child's constructor. The compiler will yell at you if you forget. - Overloading vs Overriding confusion: Overriding = same method name AND same parameters (replaces parent behavior). Overloading = same name but DIFFERENT parameters (adds a new version). These are VERY different!
- **Using \
@Override\annotation**: Always use it when overriding. It protects you -- if you accidentally misspell the method name or change the parameters, the compiler catches it. - Inheritance depth abuse: Do not create chains like Animal -> Mammal -> DomesticAnimal -> Pet -> Dog -> GoldenRetriever. Deep hierarchies are fragile and confusing. Keep it shallow (2-3 levels max).
- Java has single inheritance: A class can only extend ONE parent. If you need to share behavior from multiple sources, use interfaces (coming in a later lesson!).
- Private members exist but are hidden: If the parent has a \
private int secret\, it IS in the child object's memory, but the child's code cannot access it directly. Use protected or provide a getter.
Interview Tip
Interviewers commonly ask: "What is the difference between method overriding and method overloading?" Nail it: "Overloading is compile-time polymorphism -- same method name, different parameter lists, resolved at compile time. Overriding is runtime polymorphism -- same method signature in parent and child, resolved at runtime based on the actual object type. Overriding requires the \@Override\ annotation (by convention) and the IS-A relationship."
Quick Quiz
- If Instrument has a private field \
price\, can Guitar directly access \this.price\? If not, how can Guitar read or modify it? - What happens if you do NOT call \
super()\in a child constructor and the parent does NOT have a no-argument constructor? - Can a Guitar object call \
tune()\even though Guitar never defines a \tune()\method? Why?
Summary -- Key Takeaways
- Inheritance allows a subclass to reuse fields and methods from a superclass, eliminating code duplication (DRY principle).
- Use \
extends\to create a subclass. Use \super()\to call the parent constructor and \super.method()\to call parent methods. - Method overriding lets a child replace inherited behavior. Always use the \
@Override\annotation. - Java supports single inheritance only -- one parent per class. Constructor chaining ensures parents are initialized before children.