Inheritance lets a new class take the fields and methods of an existing one and then add or change what it needs. Used on a genuine "is-a" relationship it removes whole piles of duplicated code; used on anything else it produces a hierarchy that is harder to change than the duplication it replaced.
What does extends actually give you?
Two things, and only two. The subclass inherits the parent's instance fields and methods, so it does not have to redeclare them. And the subclass may override any inherited method it wants to behave differently. Everything else has to be written again.
Fields are inherited but not initialised for you: a parent field starts at the same zero value it would have in a standalone object, and only the parent's constructor can give it a meaningful value. That is why the parent constructor call is the thing to get right first.
How does a subclass get its parent's state?
With an explicit super(...) call, placed as the first statement of the constructor. The parent constructor runs before the subclass body, so by the time your subclass code starts, the inherited fields are already set up.
public class Demo {
static class Vehicle {
private final String registration;
private int odometerKm;
Vehicle(String registration) {
this.registration = registration;
}
void drive(int km) {
odometerKm += km;
}
int odometerKm() {
return odometerKm;
}
String describe() {
return registration + " odometer " + odometerKm + " km";
}
}
static class Car extends Vehicle {
private final int doors;
Car(String registration, int doors) {
super(registration);
this.doors = doors;
}
@Override
String describe() {
return super.describe() + ", " + doors + " doors";
}
}
public static void main(String[] args) {
Car car = new Car("KA-42-AB", 5);
car.drive(120);
System.out.println(car.describe());
System.out.println("odometer: " + car.odometerKm());
}
}The car inherits drive and odometerKm without restating them, and the output shows the inherited odometer reaching 120. The overridden describe calls super.describe() to reuse the parent's wording and then adds its own, which is usually better than copying the parent's text into the subclass.
If you write no super(...) call, the compiler inserts a call to the parent's no-argument constructor for you. That only works if the parent has one, which is one more reason to keep a simple no-argument constructor available in classes meant to be extended.
How far can a hierarchy go?
As deep as you like, and it compiles fine. Each level inherits from the level above it and can add its own behaviour, calling super at each step:
public class Demo {
static class Vehicle {
private final String registration;
Vehicle(String registration) {
this.registration = registration;
}
String describe() {
return "vehicle " + registration;
}
}
static class Car extends Vehicle {
private final int doors;
Car(String registration, int doors) {
super(registration);
this.doors = doors;
}
@Override
String describe() {
return super.describe() + " with " + doors + " doors";
}
}
static class ElectricCar extends Car {
private final int batteryKwh;
ElectricCar(String registration, int doors, int batteryKwh) {
super(registration, doors);
this.batteryKwh = batteryKwh;
}
@Override
String describe() {
return super.describe() + " and a " + batteryKwh + " kWh battery";
}
}
public static void main(String[] args) {
ElectricCar e = new ElectricCar("EV-7", 5, 77);
System.out.println(e.describe());
}
}The single output line reads vehicle EV-7 with 5 doors and a 77 kWh battery, assembled from three classes. Deep hierarchies are legal and they do save typing, but each level adds a step for the reader: to know what an electric car's describe prints, you have to read three methods. Two or three levels is usually plenty; five is where people start regretting it.
Who calls whom in the constructor chain?
Java starts at the top of the chain and works down. The grandparent constructor runs first, then the parent, then the subclass body, each one adding its own setup. Inside a subclass you have two choices for the first statement of a constructor: super(...) to call the parent, or this(...) to call another constructor of the same class. You may use one or the other, never both.
public class Demo {
static class Vehicle {
private final String registration;
Vehicle(String registration) {
this.registration = registration;
}
String registration() {
return registration;
}
}
static class Car extends Vehicle {
private final int doors;
Car(String registration) {
this(registration, 4);
}
Car(String registration, int doors) {
super(registration);
this.doors = doors;
}
int doors() {
return doors;
}
}
public static void main(String[] args) {
Car car = new Car("MH-12-XY");
System.out.println(car.registration() + " / " + car.doors() + " doors");
}
}The short constructor delegates to the longer one with this(registration, 4), and the longer one calls the parent with super(registration). The result prints MH-12-XY / 4 doors. A useful detail: a final field of the parent is already assigned by the time the subclass constructor body runs, so you can read it with a getter like registration() inside the subclass.
Is inheritance the right tool, or should you compose?
Apply the is-a test: can you say "a car is a vehicle" without wincing? A car really is a vehicle, so a car may extend a vehicle. A square is not a rectangle, even though every square has four right angles, because a square has extra rules that a rectangle does not obey.
public class Demo {
static class Rectangle {
private double width;
private double height;
Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
void setWidth(double width) {
this.width = width;
}
void setHeight(double height) {
this.height = height;
}
double area() {
return width * height;
}
}
static class Square extends Rectangle {
Square(double side) {
super(side, side);
}
void setSide(double side) {
setWidth(side);
setHeight(side);
}
}
public static void main(String[] args) {
Square s = new Square(3);
System.out.println("area: " + s.area());
s.setWidth(5);
System.out.println("area after setWidth(5): " + s.area());
}
}The square starts at an area of 9.0, which is right. Then the inherited setWidth(5) changes one side only, and the area becomes 15.0: a rectangle that is 5 wide and 3 tall, wearing a square's name. The code compiled, the compiler was happy, and the object is now nonsense. That is the failure mode inheritance introduces when the relationship is not really an is-a one. The safer alternative is composition: a square that holds a rectangle and exposes only what makes sense for a square.
Inheritance rules worth memorising
extendsgives you the parent's fields and methods, and lets you override its methods.super(...)must be the first statement of a constructor, or you usethis(...)instead.- You inherit one class only, but you can chain any number of levels.
- Private parent fields exist but are not directly reachable from the subclass.
- Overriding keeps the parent's contract: the same signature, equal or wider access.
- Inherit only for a real is-a relationship, otherwise hold the other object as a field.
Try it yourself: build a small hierarchy for a music library. Write a Track class with a title and a length in seconds, then a Podcast class that extends it with an episode number and a show name, calling super(title, length) and adding a describe() that uses super.describe() plus its own text. Add a third level, InterviewEpisode, that extends Podcast with a guest name. Print the description of one of each. Then write the is-a test out loud for a Single that extends Album, decide whether the relationship genuinely holds, and if it does not, rewrite it as composition with the album held as a field.