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 + ")";
}
}