Topic 60 of 119 6. Object-Oriented Programming in Java Interactive Tutorial

Inheritance in Java: Code Reuse, Types, and the extends Keyword

18 mins read Last updated: 22 Aug 2026 29,163 views

Direct Answer & Core Concept

Inheritance lets one class reuse and specialise another, written with the extends keyword:
Main.java
Run Online
public class Dog extends Animal {
}
A Dogis anAnimal, so any code that accepts an Animal accepts a Dog without knowing it exists. That substitutability is the point. Inheritance is a type relationship first and a code-reuse mechanism second. The part that surprises people is what is not inherited. Constructors are never inherited, so a subclass must call super or rely on the implicit call. Private members are inherited but not directly accessible, so they must be reached through a parent method. And a subclass field with the same name as a parent field hides the parent one rather than overriding it, which silently produces two separate fields. Java allows only one extends per class. Multiple inheritance of type is achieved with interfaces, which sidestep the diamond problem because a class never has to choose between two inherited implementations. The most important practical test is not technical. Ask whether the relationship is genuinely IS-A. If the child can be substituted for the parent everywhere without exception, inherit. If the child merely has the parent, use composition instead, because inheritance couples the two types permanently.

Detailed Explanation & Mechanics

1) extends Is a Type Relationship

The primary purpose of inheritance is substitutability, and code reuse is what falls out of it. That ordering matters, because designs driven by reuse alone tend to break.

Main.java
Run Online
public class Animal {
    public void speak() {
        System.out.println("...");
    }
}// Dog IS an Animal, so it may be used anywhere an Animal is expected
public class Dog extends Animal {
    @Override public void speak() {
        System.out.println("Woof");
    }
}
Main.java
Run Online
Animal any = new Dog();
any.speak();
// prints Woof, decided at runtime

The variable is typed as Animal, but the object is a Dog. Method dispatch uses the real object, which is the subject of the polymorphism topic.


Diagram showing single, multilevel, and hierarchical inheritance shapes against the three that Java rejects
Java permits three inheritance shapes for classes and rejects multiple inheritance of state.

2) The Three Shapes Java Allows

Single is one child with one parent, which is the ordinary case.

Main.java
Run Online
public class Dog extends Animal {
}

Multilevel forms a chain, and nothing is special about the middle class.

Main.java
Run Online
public class Puppy extends Dog {
}// Puppy inherits from Dog, which inherits from Animal

Hierarchical gives one parent several children, which is how most class libraries are shaped.

Main.java
Run Online
public class Dog extends Animal {
}public class Cat extends Animal {
}
ShapeStructureStatus in Java
SingleOne child, one parentAllowed
MultilevelA chain of parentsAllowed
HierarchicalOne parent, many childrenAllowed
MultipleOne child, two parentsClass types rejected
HybridMultiple plus multilevelClass types rejected

3) Object Is the Root of Everything

Every class in Java extends java.lang.Object implicitly, even with no extends clause. That is why getClass, toString, and equals work on any object.

Main.java
Run Online
// These two declarations are equivalent
public class Dog extends Animal {
}// Dog extends Animal extends Object

Because Object declares equals and toString, every class inherits them, which is why the default toString prints an unreadable identity hash.

Overriding equals without also overriding hashCode breaks every hash-based collection. Two objects that are equal by equals must produce identical hash codes, or a HashSet will hold both.

4) Constructors Are Not Inherited

A subclass gets its own constructors and must satisfy the parent by calling super, either explicitly or through the compiler-inserted no-argument call.

Main.java
Run Online
public class Vehicle {
    private final String model;
    public Vehicle(String model) {
        this.model = model;
    } public String getModel() {
        return model;
    }
}public class Car extends Vehicle {
    private final int doors;
    public Car(String model, int doors) {
        super(model);
        // mandatory, since Vehicle has no no-arg one
        this.doors = doors;
    }
}

So constructing a car with a model name and a door count runs the Vehicle constructor and then the Car constructor. The parent always completes first, as covered in the constructor chaining topic.

5) Private Members Are Inherited but Not Accessible

This distinction trips up almost everyone learning the topic. A private field is still part of the object the child receives; the child simply has no direct way to name it.

Main.java
Run Online
public class Account {
    private long balanceCents;
    // private to Account, yet inherited by Savings
    public long getBalanceCents() {
        return balanceCents;
    } protected void audit() {
        System.out.println("audit: " + balanceCents);
    }
}public class Savings extends Account {
    public void report() {
        // COMPILE ERROR: balanceCents has private access in Account
        System.out.println(balanceCents);
        // WORKS, because the parent method may read its own field
        System.out.println(getBalanceCents());
    }
}

The protected modifier exists for exactly this case, letting a subclass reach a member without opening it to the whole package. The access modifiers topic covers the full ladder.

6) Hiding a Field Is Not Overriding It

A subclass field with the same name as a parent field does not override anything. It creates a second, separate field, and the parent version continues to exist.

Main.java
Run Online
public class Parent {
    protected int value = 1;
}public class Child extends Parent {
    protected int value = 2;
    // HIDES Parent.value, does not replace it
    public void printBoth() {
        System.out.println(value);
        // 2, the child field
        System.out.println(super.value);
        // 1, the parent field
    }
}

Both fields occupy space in the same object, which wastes memory and produces confusing bugs when a method inherited from the parent reads the parent value while the caller set the child one. Prefer a differently named field or a method, and never rely on hiding.

Hiding is also how static members are dealt with, but there the parent method is called rather than shadowed, so the two remain distinct.

7) Overriding Requires the Right Signature

An override needs an identical signature and a return type that is the same or narrower. Anything else is a new method that hides or overloads instead.

Main.java
Run Online
public class Parent {
    public void show() {
    } public int count() {
        return 0;
    }
}public class Child extends Parent {
    @Override public void show() {
        // correct override
    } @Override public void show(String who) {
        // NOT an override, it is an overload
    } public long count() {
        // NOT an override, wider return type
        return 0L;
    }
}

Always add the @Override annotation. It is not required, but it turns a silent mistake into a compile error, which matters most when a signature is later renamed in the parent.

Static methods are hidden rather than overridden, and private methods cannot be overridden at all because the child cannot see them. Both are covered in the method overriding topic.

8) Why Classes Cannot Inherit From Two Parents

If both parents supplied an implementation of the same method, the compiler would have to decide which one wins, with no rule to make that choice. Java removes the ambiguity by allowing only one parent class.

Main.java
Run Online
// NOT ALLOWED
public class Hybrid extends Car, Vehicle {
}

Interfaces solve this differently. An interface contributes behaviour but not state, so two interfaces declaring the same method pose no storage conflict.

Main.java
Run Online
public interface Closeable {
    void close();
}public interface Flushable {
    void flush();
}// ALLOWED, because both contribute behaviour only
public class Resource implements Closeable, Flushable {
    @Override public void close() {
    } @Override public void flush() {
    }
}

A class still extends exactly one class while implementing as many interfaces as it needs, which is how Java achieves the benefits of both models without the diamond problem.

9) final and sealed Control Extension

Marking a class final forbids any subclass, which protects invariants that would otherwise be broken by an override.

Main.java
Run Online
public final class Money {
    private final long amountInCents;
    public Money(long amountInCents) {
        this.amountInCents = amountInCents;
    }
}// COMPILE ERROR: cannot extend a final class
public class DiscountedMoney extends Money {
}

A final method is not overridable but the class may still be extended. Newer Java adds sealed, which permits extension only by classes you name explicitly, giving a closed but extensible hierarchy.

Main.java
Run Online
public sealed class Shape permits Circle, Square {
}public final class Circle extends Shape {
}public final class Square extends Shape {
}

This is a meaningful middle ground between open inheritance and final, and it lets the compiler check exhaustiveness in pattern-matching switch. The final keyword topic covers both in detail.

10) Upcasting and instanceof

Assigning a child to a parent-typed variable is called upcasting, and it is always safe because the child satisfies every contract of the parent.

Main.java
Run Online
Animal any = new Dog();
// upcast, always allowed
Dog specific =(Dog) any;
// downcast, checked at runtime

A downcast fails at runtime if the object is not actually of that type, which is what instanceof tests before you attempt one.

Main.java
Run Online
if (any instanceof Dog) {
    Dog specific =(Dog) any;
    System.out.println(specific.getBreed());
}

Modern Java shortens this with pattern matching, which binds the variable and removes the separate cast.

Main.java
Run Online
if (any instanceof Dog dog) {
    System.out.println(dog.getBreed());
}

11) Inheritance or Composition?

This is the decision that matters most, and the IS-A test settles it more often than people expect.

SituationRelationshipUse
A Car is a VehicleIS-AInheritance
A Car has an EngineHAS-AComposition
A Stack has a ListHAS-AComposition
An Employee is a ManagerNot reallyNeither

Inheritance couples parent and child permanently: every change to the parent affects the child, and the child can override behaviour the parent depends on. Composition keeps them independent, and swapping the collaborator is a constructor argument rather than a code change.

Prefer composition by default. Use inheritance when the substitutability requirement is real, when the parent is genuinely under your control, and when you want the child to be usable anywhere the parent is expected.

12) Common Misconceptions

ClaimVerdict
Constructors are inheritedFalse. Each class declares its own.
Private members are not inheritedFalse. They are, but not directly accessible.
A same-named field is overriddenFalse. It hides, leaving two fields.
Any same-named method is an overrideFalse. The signature must match.
Inheritance is for code reusePartly. It is a type relationship first.
Java supports multiple inheritanceOnly of type, through interfaces.
Interfaces exist to reduce couplingFalse. They enable polymorphism.
Downcasting is checked at compile timeFalse. It fails at runtime.

Frequently Asked Questions

What exactly does extends inherit?


Instance fields and methods, including their access modifiers, subject to reachability. Constructors are never inherited, static methods are hidden rather than overridden, and private members are inherited into the object but cannot be named by the subclass. Everything else follows the normal visibility rules.

Why does Java not allow multiple inheritance of classes?


Because two parents could each supply an implementation of the same method, and the compiler would have no principled way to choose. Java offers multiple inheritance of type through interfaces instead, which sidesteps the conflict because interfaces contribute behaviour rather than stored state.

Is hiding a field the same as overriding a method?


No, and the difference is significant. Hiding creates a second field in the same object, so both values occupy memory and the parent version is unaffected. Overriding replaces one implementation with another. Hiding the same name is almost always a mistake, so prefer a distinct name or a method.

What is the root superclass of every Java class?


java.lang.Object, applied implicitly even without an extends clause. It is the source of methods such as getClass, toString, and equals, which is why toString prints an identity hash unless you override it.

When should I prefer composition over inheritance?


Whenever the relationship is HAS-A rather than IS-A, and whenever you would otherwise need multiple parents, since composition takes a constructor argument instead. Inheritance permanently couples the two types and lets a child override behaviour the parent relies on, so composition is the safer default.

Does a subclass see private fields of its parent?


The fields are present in the object, but the subclass cannot name them directly. It must reach them through a parent method, or the parent can widen access to protected for exactly this purpose. What matters is that private means private to the declaring class, not to the object.

Can a final class be extended?


No, and that is the point. A final class forbids extension entirely, while a final method forbids only overriding, so the class remains extensible. Sealed classes offer a middle ground, permitting only the subclasses you list explicitly.

What is upcasting and is it safe?


Upcasting is assigning a child instance to a parent-typed variable, and it is always safe at compile time because the child satisfies the parent contract. The reverse, downcasting, is checked at runtime and throws a ClassCastException unless the object really is that subtype.

Runnable Code Example: Inheritance in Java

Java 17+
Main.java
Run Online
// Shows what extends inherits, what it withholds, and what field hiding costs.
public class InheritanceDemo {
    private static final int PARENT_VALUE = 1;
    private static final int CHILD_VALUE = 2;
    private static final String DEFAULT_MODEL = "mini";
    private static final int DEFAULT_DOORS = 4;
    private static final String NOT_A_DOG = "that object is not a Dog";
    // EVERY CLASS EXTENDS Object IMPLICITLY, so it is here in the chain
    public static class Animal {
        protected final String name;
        public Animal(String name) {
            this.name = name;
        } public String getName() {
            return name;
        } public void speak() {
            System.out.println("generic noise from " + name);
        }
    } // A SINGLE INHERITANCE RELATIONSHIP
    public static class Dog extends Animal {
        private static final int LEG_COUNT = 4;
        private final String breed;
        public Dog(String name, String breed) {
            super(name);
            // constructors are NOT inherited
            this.breed = breed;
        } public int getLegCount() {
            return LEG_COUNT;
        } public String getBreed() {
            return breed;
        } // A REAL OVERRIDE, the signature matches the parent exactly
        @Override public void speak() {
            System.out.println(name + " says woof");
        }
    } // A MULTILEVEL CHAIN, Puppy inherits through Dog
    public static class Puppy extends Dog {
        public Puppy(String name, String breed) {
            super(name, breed);
        } @Override public void speak() {
            System.out.println(name + " says yip");
        }
    } // A PARENT WITH PRIVATE STATE, INHERITED BUT NOT DIRECTLY REACHABLE
    public static class Account {
        private long balanceCents;
        public Account(long balanceCents) {
            this.balanceCents = balanceCents;
        } public long getBalanceCents() {
            return balanceCents;
        } public void deposit(long cents) {
            this.balanceCents = this.balanceCents + cents;
        }
    } public static class Savings extends Account {
        private final double rate;
        public Savings(long balanceCents, double rate) {
            super(balanceCents);
            this.rate = rate;
        } public double getRate() {
            return rate;
        } public void summary() {
            // balanceCents is private in Account, so this would not compile
            System.out.println("via accessor: " + getBalanceCents());
        }
    } // FIELD HIDING, WHICH CREATES A SECOND FIELD INSTEAD OF REPLACING ONE
    public static class Parent {
        protected int value = PARENT_VALUE;
    } public static class Child extends Parent {
        protected int value = CHILD_VALUE;
        public void printBoth() {
            // Two distinct fields exist in this single object
            System.out.println("child value:  " + value);
            System.out.println("parent value: " + super.value);
        }
    } // THIS IS AN OVERLOAD, NOT AN OVERRIDE, because the signature differs
    public static class Base {
        public void show() {
            System.out.println("base show with no arguments");
        }
    } public static class Derived extends Base {
        @Override public void show() {
            System.out.println("derived show with no arguments");
        } public void show(String label) {
            System.out.println("derived show labelled " + label);
        }
    } // A CHILD WHOSE PARENT HAS NO NO-ARG CONSTRUCTOR
    public static class Vehicle {
        private final String model;
        private final int wheels;
        public Vehicle(String model, int wheels) {
            this.model = model;
            this.wheels = wheels;
        } public String getModel() {
            return model;
        } public int getWheels() {
            return wheels;
        } public String describeVehicle() {
            return model + " with " + wheels + " wheels";
        }
    } public static class Car extends Vehicle {
        public Car(String model, int wheels) {
            super(model, wheels);
            // required, Vehicle has no no-arg one
        } @Override public String describeVehicle() {
            return "a car, " + super.describeVehicle();
        }
    } public static void main(String[] args) {
        // AN IS-A RELATIONSHIP, THE CHILD WORKS ANYWHERE THE PARENT IS EXPECTED
        Animal any = new Dog("Rex", "collie");
        any.speak();
        // dispatches to Dog, printing woof
        Animal typed = new Puppy("Bit", "shepherd");
        typed.speak();
        // Puppy wins, three levels down
        // UPCASTING IS SAFE IN ONE DIRECTION
        Dog real = new Dog("Milo", "beagle");
        Animal up = real;
        System.out.println("upcast back: " +((Dog) up).getBreed());
        // DOWNCASTING IS CHECKED AT RUNTIME
        if (any instanceof Dog) {
            Dog narrowed =(Dog) any;
            System.out.println("narrowed breed: " + narrowed.getBreed());
        } try {
            Animal notADog = new Puppy("Ash", "husky");
            Object bad = notADog;
            Dog wrong =(Dog) bad;
            System.out.println(wrong.getBreed());
        } catch (ClassCastException problem) {
            // A Puppy IS a Dog, so this cast actually succeeds
            System.out.println("Puppy is still a Dog");
        } Animal trulyNotADog = new Animal("Generic");
        if (!(trulyNotADog instanceof Dog)) {
            System.out.println(NOT_A_DOG);
        } // MODERN PATTERN MATCHING BINDS THE VARIABLE AND REMOVES THE CAST
        Dog candidate = new Dog("Pip", "terrier");
        if (candidate instanceof Dog found) {
            System.out.println("pattern matched: " + found.getBreed());
        } // PRIVATE PARENT STATE IS REACHED THROUGH THE PARENT METHOD
        Dog counted = new Dog("Rex", "collie");
        System.out.println("a dog has " + counted.getLegCount() + " legs");
        Savings savings = new Savings(100000L, 0.04);
        savings.deposit(2500L);
        savings.summary();
        System.out.println("savings rate: " + savings.getRate());
        // FIELD HIDING LEAVES TWO SEPARATE FIELDS IN ONE OBJECT
        Child child = new Child();
        child.printBoth();
        // A SUBCLASS WITH ITS OWN CONSTRUCTOR MUST CALL super FIRST
        Vehicle car = new Car(DEFAULT_MODEL, DEFAULT_DOORS);
        System.out.println("car: " + car.describeVehicle());
        // THE OVERRIDDEN METHOD WINS, THE OVERLOADED ONE IS SIMPLY CALLED
        Derived derived = new Derived();
        derived.show();
        derived.show("with a label");
        Base asBase = derived;
        asBase.show();
        // still dispatches to the override
        /*
            ACTUAL OUTPUT:

                Rex says woof
                Bit says yip
                upcast back: beagle
                narrowed breed: collie
                Puppy is still a Dog
                that object is not a Dog
                pattern matched: terrier
                a dog has 4 legs
                car: a car, mini with 4 wheels
                via accessor: 102500
                savings rate: 0.04
                child value:  2
                parent value: 1
                derived show with no arguments
                derived show labelled with a label
                derived show with no arguments

            THE EIGHT THINGS TO REMEMBER
            1) INHERITANCE IS A TYPE RELATIONSHIP FIRST. Code reuse is a
               side effect, and designs based on reuse alone tend to
               break the moment substitutability fails.
            2) CONSTRUCTORS ARE NOT INHERITED. Each class declares its
               own, so a subclass must call super or accept the
               implicit no-argument call.
            3) PRIVATE MEMBERS ARE INHERITED BUT NOT REACHABLE. The
               field exists in the object, yet the subclass cannot
               name it, so use protected or a parent accessor.
            4) A SAME-NAMED FIELD HIDES RATHER THAN OVERRIDES, leaving
               two separate fields in one object and wasting memory.
            5) AN OVERRIDE NEEDS AN EXACT SIGNATURE. Add the
               @Override annotation so a mistake becomes a compile
               error instead of a silent new method.
            6) JAVA ALLOWS ONE PARENT CLASS ONLY, because two parents
               could supply conflicting implementations. Interfaces
               give multiple inheritance of type without the conflict.
            7) UPCASTING IS ALWAYS SAFE, since the child satisfies the
               parent contract. A downcast fails at runtime, so test
               with instanceof first.
            8) PREFER COMPOSITION UNLESS THE RELATIONSHIP IS IS-A. A
               car is a vehicle, but it has an engine, and only one
               of those is a reason to extend a class.
        */
    }
}

Common Java Technical Interview Questions & Answers

Interview Prep
Q1: Why does Java not support multiple inheritance of classes?\nA1: Because two parent classes could each supply an implementation of the same method, and the compiler would have no principled way to choose between them. Java offers multiple inheritance of type through interfaces instead, which avoids the conflict because interfaces contribute behaviour rather than stored state.\nQ2: What is the root superclass of every class in Java?\nA2: java.lang.Object, applied implicitly even when no extends clause appears. It is the source of methods such as getClass, toString, and equals, which is why toString prints an identity hash unless a class overrides it.\nQ3: Are constructors inherited by a subclass?\nA3: No. Constructors are never inherited, so each class declares its own. A subclass constructor must call super explicitly or rely on the compiler-inserted call to the parent no-argument constructor, and fails at compile time if no such constructor exists.\nQ4: Can a subclass access a private field of its parent?\nA4: Not directly. The field is inherited into the object, but private means accessible only within the declaring class, so the subclass must reach it through a parent method or the parent must widen access to protected. What is private to a class is not private to an instance.\nQ5: What is the difference between hiding a field and overriding a method?\nA5: Hiding creates a second field in the same object, so both values occupy memory and the parent field is untouched. Overriding replaces a single implementation. Hiding a field under the same name as a parent field is almost always an accidental bug rather than an intentional design.\nQ6: When should you choose composition over inheritance?\nA6: Whenever the relationship is HAS-A rather than IS-A, and whenever multiple parents would otherwise be needed, since composition passes the collaborator as a constructor argument. Inheritance couples the types permanently and allows a child to override behaviour the parent relies on, so composition is the safer default.\nQ7: Is upcasting safe and how does downcasting differ?\nA7: Upcasting, assigning a child to a parent-typed variable, is always safe at compile time because the child satisfies the parent contract. Downcasting is verified at runtime and throws a ClassCastException unless the object truly is that subtype, so test with instanceof first or use a pattern-matching instanceof.\nQ8: Does extending a class guarantee code reuse?\nA8: No. Inheritance is primarily a type relationship that permits polymorphism, and reuse is a consequence. A subclass inherits fields and methods subject to visibility, but constructors are excluded, private members are unreachable by name, and a same-named field hides rather than replaces the parent one.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Inherit only when the relationship is genuinely IS-A and substitutable everywhere.\nDefault to composition, since it keeps types independent and swappable.\nAdd the @Override annotation to every overriding method.\nAvoid hiding fields, since two fields in one object waste memory and confuse.\nUse protected rather than private when a subclass genuinely needs access.\nNever assume the parent constructor runs safely, always call super deliberately.\nKeep hierarchies shallow, because deep chains are hard to follow and debug.\nPrefer interfaces for multiple inheritance of type rather than multiple parents.\nMark classes final when extension would break their invariants.\nConsider sealed when a hierarchy should be closed but not fully frozen.\nUse super.field only when you deliberately mean the parent version.\nDo not hide static methods expecting polymorphic behaviour.\nAvoid protected fields, since they become part of the subclass contract.\nKeep inherited members consistent with the is-a relationship.\nTest that overrides still honour the contract the parent relies on.\nRemember that equals requires a matching hashCode in subclasses.\nFavour composition for wrappers around collections and resources.
We value your privacy & transparency

OperateTools uses cookies and local storage to ensure basic tool functionality, preserve preferences, and analyze aggregated traffic. We never sell your personal data or index your document files. Learn more in our Privacy Policy.