skip to content
everything.oop.inheritance

inheritance

extends, super, and how a subclass object is laid out in memory.

chapter
chapter 03.06
reading time
7 min read

class Dog extends Animal makes every Dog also an Animal: it inherits Animal's fields and methods, and a Dog object physically contains Animal's fields. methods can be overridden (the object's class decides which runs). fields can only be hidden (the variable's type decides which you get). constructors aren't inherited at all, they chain.

the picture

think of a form with a stapled extension. the base form (Animal) has name and legs. the dog extension adds breed and good-boy status. a filled-in dog form is the base form plus the extension, stapled together. anyone who only knows how to read base forms can still read the first page.

object =
classes
class Object
toString()
equals(..)
hashCode()
…
class Animal
private String name
int legs
describe()
class Dog
String breed
boolean good
describe() · overrides
fetch()
one Dog object, on the heap32 B
header (the JVM's)
mark wordhash · lock · age
class→ Dog
from Animal
nameprivate: it's in every Dog, Dog's code just can't name it→ "rex"
legs4
from Dog
breed→ "corgi"
goodtrue

the Animal part sits at the same offsets it has in a plain Animal. so code compiled against Animal reads legs from a Dog without knowing it's a Dog.

hover a class to see which slice of the object it contributes. flip between new Animal() and new Dog(): the Animal part doesn't move. purple is the JVM's header, blue are references. sizes measured on jdk 25.

that "same offsets" detail is the trick that makes inheritance cheap. a method compiled for Animal reads legs at a fixed position. hand it a Dog and the legs is right where it expects. no conversion, no copy. that's why a Dog can go anywhere an Animal is expected.

what you get, and what you don't

member of Animalin Dog?
public / protected fields and methodsinherited, usable
package-private membersinherited, usable only if Dog is in the same package
private fieldsin every Dog object, but Dog's code can't name them
private methodsnot inherited (Dog can declare one with the same name, unrelated)
constructorsnever inherited. Dog's constructors call them via super(...)
static membersreachable through Dog too, but hidden, not overridden

overriding and super

a subclass can replace an inherited method by declaring one with the same signature. super.method() calls the parent's version from inside the override, which is how you extend behaviour instead of replacing it.

Main.java
class Animal {
    final String name;
 
    Animal(String name) {
        this.name = name;
    }
 
    String describe() {
        return "an animal named " + name;
    }
}
 
class Dog extends Animal {
    final String breed;
 
    Dog(String name, String breed) {
        super(name);                 // constructors chain, they aren't inherited
        this.breed = breed;
    }
 
    @Override
    String describe() {
        return super.describe() + ", who is a good " + breed;
    }
}
 
void main() {
    Animal plain = new Animal("rex");
    Animal dog = new Dog("rex", "corgi");
    IO.println(plain.describe());
    IO.println(dog.describe());
}
output
an animal named rex
an animal named rex, who is a good corgi

look at the last line: dog is declared as Animal, and the Dog version ran anyway. the variable's type says what you're allowed to call. the object's class decides which code runs. that's polymorphism, and it gets its own chapter. the constructor chain gets one too: constructors and init order.

fields don't override. they hide.

here's the surprise. declare a field in Dog with the same name as one in Animal, and you don't replace it. you get two fields, both in the object.

predict the output
java
class Animal {
    String name = "animal";
    String name() { return "animal"; }
}
 
class Dog extends Animal {
    String name = "dog";
    @Override String name() { return "dog"; }
}
 
void main() {
    Animal a = new Dog();
    IO.println(a.name + " " + a.name());
}
stack
main
Animal a
Dog d
heap
Dog
name (Animal's)
name (Dog's)
String
"animal"
String
"dog"
one Dog, two references to it with different declared types, and two fields called name inside it. a.name picks Animal's slot because a is declared as Animal. d.name picks Dog's.
Main.java
class Animal {
    String name = "animal";
    String name() { return "animal"; }
}
 
class Dog extends Animal {
    String name = "dog";
    @Override String name() { return "dog"; }
}
 
void main() {
    Animal a = new Dog();
    Dog d = (Dog) a;
    IO.println(a.name + " " + a.name());
    IO.println(d.name + " " + d.name());
    IO.println(((Animal) d).name);    // a cast is enough to switch fields
}
output
animal dog
dog dog
animal

the lesson: never reuse a field name in a subclass. better yet, keep fields private and you physically can't.

casting up and down

going up the tree (Animal a = dog) is always safe, so it's implicit. going down (Dog d = (Dog) a) is a promise that javac can't check, so the JVM checks it at runtime and throws ClassCastException if you lied. the modern way is to test and cast in one step:

java
if (a instanceof Dog d) {      // pattern matching, java 16+
    IO.println(d.breed);
}

one parent, and a root for everyone

java classes have exactly one direct superclass. leave out extends and you get Object. so every class sits in one tree with Object at the top, and every object has toString(), equals() and hashCode().

  • the root. 11 methods, no fields
  • ├── abstract
  • │ ├── Integerfinal
  • │ └── Longfinal
  • ├── Stringfinal: can't be extended
  • ├──
  • │ └──
  • │ └── ArrayList
  • └── yours
  • ├── Dog
  • └── Cat
single inheritance: one parent each, all the way up to Object. a class can't have two parents, but it can implement any number of interfaces (next chapters).

final on a class stops the tree: String, Integer and every record are final. final on a method stops overriding just that method. both are how a class says "i wasn't designed to be extended, and i'm not pretending".

keylines
  • a subclass object contains every field of its superclasses, private ones included.
  • the parent's fields keep their positions, so a Dog works anywhere an Animal does.
  • methods override: the object decides. fields hide: the variable's type decides.
  • constructors chain with super(...). they're never inherited.
  • always @Override. never reuse a field name.