Inheritance and Constructors

Constructors Are Never Inherited

A subclass inherits its superclass's public methods and fields, but it does not inherit constructors, and it can't directly touch the superclass's private fields. Yet every Intake object still contains RobotPart's private name field, and that field needs to be properly set up before anything uses it — so how does a subclass initialize state that belongs to its superclass?

The super(...) Call

The keyword super, used like a method call, runs the superclass's constructor code in the context of the object already being built. Here's the part of RobotPart (Lesson 17.1) that matters for construction, and an Intake that adds a field of its own:

public class RobotPart
{
    private String name;

    public RobotPart(String name) { this.name = name; }
    // ...getName(), log(), and stop() as in Lesson 17.1
}

public class Intake extends RobotPart
{
    private boolean hasGamePiece;

    public Intake(String name)
    {
        // runs RobotPart's constructor, setting the private name field
        super(name);
        hasGamePiece = false;  // Intake's own field
    }
}

super(name) isn't creating a second object — it's the one and only way to run RobotPart's own initialization logic for the object currently under construction. Without it, name would never get set, since Intake has no direct access to that private field to assign it itself.

The Implicit super() Call

If a constructor doesn't contain an explicit super(...) call, Java automatically inserts a no-argument super() call at its start. That only works if the superclass actually has a no-argument constructor — if it doesn't, any subclass constructor must explicitly call super(...) with arguments matching a constructor that does exist, or the code won't compile. RobotPart has only RobotPart(String name), so every subclass constructor must call super(...) itself.

Where super(...) Goes

On Java 25, a constructor may run a few statements before super(...), as long as they don't use the object being built — checking or preparing an argument, for example:

public Intake(String name)
{
    if (name.isBlank()) throw new IllegalArgumentException("Intake needs a name");
    super(name.trim());
    hasGamePiece = false;
}

What those early statements can't do is use the object itself: calling one of its methods (even an inherited one like getName()) or reading its fields before super(...) is a compile error, because the RobotPart part of the object doesn't exist yet. On Java 17, super(...) must be the very first statement of the constructor — nothing at all can come before it. Putting super(...) first works on every version.

The Chain Bottoms Out at Object

Whether called explicitly or inserted implicitly, every chain of super calls eventually reaches java.lang.Object's own no-argument constructor — the one class with no superclass of its own (Lesson 17.4). From there, construction "unwinds": Object's constructor logic runs first, then each class going back down the hierarchy, ending with whichever class was actually instantiated. By the time the rest of your own constructor's body runs, every inherited field further up the chain has already been properly initialized.

Intake intake = new Intake("Front Intake");
// Order of execution: Object() -> RobotPart(String) -> Intake(String)

A Middle Ground: protected

private fields are hidden even from subclasses. Java has one access level in between: a field or method marked protected can be used directly by subclasses (and by other classes in the same package). If RobotPart had declared protected String name;, Intake could read and assign name itself. This course keeps fields private and goes through super(...) and getters instead, so the superclass stays in control of its own data. Lesson 23.1 compares all four access levels side by side.

Writing Your Own Exception

Now that you can write a subclass, you can create your own exception type (Ch.12): extend RuntimeException and pass the message up with super(message), so the inherited getMessage() returns it:

public class ArmLimitException extends RuntimeException
{
    public ArmLimitException(String message)
    {
        super(message);  // RuntimeException stores it for getMessage()
    }
}

throw new ArmLimitException("Arm past soft limit"); then works like any built-in exception, and a catch (ArmLimitException e) block can handle exactly this problem and nothing else. Because it extends RuntimeException, it's unchecked.

Common Pitfalls

  • Trying to assign a superclass's private field directly. this.name = name; inside Intake's constructor doesn't compile if name is private in RobotPart — only super(name) can reach it.
  • Using the object before super(...) has run. On Java 25, statements that don't touch the object (like checking an argument) may come before super(...), but calling the object's methods or reading its fields there doesn't compile. On Java 17, super(...) must be the very first statement, with nothing before it.
  • Assuming a no-argument super() always works. If the superclass has no no-argument constructor (only ones taking parameters), Java can't insert an implicit call — the subclass must explicitly call super(...) with matching arguments.

Key Takeaways

  • Constructors are never inherited — only a super(...) call can run the superclass's constructor logic to initialize its own (even private) fields.
  • If a subclass constructor doesn't call super(...) explicitly, Java inserts a no-argument super() call automatically — which only works if the superclass has one.
  • On Java 25, only statements that don't use the object may come before super(...); on Java 17, super(...) must be first.
  • Every construction chain eventually reaches Object's constructor, which runs first; the subclass's own code only runs after every class above it has finished initializing.
  • protected lets subclasses use a member directly; a custom exception is just a subclass of RuntimeException that calls super(message).

Derived from csawesome-2026: unit-5-inheritance-optional/5.02-inheritance-and-constructors.md (CSA 5.2)