Zum Hauptinhalt springen

Week 2 Day 4 — Inheritance, super, Override vs Overload

Goal

Today I want inheritance as is-a substitution, and a sharp line between override and overload.

Main questions:

  1. What does extends mean?
  2. What do super and super() do?
  3. What is overriding vs overloading?
  4. What cannot be overridden?
  5. When is inheritance the wrong tool? (preview of Week 3)

1. Inheritance

class Child extends Parent means Child is-a Parent. Child gets Parent’s members (except private ones) and can add or override behavior.

Java has single class inheritance. Every class eventually extends Object if it extends nothing else.


public class Payment {
public void settle() {
// default
}
}

public class CardPayment extends Payment {
@Override
public void settle() {
chargeCard();
}
}

Payment p = new CardPayment();
p.settle(); // CardPayment.settle — runtime type wins

The variable type Payment says what I can call. The runtime type CardPayment says which implementation runs. That is polymorphism (Week 3 Day 1).

Memory sentence:

Inheritance is is-a. The variable type is the API. The runtime type is the implementation.


2. super and constructors

Subclass constructors must start by constructing the parent.

  • If I write super(args), that runs the matching parent constructor.
  • If I write nothing, Java inserts super() — the parent no-arg constructor. If it does not exist, the child does not compile.

public class CardPayment extends Payment {
private final String last4;

public CardPayment(String last4) {
super(); // implicit if omitted, only if Payment() exists
this.last4 = last4;
}
}

super.settle() calls the parent implementation from the child. I use it to extend, not to replace and then secretly depend on parent internals.


3. Overriding vs overloading

OverridingOverloading
Same nameYesYes
Same parameter typesYes (same signature)No — different parameters
When chosenRuntime (virtual dispatch)Compile time
@OverrideYes, use itDoes not apply

void print(Object o) { }
void print(String s) { } // overload

print(null); // compiles to print(String) — most specific match

class Parent {
void run() { }
}
class Child extends Parent {
@Override
void run() { } // override
}

@Override is not required. I always write it. If I mistype the signature, the compiler fails instead of silently adding an overload.

Covariant returns are allowed: a child clone() may return Child instead of Object.

A child override cannot throw new checked exceptions that the parent method does not declare.


4. What does not override

  • static methods hide. The compiler binds them to the variable type.
  • private methods are not visible, so they are not overridden.
  • final methods cannot be overridden.
  • Fields hide. There is no field polymorphism. I do not put API in fields.

class Parent {
static String kind() { return "P"; }
}
class Child extends Parent {
static String kind() { return "C"; }
}

Parent p = new Child();
p.kind(); // "P" — static bind to Parent

Memory sentence:

Instance methods override at runtime. Static methods hide at compile time.


5. Spring connection

Spring proxies often extend the concrete class (CGLIB) or implement its interface (JDK proxy). They override methods to add transactions, security, or async.

Consequences I already need:

  • private methods are not proxied. There is nothing to override.
  • final methods cannot be overridden by a CGLIB subclass.
  • Calling this.save() inside the class does not go through the proxy, so @Transactional on save does not start. That is self-invocation — Spring book Week 8, grounded in override rules.

Prefer implements Interface for beans I want JDK proxies and clear substitution. Week 3 Day 5 expands this.

Inheritance of domain classes: I use it when the is-a story is stable (RuntimeException hierarchy). I do not extend a concrete OrderService to make VipOrderService if composition or a strategy interface will do.


6. Common traps

Trap 1: Overload vs override mix-up.
Different parameters = overload = compile time. Same signature in a subclass = override = runtime.

Trap 2: Forgetting @Override and accidentally overloading (run(int) vs run()).

Trap 3: Expecting static to be polymorphic.

Trap 4: Deep domain inheritance for reuse of fields.
Reuse of fields is often composition. Inheritance is reuse of type.


Practice Questions and Answers

Question 1

What is the difference between overriding and overloading?

Answer:

Overloading is several methods with the same name and different parameters. The compiler picks one. Overriding is a subclass replacing an instance method with the same signature. The JVM picks the implementation from the runtime type.


Question 2

Why should I write @Override?

Answer:

If the signature does not match a superclass method, the compiler errors. Without it, I silently add a new method and polymorphism never runs.


Question 3

Why must a subclass constructor call super(...)?

Answer:

The parent part of the object has to be initialized first. If I omit the call, Java inserts super(). If the parent has no no-arg constructor, I must call an explicit super(args).


Question 4

Can I override a static method?

Answer:

No. I can declare another static method with the same signature in the subclass. That hides. Dispatch uses the compile-time type, not the instance.


Question 5

Why does @Transactional on a private method not work?

Answer:

A proxy works by overriding (or implementing) a visible method and wrapping the call. Private methods are not part of that dispatch. The call never hits a subclass override, so no proxy advice runs.


Memory sentences

The variable type is the API. The runtime type is the implementation.

Override is runtime. Overload is compile time. Use @Override.

Proxies work by overriding visible methods. private and final sit outside that.

Next: Week 2 Day 5 — Object: toString, equals, hashCode