Inheritance lets one class take over the fields and methods of another and add to them. It is the feature most tutorials reach for first, and it is also the feature most likely to be the wrong tool for the job you are holding.
So this article does two things. It teaches extends, super, protected and final properly, down to the exact order in which constructors run. And it is honest about the cost, because a class hierarchy that looked reasonable on day one is the single hardest thing to unwind six months later.
![]()
Every output line and every error message below was produced by compiling and running the code on OpenJDK 21.0.6. Where a rule is enforced by the compiler, the real javac message is quoted rather than paraphrased.
What inheritance is, and the "is a" test
class B extends A says two things at once. It says a B object contains everything an A object contains, and it says a B is an A — it can be used anywhere an A is expected.
That second half is the part people skip, and it is the part that decides whether inheritance is correct. The test is a sentence: can you say "every B is a A" and have it be true of the domain, not just convenient for your code?
| Relationship | Is the sentence true? | Right tool |
|---|---|---|
SavingsAccount / Account | every savings account is an account | inheritance |
Manager / Employee | every manager is an employee | inheritance |
Car / Engine | a car is not an engine, it has one | composition |
Invoice / Buffer | an invoice is not a buffer, it uses one | composition |
Stack / ArrayList | a stack is not a list — a list lets you insert in the middle | composition |
The last three are all the same mistake wearing different clothes: reaching for extends because the other class already has code you want. That is code reuse, not classification, and composition does it without the side effects. There is a section on that at the end of this article, with both versions of the same program.
Read the rest of this article as the manual for a sharp tool. Learn it thoroughly, then use it sparingly.
extends: what a subclass gets
A subclass declares extends and inherits the superclass's fields and methods, subject to the access modifiers. It can add its own, and it can call the inherited ones as if it had declared them.
class Account {
String owner;
double balance;
Account(String owner, double balance) {
this.owner = owner;
this.balance = balance;
}
void deposit(double amount) {
balance += amount;
}
String describe() {
return owner + " has " + balance;
}
}
class SavingsAccount extends Account {
double rate;
SavingsAccount(String owner, double balance, double rate) {
super(owner, balance);
this.rate = rate;
}
void addInterest() {
deposit(balance * rate);
}
}
public class InheritanceBasics {
public static void main(String[] args) {
SavingsAccount s = new SavingsAccount("Mai", 1000.0, 0.05);
s.deposit(500.0);
s.addInterest();
System.out.println(s.describe());
System.out.println("owner field = " + s.owner);
System.out.println("rate field = " + s.rate);
}
}
Mai has 1575.0
owner field = Mai
rate field = 0.05
SavingsAccount never declares owner, balance, deposit or describe, and uses all four. addInterest calls deposit with no qualifier, and it reads balance the same way — inherited members are ordinary members of the subclass.
What crosses the boundary and what does not:
| Member | Inherited? |
|---|---|
public and protected fields and methods | yes |
| package-private members | only when both classes are in the same package |
private members | no — they exist in the object, but the subclass cannot name them |
static fields and methods | yes, and accessible through the subclass name |
| Constructors | never |
| Initializer blocks | not inherited; each class runs its own |
The private row deserves a second look, because it trips people up. A private field is still allocated in every subclass instance — the object carries it — but no subclass code can read or write it. The only route in is a public or protected accessor the superclass chose to provide.
Constructors are not inherited
This is the rule that produces the most confusing first error, because the message points at a line you did not think was doing anything.
class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
}
public class CtorNotInherited {
public static void main(String[] args) {
Dog d = new Dog("Rex");
System.out.println(d.name);
}
}
CtorNotInherited.java:9: error: constructor Animal in class Animal cannot be applied to given types;
class Dog extends Animal {
^
required: String
found: no arguments
reason: actual and formal argument lists differ in length
CtorNotInherited.java:14: error: constructor Dog in class Dog cannot be applied to given types;
Dog d = new Dog("Rex");
^
required: no arguments
found: String
reason: actual and formal argument lists differ in length
Two errors from a class with no visible constructor at all. The second one is the direct answer: Dog has no constructor taking a String, because Animal(String) did not come along with the inheritance. Dog got the compiler's default no-argument constructor instead — that is what required: no arguments means.
The first error is the consequence of the same fact. The generated Dog() starts with an implicit super(), Animal has no no-argument constructor, and the call fails. One missing constructor, two errors, neither of them on a line you wrote.
Every class inherits from java.lang.Object
A class with no extends clause is not at the top of anything. It extends java.lang.Object implicitly, so every object in a Java program has the same root.
class Vehicle {
int wheels = 4;
}
class Car extends Vehicle {
String model = "Civic";
}
class SportsCar extends Car {
int topSpeed = 250;
}
public class ObjectRoot {
public static void main(String[] args) {
SportsCar sc = new SportsCar();
Class<?> c = sc.getClass();
while (c != null) {
System.out.println(c.getName());
c = c.getSuperclass();
}
System.out.println("after Object: " + Object.class.getSuperclass());
System.out.println();
System.out.println("toString -> " + sc.toString());
System.out.println("hashCode -> " + Integer.toHexString(sc.hashCode()));
System.out.println("equals -> " + sc.equals(sc) + " / " + sc.equals(new SportsCar()));
System.out.println("getClass -> " + sc.getClass().getSimpleName());
}
}
SportsCar
Car
Vehicle
java.lang.Object
after Object: null
getSuperclass() walks one link at a time and returns null at the top, which is the concrete proof that Object is the end of the chain and that Vehicle — which declares no extends — is nonetheless below it.
That inheritance is why these four methods exist on every reference you will ever hold:
toString -> SportsCar@6d6f6e28
hashCode -> 6d6f6e28
equals -> true / false
getClass -> SportsCar
The hex suffix is the identity hash and differs from run to run; the rest is stable.
| Method | Default behaviour inherited from Object |
|---|---|
toString() | the class name, @, and the hex identity hash — exactly what you see above |
equals(Object) | reference identity, so a.equals(b) is a == b |
hashCode() | an identity hash, constant for the life of the object |
getClass() | the runtime class; it is final, so nothing can change what it reports |
Object also contributes clone(), notify(), notifyAll() and the wait() overloads. equals returning false for two separately constructed SportsCar objects is the default identity behaviour, not a bug — giving it a value-based meaning is a deliberate act you perform yourself.

Java has single inheritance of classes
A class extends exactly one class. Listing two is not a semantic error the compiler explains politely — the grammar simply ends at the first class name.
class Engine {
void start() { System.out.println("engine start"); }
}
class Radio {
void play() { System.out.println("radio play"); }
}
class Car extends Engine, Radio {
}
SingleInheritance.java:9: error: '{' expected
class Car extends Engine, Radio {
^
The caret is on the comma. javac finished reading extends Engine and expected the class body to start.
The restriction exists to avoid the diamond problem: if Car inherited a concrete reset() from two superclasses, there would be no principled way to decide which body runs. Java's answer is that a class has one implementation parent and any number of interface types — which is where multiple inheritance of type lives, and the subject of article 30.
The example above is also a good "is a" test failure. A car is not an engine and is not a radio; it has one of each. Two fields solve it with no language feature at all.
How super(...) chains constructors
This is where beginners get stuck, so it is worth being precise. Every constructor begins by running a superclass constructor. If the first statement is not an explicit super(...) or this(...), the compiler inserts super() for you.
The insertion is not a figure of speech. Compile a subclass with no constructor at all and disassemble it:
class Parent {
Parent() {
System.out.println("Parent()");
}
}
class Child extends Parent {
int x = 7;
}
class Child extends Parent {
int x;
Child();
Code:
0: aload_0
1: invokespecial #1 // Method Parent."<init>":()V
4: aload_0
5: bipush 7
7: putfield #7 // Field x:I
10: return
}
The generated Child() calls Parent."<init>" before it does anything else, and only then assigns x. Field initializers run after the superclass constructor returns — that ordering matters, and the next example makes it visible.
Trace the whole thing. ConstructorChain.trace prints an incrementing counter, so the numbers in the output are the real execution order and not a claim:
class Base {
String baseTag = ConstructorChain.trace("Base field initializer");
Base(String from) {
ConstructorChain.trace("Base body (arg = " + from + ")");
}
}
class Middle extends Base {
String middleTag = ConstructorChain.trace("Middle field initializer");
Middle() {
super(ConstructorChain.trace("argument of super(...) in Middle"));
ConstructorChain.trace("Middle body");
}
}
class Leaf extends Middle {
String leafTag = ConstructorChain.trace("Leaf field initializer");
Leaf() {
ConstructorChain.trace("Leaf body");
}
}
public class ConstructorChain {
static int step = 0;
static String trace(String msg) {
System.out.println((++step) + ". " + msg);
return msg;
}
public static void main(String[] args) {
System.out.println("new Leaf()");
new Leaf();
}
}
new Leaf()
1. argument of super(...) in Middle
2. Base field initializer
3. Base body (arg = argument of super(...) in Middle)
4. Middle field initializer
5. Middle body
6. Leaf field initializer
7. Leaf body
Read that against the source and the shape is clear. new Leaf() enters Leaf(), which immediately calls the implicit super(). That enters Middle(), which evaluates the argument expression for super(...) — step 1, the only thing that happens on the way up — and calls Base(String). Base calls Object(). Only now does anything execute in a body: Base's field initializer, then Base's body, then Middle's, then Leaf's.
So the call travels up and the work happens down. For each class the order is: superclass constructor, then that class's field initializers and instance initializer blocks, then that class's constructor body.

The practical consequence: when a superclass constructor runs, none of the subclass's fields have been initialized yet. They still hold their default values.
When the superclass has no no-argument constructor
Write a constructor in the subclass, forget super(...), and the invisible super() the compiler inserts has nothing to call.
class Shape {
String name;
Shape(String name) {
this.name = name;
}
}
class Circle extends Shape {
double radius;
Circle(double radius) {
this.radius = radius;
}
}
MissingSuper.java:12: error: constructor Shape in class Shape cannot be applied to given types;
Circle(double radius) {
^
required: String
found: no arguments
reason: actual and formal argument lists differ in length
found: no arguments is the tell. Nothing in Circle calls Shape with zero arguments, but the compiler did. The fix is to call it yourself: super("circle") or Circle(String name, double radius) { super(name); ... }.
Note the asymmetry this creates. Giving a class a constructor with parameters removes the default no-argument one, and every subclass immediately has to supply super(...). A superclass that keeps a no-argument constructor is easier to extend — which is a design decision, not an accident.
super(...) must be the first statement
The call has to be the very first statement in the constructor. Not the first meaningful one — the first one.
class Square extends Shape {
double side;
Square(double side) {
this.side = side;
super("square");
}
}
SuperNotFirst.java:14: error: call to super must be first statement in constructor
super("square");
^
The same rule governs this(...), which delegates to another constructor of the same class. A constructor may start with super(...) or this(...), never both:
class Rect extends Shape {
double w, h;
Rect(double w, double h) {
super("rect");
this.w = w;
this.h = h;
}
Rect(double side) {
super("square");
this(side, side);
}
}
ThisAndSuper.java:20: error: call to this must be first statement in constructor
this(side, side);
^
That is the correct design anyway: Rect(double) should call this(side, side) and let that constructor do the single super(...). Exactly one superclass constructor runs per object, no matter how many this(...) hops are chained in front of it.
The "first statement" rule is what JDK 21 enforces, and it is the rule to learn. Java 25 finalised JEP 513, Flexible Constructor Bodies, which permits statements before
super(...)provided they do not read or write the instance under construction. The examples in this article were compiled on OpenJDK 21.0.6, where the rule is absolute.
Reaching the superclass with super.method() and super.field
Inside a subclass, super. names the superclass's version of a member. It is how you extend behaviour rather than replace it:
class Report {
String title;
Report(String title) {
this.title = title;
}
String render() {
return "== " + title + " ==";
}
}
class SalesReport extends Report {
double total;
SalesReport(double total) {
super("Sales");
this.total = total;
}
String render() {
return super.render() + "\n total: " + total;
}
}
== Sales ==
total: 1250.5
SalesReport.render reuses the superclass output and appends to it. Drop the super. and the method calls itself:
class SalesReport extends Report {
String render() {
return render() + " + total";
}
}
Exception in thread "main" java.lang.StackOverflowError
at SalesReport.render(ForgotSuper.java:9)
at SalesReport.render(ForgotSuper.java:9)
at SalesReport.render(ForgotSuper.java:9)
at SalesReport.render(ForgotSuper.java:9)
at SalesReport.render(ForgotSuper.java:9)
A stack trace of one repeated line is the signature of exactly this mistake. super. is not decoration; it is what makes the call go to a different method.
super.someField works the same way and is the only way to read a superclass field that a subclass field of the same name has hidden — which is the subject of the next section. What super. cannot do is skip a level: there is no super.super.render(), because a class is only allowed to depend on its immediate parent's contract.
Redefining a method the superclass already declares is overriding, and its full semantics — which body actually runs, @Override, and what happens through a superclass-typed reference — belong to article 28. Here it is only scaffolding for showing what super. reaches.
Field hiding is not overriding
This is the trap in this article, and it is worth slowing down for. When a subclass declares a field with the same name as a superclass field, it does not replace it. Both fields exist on the object, and which one you read is decided by the declared type of the reference, at compile time.
import java.lang.reflect.Field;
class Config {
String source = "base";
int timeout = 30;
String fromBase() {
return "inside Config: source=" + source + " timeout=" + timeout;
}
}
class DevConfig extends Config {
String source = "dev";
String fromSub() {
return "inside DevConfig: source=" + source + " super.source=" + super.source
+ " timeout=" + timeout;
}
}
public class FieldHiding {
static void readAsBase(Config c) {
System.out.println("through a Config reference: source=" + c.source);
}
public static void main(String[] args) throws Exception {
DevConfig dev = new DevConfig();
Config asBase = dev;
System.out.println("same object? " + (dev == asBase));
System.out.println("dev.source = " + dev.source);
System.out.println("asBase.source = " + asBase.source);
readAsBase(dev);
System.out.println(dev.fromBase());
System.out.println(dev.fromSub());
System.out.println();
System.out.println("slots on one object:");
for (Class<?> c = dev.getClass(); c != Object.class; c = c.getSuperclass()) {
for (Field f : c.getDeclaredFields()) {
f.setAccessible(true);
System.out.println(" " + c.getSimpleName() + "." + f.getName()
+ " = " + f.get(dev));
}
}
}
}
same object? true
dev.source = dev
asBase.source = base
through a Config reference: source=base
inside Config: source=base timeout=30
inside DevConfig: source=dev super.source=base timeout=30
slots on one object:
DevConfig.source = dev
Config.source = base
Config.timeout = 30
dev and asBase are the same object — dev == asBase prints true — and reading source through them gives different strings. Nothing about the object changed; only the type of the name used to reach it.
The reflection dump at the end shows why: the object genuinely carries DevConfig.source and Config.source side by side. Hiding does not overwrite a slot, it adds one. timeout appears once because only Config declares it, so both references agree about it.

Method calls do not behave like this at all, and the contrast is the whole subject of article 28. For now, the operative rule is the plain one:
⚠️ Never give a subclass field the same name as a superclass field. There is no case where the two-slot behaviour is what you wanted, and the resulting bug reads as "the value I set is not the value I get".
The accident usually happens in a constructor. Writing String source; in the subclass because the field "should be there" quietly creates the second slot, the constructor fills the subclass copy, and every inherited superclass method keeps reading the empty superclass copy — which prints null.
protected: what a subclass in another package can touch
protected exists for exactly one purpose: to expose a member to subclasses without exposing it to the world. It only means anything once the subclass is in a different package, so the demonstration needs a real two-package tree:
src/com/example/base/Employee.java
src/com/example/hr/Manager.java
package com.example.base;
public class Employee {
private String id = "E-001";
protected String name;
protected double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
protected void raise(double pct) {
salary += salary * pct;
}
public String toString() {
return name + " (" + id + ") earns " + salary;
}
}
package com.example.hr;
import com.example.base.Employee;
public class Manager extends Employee {
private int reports;
public Manager(String name, double salary, int reports) {
super(name, salary);
this.reports = reports;
}
public void annualReview() {
raise(0.10);
salary += reports * 500;
System.out.println("reviewed " + name + ", salary now " + salary);
}
}
reviewed Linh, salary now 68000.0
Linh (E-001) earns 68000.0
Manager is in com.example.hr and reads salary, writes salary and calls raise — all protected, all across a package boundary. It cannot touch id:
src/com/example/hr/Intern.java:11: error: id has private access in Employee
System.out.println(id);
^
Yet id is clearly there: toString printed E-001 for a Manager. The field is inherited into the object and hidden from the subclass's code at the same time.
Drop the extends and the access disappears with it. A plain class in the same foreign package gets nothing:
package com.example.hr;
import com.example.base.Employee;
public class Auditor {
public void audit(Employee e) {
System.out.println(e.salary);
}
}
src/com/example/hr/Auditor.java:7: error: salary has protected access in Employee
System.out.println(e.salary);
^
There is one more rule, and it surprises almost everyone. A subclass in another package may reach a protected member only through a reference of its own type, not through a superclass-typed one:
package com.example.hr;
import com.example.base.Employee;
public class Payroll extends Employee {
public Payroll(String name, double salary) {
super(name, salary);
}
public void compare(Employee other) {
System.out.println(this.salary);
System.out.println(other.salary);
}
}
src/com/example/hr/Payroll.java:12: error: salary has protected access in Employee
System.out.println(other.salary);
^
this.salary compiled. other.salary did not, and other is an Employee — the very class that declared the field. The rule grants a subclass access to its own inherited state, not to the state of arbitrary instances of its parent.
| Modifier | Same class | Same package | Subclass, other package | Everywhere |
|---|---|---|---|---|
private | yes | no | no | no |
| package-private (no modifier) | yes | yes | no | no |
protected | yes | yes | yes, through a reference of its own type | no |
public | yes | yes | yes | yes |
Note the second column: protected includes package access. Inside com.example.base, an unrelated class reads salary without any inheritance at all — same package, no inheritance, protected read: 42000.0. protected is strictly wider than package-private, never narrower.
Every protected member is a promise to every future subclass, and it is a promise that is much harder to withdraw than a private one. Prefer private with a protected accessor when the subclass needs a value but not the field.
final: blocking inheritance and overriding
Article 25 introduced final as a modifier that stops reassignment. On classes and methods it does something different: it stops extension.
A final class cannot be a superclass:
final class Money {
final double amount;
Money(double amount) {
this.amount = amount;
}
}
class Discount extends Money {
Discount(double amount) {
super(amount);
}
}
FinalClass.java:9: error: cannot inherit from final Money
class Discount extends Money {
^
The same message is what you get from class MyString extends String:
ExtendString.java:1: error: cannot inherit from final String
public class ExtendString extends String {
^
String is final on purpose. Its immutability, the string pool and its hashCode cache are only safe if no subclass can interfere, and the language guarantees that by refusing the extends.
A final method cannot be redefined by a subclass:
class Session {
private final String token = "abc123";
public final String token() {
return token;
}
}
class FakeSession extends Session {
public String token() {
return "anything";
}
}
FinalMethod.java:10: error: token() in FakeSession cannot override token() in Session
public String token() {
^
overridden method is final
That is the mechanism behind Object.getClass() — it is final, so no class can lie about its own runtime type.
| You write | You are saying |
|---|---|
final class X | this class's behaviour is fixed; extend it and you get a compile error |
final void m() | subclasses may exist, but this method's body is not negotiable |
private void m() | not visible to subclasses, so effectively not overridable either |
Marking a class final is not defensive posturing; it is the decision to keep the freedom to change the class later. Which is exactly the problem the next section describes.
The fragile base class problem
Here is the cost, in the smallest form that still shows it. A Mailer sends mail; a CountingMailer subclass counts what it sends.
class Mailer {
void send(String to) {
System.out.println(" -> " + to);
}
void sendAll(String[] recipients) {
for (String r : recipients) {
System.out.println(" -> " + r);
}
}
}
class CountingMailer extends Mailer {
int sent = 0;
void send(String to) {
sent++;
super.send(to);
}
void sendAll(String[] recipients) {
sent += recipients.length;
super.sendAll(recipients);
}
}
One send and a batch of two gives the right answer:
-> a@example.com
-> b@example.com
-> c@example.com
sent = 3
Now somebody tidies up Mailer. The duplicated print in sendAll becomes a call to send. The public API is untouched, no signature moves, and no subclass is edited:
void sendAll(String[] recipients) {
for (String r : recipients) {
send(r);
}
}
-> a@example.com
-> b@example.com
-> c@example.com
sent = 5
Identical mail, and the counter is now wrong. CountingMailer.sendAll adds 2 and then Mailer.sendAll calls send twice, which adds 2 more. The batch is counted twice.
Nothing here is a bug in either class read on its own. The subclass depended on a fact the superclass never promised — that sendAll did not go through send — and a refactoring inside the superclass silently changed the answer. That is the fragile base class problem: a subclass is coupled to its superclass's implementation, not only to its API, and there is no syntax for the superclass author to declare which internal calls are part of the contract.
It gets worse in the real world, where the superclass lives in a library you upgrade rather than a file you can read in the same commit. The JDK has exactly this shape. Write the same counting subclass twice, once over HashSet and once over ArrayList:
class CountingSet<E> extends HashSet<E> {
int added = 0;
public boolean add(E e) {
added++;
return super.add(e);
}
public boolean addAll(Collection<? extends E> c) {
added += c.size();
return super.addAll(c);
}
}
CountingList extends ArrayList with a body identical line for line. One add plus a batch of three goes into each:
HashSet subclass: size=4 added=7
ArrayList subclass: size=4 added=4
Four elements either way, and the HashSet subclass counted seven. HashSet does not implement addAll at all — reflection reports it as declared in java.util.AbstractCollection, whose version loops calling add, so the subclass's add fires three extra times. ArrayList declares its own addAll and never calls add.
Identical subclass code, one JDK, two different answers, and nothing in either class's published API says which one you are getting. That is why "prefer composition to inheritance" is standard advice rather than a stylistic preference.
Composition instead of inheritance
Composition means holding the other object in a field and calling it, instead of becoming it. The counting mailer, written the other way, with the refactored Mailer unchanged:
class CountingMailerBox {
private final Mailer inner = new Mailer();
int sent = 0;
void send(String to) {
sent++;
inner.send(to);
}
void sendAll(String[] recipients) {
for (String r : recipients) {
send(r);
}
}
}
inheritance -> sent = 5
composition -> sent = 3
Same program, same Mailer, and the composed version is correct. It never calls inner.sendAll, so however Mailer chooses to implement batching internally is none of its business. The dependency is on the two methods it actually calls, and nothing else.
The second effect is control over the API. Inheriting drags every public member of the superclass into your class whether it makes sense there or not:
class Buffer {
private StringBuilder sb = new StringBuilder();
void write(String s) { sb.append(s); }
void clear() { sb.setLength(0); }
String content() { return sb.toString(); }
}
class InvoiceInherits extends Buffer {
void addLine(String item, double price) {
write(item + " " + price + "\n");
}
}
class InvoiceComposes {
private final Buffer buf = new Buffer();
void addLine(String item, double price) {
buf.write(item + " " + price + "\n");
}
String render() { return buf.content(); }
}
clear() was never meant to be part of an invoice, but InvoiceInherits published it anyway, and a caller can wipe a half-built invoice:
inherited invoice:
Tea 3.0
composed invoice:
Coffee 4.5
Tea 3.0
The composed version does not have the method to misuse:
NoClear.java:5: error: cannot find symbol
b.clear();
^
symbol: method clear()
location: variable b of type InvoiceComposes
An honest comparison, because inheritance is not always the loser:
| Inheritance | Composition | |
|---|---|---|
| Relationship expressed | is a | has a |
| Coupled to | the superclass's implementation, including its internal calls | only the methods you call |
| API surface | every public and protected member comes along | exactly what you choose to expose |
| Superclass changes | can silently change behaviour, as above | cannot reach you |
| Substitutability | a subclass can be passed wherever the superclass is expected | you get none for free |
| Delegation cost | none to write | one forwarding method per operation you re-expose |
The last row is the real trade. Composition makes you write forwarding methods, and for a wide API that is genuinely tedious. Inheritance is the right answer when the "is a" sentence is true and you want substitutability — a Manager that can be passed to any method taking an Employee is the whole point. Reach for composition when you only wanted the code.
Common mistakes
Expecting constructors to be inherited. new Dog("Rex") after class Dog extends Animal with only Animal(String) in scope gives constructor Dog in class Dog cannot be applied to given types, because Dog has only the default no-argument constructor. Declare the constructor you want and forward with super(name).
Forgetting super(...) when the superclass has no no-argument constructor. The message names the superclass and says found: no arguments, which reads oddly on a subclass constructor that clearly has one. It is describing the super() the compiler inserted.
Putting super(...) anywhere but first. call to super must be first statement in constructor. If you need to compute a value for it, compute it in the argument expression or in a private static helper — those are evaluated before the superclass constructor runs, as step 1 of the trace showed.
Redeclaring a superclass field in the subclass. No error, no warning, two slots, and dev.source versus asBase.source printing different strings for one object. If you want the subclass to change the value, assign to the inherited field instead of declaring a new one.
Using extends because the superclass has code you want. The Invoice that extended Buffer inherited clear() and published it to every caller. If the sentence "an invoice is a buffer" is false, hold a Buffer in a field.
Reaching for protected by reflex. Every protected member is part of the contract with all future subclasses, and widening it later is easy while narrowing it is a breaking change. Start private.
FAQ
Does a subclass inherit constructors in Java?
No. Constructors are the one thing extends never carries across, because a constructor is tied to the class it names. A subclass either declares its own constructors or gets the compiler's default no-argument one. That is why class Dog extends Animal { } with Animal(String) in the superclass fails on new Dog("Rex") with constructor Dog in class Dog cannot be applied to given types.
What does super() do if I do not write it?
The compiler inserts super() as the first statement of any constructor that does not start with an explicit super(...) or this(...). It is a real call — disassembling a subclass with no constructor shows invokespecial Method Parent."<init>":()V as the first instruction. If the superclass has no no-argument constructor, that inserted call does not compile, and the error appears on your constructor's signature line.
Why does javac say "constructor X in class X cannot be applied to given types"?
Two different situations produce it. If the class named is the superclass and the message says found: no arguments, the implicit super() failed — add an explicit super(...) with the right arguments. If the class named is the one you are instantiating and required: no arguments, you called a constructor the class does not have, usually because you expected it to inherit one.
Can a Java class extend more than one class?
No. class Car extends Engine, Radio does not even parse — javac reports '{' expected with the caret on the comma. A class has exactly one superclass, defaulting to java.lang.Object. Multiple inheritance of type comes from interface, which a class can implement any number of, and which article 30 covers.
What is the difference between hiding a field and overriding a method?
A subclass field with the same name as a superclass field hides it: both slots exist on the object, and which one is read is decided by the declared type of the reference at compile time. The same object printed dev through a DevConfig reference and base through a Config reference. Methods resolve by the object's actual class instead, which is why the two look similar in source and behave nothing alike — article 28 covers that half in full.
When should I use composition instead of inheritance?
Whenever the "is a" sentence is false, and often even when it is true. Use inheritance when a subclass genuinely must be usable everywhere the superclass is, and when you control both classes. Use composition when you only wanted to reuse the code — it does not drag the superclass's whole API into yours, and it cannot be broken by a refactoring inside the class you are reusing, as the counter that jumped from 3 to 5 showed.
Conclusion
Inheritance is two mechanisms wearing one keyword. extends copies a set of members into your class, and it declares an "is a" relationship that lets your object stand in for the superclass. The first is easy to reach for and the second is what actually makes it correct.
The details that decide whether your code works are all in this article's outputs. Constructors do not cross the boundary, so new Dog("Rex") fails on a class you never gave a constructor. super(...) runs first, always, and the chain walks up to Object before a single body executes — steps 1 through 7 in the trace, in that exact order. A redeclared field creates a second slot and a same-object read that returns two different values. And a superclass that quietly refactors sendAll to call send can turn a correct subclass into a wrong one without either file looking broken.
Next in this series: polymorphism — overriding versus overloading. Method overriding is where inheritance stops being about reuse and starts being about substitution: which body runs when you call a method through a superclass reference, what @Override buys you, and why overloading — chosen by the compiler from the static types — is a completely different mechanism that only looks similar.