Java Constructor Inheritance: How super() Works
java constructor inheritance: Understand why constructors are not inherited in Java, how super() chains constructors, and how to handle parameterized superclass constr...
java constructor inheritance requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
In Java, constructors are not inherited. When you create a subclass, it does not automatically receive the constructors of its parent class. Instead, every constructor in the subclass must call one constructor from the superclass, either explicitly with super() or implicitly through the default no-argument super() call. This design ensures that the superclass state is initialized before the subclass adds its own fields and behavior.
Why Constructors Are Not Inherited in Java
Constructors are not members of a class in the same way fields and methods are. When you extend a class, the subclass does not inherit the constructors of the parent. Each constructor in the subclass must ensure that a constructor of the superclass is invoked, either explicitly or implicitly. This is deliberate: a constructor initializes the state of the specific class it belongs to, and a subclass has additional fields and behavior that require its own initialization logic.
Consider this simple example:
class Animal { public Animal() { System.out.println("Animal created"); } } class Dog extends Animal { public Dog() { // implicit super() call to Animal() } }
The Dog constructor does not explicitly call super(), but the compiler inserts it automatically as the first statement. This is why the output when you create a Dog is Animal created. The implicit super() call invokes the no-argument constructor of the superclass.
The Implicit super() Call in Every Constructor
Every constructor in Java must call either another constructor in the same class (via this()) or a constructor of the superclass (via super()). If you do not write either call, the compiler adds super() with no arguments. This implicit call has two important consequences.
First, the superclass must have a no-argument constructor. If it does not, the code will not compile. Second, the super() call must be the first statement in the constructor body. You cannot place any other logic before it.
class Vehicle { private String model; public Vehicle(String model) { this.model = model; } } class Car extends Vehicle { private int doors; public Car(String model, int doors) { super(model); // must be first this.doors = doors; } }
Here, Vehicle only has a parameterized constructor, so Car must explicitly call super(model). If you omitted that call, the compiler would try to insert super() and fail because Vehicle has no no-argument constructor.
Handling Parameterized Superclass Constructors
When a superclass defines only parameterized constructors, every subclass constructor must explicitly call one of them. This is common when the superclass requires certain fields to be initialized before the subclass can proceed. The explicit call makes the dependency clear and ensures that the superclass is fully constructed before the subclass initializes its own fields.
class Account { private String accountId; public Account(String accountId) { this.accountId = accountId; } } class SavingsAccount extends Account { private double interestRate; public SavingsAccount(String accountId, double interestRate) { super(accountId); this.interestRate = interestRate; } }
In this example, SavingsAccount must pass accountId to the superclass constructor. The super(accountId) call must be the first line. If you need to validate or transform the argument before passing it to super(), you cannot do it directly in the constructor body because super() must be first. Instead, you can use a static helper method that returns the value, as long as the method does not reference instance fields.
Constructor Chaining Across Multiple Levels
Constructor calls chain upward through the class hierarchy. When you create an instance of a subclass, the first constructor invoked is the subclass constructor, which immediately calls super(), which calls its own super(), and so on until reaching the Object class constructor. This chain ensures that the most superclass state is initialized first.
class A { public A() { System.out.println("A"); } } class B extends A { public B() { System.out.println("B"); } } class C extends B { public C() { System.out.println("C"); } }
Creating new C() prints A, B, C in that order. Each constructor runs after the superclass constructor completes. This ordering is critical for correct initialization because a subclass might rely on fields set by the superclass.
If a subclass constructor calls another constructor in the same class using this(), the chaining still eventually reaches a super() call. For example:
class Point { private int x, y; public Point() { this(0, 0); // calls the parameterized constructor } public Point(int x, int y) { this.x = x; this.y = y; } }
Here, the no-argument constructor delegates to the parameterized one, which then implicitly calls super() (the Object constructor). This pattern is useful for providing default values without duplicating initialization logic.
Common Mistakes with Constructor Invocation
A frequent error is forgetting to call super() when the superclass lacks a no-argument constructor. The compiler error message is clear: "constructor SuperClass in class SuperClass cannot be applied to given types; required: ...; found: no arguments". The fix is to add an explicit super(...) call with the correct arguments.
Another mistake is attempting to call super() after other statements. The Java compiler enforces that super() or this() must be the first statement in a constructor. If you try to assign a field before calling super(), you get a compilation error. This rule exists because the superclass constructor must complete before any subclass fields are accessed or modified.
A less obvious issue arises when a subclass constructor calls a method that is overridden in the subclass. Because the superclass constructor runs before the subclass fields are initialized, calling an overridden method from the superclass constructor can lead to unexpected behavior. This is not directly about constructor inheritance, but it is a common pitfall when designing constructors in a hierarchy. The safest approach is to avoid calling overridable methods from constructors.
Maintainability and Design Considerations
Constructor inheritance has direct implications for code maintainability. When a superclass constructor changes its parameters, every subclass that explicitly calls it must be updated. If a subclass relies on the implicit super() call, adding a parameterized constructor to the superclass without keeping a no-argument version will break all subclasses that do not explicitly call super(). This is a common source of compilation failures during refactoring.
To reduce coupling, you can provide a protected no-argument constructor in the superclass that supplies sensible defaults, even if the main constructors require arguments. This gives subclasses flexibility while still enforcing required initialization. However, doing so may hide mandatory dependencies, so weigh the tradeoff between flexibility and correctness.
Another design option is to use a static factory method instead of a constructor when the initialization logic is complex or when you need to return a specific subclass instance. Factory methods can have descriptive names and can delegate to private constructors, but they do not change the fundamental rule that constructors themselves are not inherited.
Understanding how super() works is essential for writing robust class hierarchies. Always make the superclass constructor call explicit when the superclass has parameterized constructors, and keep the first statement rule in mind when designing constructors that need to perform validation or setup before delegation.