V
Vel·ToolKit
Simple · Fast · Ready to use
EN
Chapter 8 of 20

Inheritance & Polymorphism

extends, super, overriding, final, the Object trio, casting

extends: Single Inheritance

Java has single inheritance — a class can extends only one parent (but can implements multiple interfaces). A subclass automatically gets the parent's non-private members; it can add new members and override parent methods.

// Inherit.java
public class Inherit {
    public static void main(String[] args) {
        Dog d = new Dog("Rex", "Husky");
        d.eat();   // from Animal
        d.bark();  // from Dog
    }
}

class Animal {
    String name;
    Animal(String name) { this.name = name; }
    void eat() { System.out.println(name + " is eating"); }
}

class Dog extends Animal {
    String breed;
    Dog(String name, String breed) {
        super(name);     // call the parent constructor
        this.breed = breed;
    }
    void bark() { System.out.println(name + " (" + breed + ") barks"); }
}

super and the Constructor Chain

super(...) calls the parent constructor and must be the first line of the subclass constructor (if not called explicitly, the compiler inserts super() — which requires the parent to have a no-arg constructor). super.method() calls the overridden parent method.

// SuperDemo.java
public class SuperDemo {
    public static void main(String[] args) {
        new B().greet();
    }
}

class A {
    A() { System.out.println("A()"); }
    void greet() { System.out.println("Hi from A"); }
}

class B extends A {
    B() {
        super();              // auto-called even if omitted (requires A to have a no-arg constructor)
        System.out.println("B()");
    }
    @Override
    void greet() {
        super.greet();        // call the parent's greet
        System.out.println("Hi from B");
    }
}

Method Overriding and @Override

A subclass redefining a parent method with the same signature is "overriding". Strongly add the @Override annotation: if you mistype the signature, the compiler errors immediately instead of it becoming an unrelated new method.

// Override.java
public class Override {
    public static void main(String[] args) {
        new Square(3).describe();
    }
}

class Shape {
    double area() { return 0; }
    void describe() {
        System.out.println("area = " + area());
    }
}

class Square extends Shape {
    double side;
    Square(double side) { this.side = side; }

    @Override
    double area() { return side * side; }
}

Polymorphism: a Parent Reference to a Child Object

Java method dispatch is based on the runtime object type (dynamic dispatch): when a parent-type reference holds a child object, the call goes to the child's version. This is the core of object-oriented design.

// Polymorphism.java
public class Polymorphism {
    public static void main(String[] args) {
        Shape[] shapes = { new Circle(5), new Square(3) };
        for (Shape s : shapes) {
            System.out.println(s.area()); // whose area() runs? depends on the runtime type
        }
    }
}

class Shape {
    double area() { return 0; }
}
class Circle extends Shape {
    double r;
    Circle(double r) { this.r = r; }
    @Override double area() { return Math.PI * r * r; }
}
class Square extends Shape {
    double side;
    Square(double side) { this.side = side; }
    @Override double area() { return side * side; }
}

final Classes and final Methods

A final class cannot be subclassed (String, Integer, etc. are final). A final method cannot be overridden by a subclass. Used to guarantee certain behavior won't change.

// FinalDemo.java
public class FinalDemo {
    public static void main(String[] args) {
        System.out.println(new B().tag());
    }
}

class A {
    final String tag() { return "A"; }   // subclasses cannot override
}

class B extends A {
    // @Override String tag() { return "B"; } // compile error
}

// final class C forbids C from being subclassed
final class C {}

Upcasting / Downcasting

Upcasting (child→parent) is always safe and implicit; downcasting (parent→child) needs an explicit cast and may throw ClassCastException at runtime — check with instanceof first. Java 16+ pattern variables make this cleaner (see Chapter 3).

// Casting.java
public class Casting {
    public static void main(String[] args) {
        Animal a = new Dog();  // upcasting, implicit
        a.eat();

        // downcasting requires an explicit cast + a safety check
        if (a instanceof Dog d) {     // Java 16+ pattern variable
            d.bark();
        }

        Animal a2 = new Animal();
        // ((Dog) a2).bark();  // ClassCastException at runtime
    }
}

class Animal { void eat() { System.out.println("eat"); } }
class Dog extends Animal { void bark() { System.out.println("bark"); } }

Object and equals / hashCode / toString

Every class implicitly extends java.lang.Object. The three most commonly overridden methods: equals (content equality), hashCode (hash value, used by HashMap/HashSet), toString (string representation, handy for logging). Once you override equals you must override hashCode **together**, or collections will misbehave.

// EqualsHashCode.java
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public class EqualsHashCode {
    public static void main(String[] args) {
        Set<Point> set = new HashSet<>();
        set.add(new Point(1, 2));
        System.out.println(set.contains(new Point(1, 2))); // true (equals/hashCode overridden)
        System.out.println(new Point(1, 2));               // Point(1, 2)
    }
}

class Point {
    final int x;
    final int y;
    Point(int x, int y) { this.x = x; this.y = y; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Point p)) return false;
        return x == p.x && y == p.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }

    @Override
    public String toString() {
        return "Point(" + x + ", " + y + ")";
    }
}