What inheritance gives you
class B extends A means: every B is an A, and B starts with everything A has.
class Person {
protected String name;
Person(String name) {
this.name = name;
}
public String describe() {
return "Person: " + name;
}
}
class Student extends Person {
private int roll;
Student(String name, int roll) {
super(name);
this.roll = roll;
}
@Override
public String describe() {
return "Student: " + name + " (roll " + roll + ")";
}
}
public class InheritDemo {
public static void main(String[] args) {
Person p = new Person("Dr Rao");
Person s = new Student("Anita", 101);
System.out.println(p.describe());
System.out.println(s.describe());
}
}
Output:
Person: Dr Rao
Student: Anita (roll 101)
Three things to notice.
super(name) calls the parent constructor. It must be the first statement in the child constructor, because the parent part of the object must exist before the child adds to it.
@Override is optional but you should always write it. If you misspell the method name, the annotation turns a silent bug into a compile error. Without it, describ() would just quietly become a new unrelated method.
The variable s is declared as Person but holds a Student, and s.describe() runs the Student version. The decision is made at run time from the actual object, not at compile time from the declared type. That is dynamic dispatch, and the next lesson is about it.
Now the important half of this lesson
Inheritance is the most overused feature in OOP. Students reach for extends any time two classes share code, and that is the wrong reason.
The correct test is "is a", and it must survive substitution. If code written against the parent works correctly when handed a child, the relationship is valid. If it breaks, the relationship is a lie, no matter how much code you saved.
The classic broken example, runnable
class Rectangle {
protected int width;
protected int height;
public void setWidth(int w) { this.width = w; }
public void setHeight(int h) { this.height = h; }
public int area() { return width * height; }
}
class Square extends Rectangle {
@Override
public void setWidth(int w) { this.width = w; this.height = w; }
@Override
public void setHeight(int h) { this.width = h; this.height = h; }
}
public class SquareProblem {
static void resizeAndCheck(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
System.out.println("expected area 20, got " + r.area());
}
public static void main(String[] args) {
resizeAndCheck(new Rectangle());
resizeAndCheck(new Square());
}
}
Output:
expected area 20, got 20
expected area 20, got 16
The resizeAndCheck method is completely reasonable. It sets a width and a height and expects the area to be their product. It is correct for Rectangle and wrong for Square, and nothing in it can be fixed, because the bug is in the class relationship.
A square is a rectangle in geometry. A Square is not a Rectangle in code, because a Rectangle promises that width and height can be set independently and a Square cannot keep that promise. Inheritance inherits the promises, not just the fields.
This is the Liskov Substitution Principle, and you have just seen it fail in eight lines you can run yourself.
Two more warning signs that you are misusing inheritance. First, an overridden method that throws UnsupportedOperationException — the child is refusing a promise the parent made. Second, a method that checks if (obj instanceof Square) to decide what to do; if callers must know the subclass, the abstraction has failed.
Prefer composition
When you want the code, not the identity, hold an object instead of extending it.
class Engine {
void start() { System.out.println("engine started"); }
}
class Car {
private final Engine engine = new Engine();
void start() {
engine.start();
System.out.println("car ready");
}
}
public class CompositionDemo {
public static void main(String[] args) {
new Car().start();
}
}
A car has an engine. It is not an engine. Writing class Car extends Engine would give Car every engine method, including ones that make no sense on a car, and would lock the design forever.
Composition also survives change. If you later need an ElectricEngine, Car holds an Engine interface and you swap the object. With inheritance you would be rewriting the class hierarchy.
The rule to remember
Use inheritance when the child genuinely is a kind of the parent and can honour every promise the parent makes. Use composition when you just want to reuse behaviour. In real Java codebases composition outnumbers inheritance heavily, and that is a sign of health, not of missing knowledge.
Java allows only single inheritance of classes — one extends. This is deliberate, to avoid the diamond problem where a class inherits two conflicting versions of the same method. You get multiple inheritance of type through interfaces instead, which is the subject of a later lesson.