Zum Hauptinhalt springen

Week 2 Day 3 — Constructors, this, and Initialization

Goal

Today I want construction as the moment an object becomes valid.

Main questions:

  1. What does a constructor do?
  2. What is the default constructor?
  3. How do this() and this.field differ?
  4. In what order does Java initialize?
  5. Why does Spring prefer constructors for injection?

1. Constructors

A constructor’s job is to establish invariants and assign fields.


public class OrderService {
private final OrderRepository orders;
private final Clock clock;

public OrderService(OrderRepository orders, Clock clock) {
this.orders = Objects.requireNonNull(orders);
this.clock = Objects.requireNonNull(clock);
}
}

this.orders is the field. The parameter orders would shadow the field without this..

After the constructor returns, a well-designed object is ready. No init() the caller might forget.

Memory sentence:

If the object is not valid, the constructor should throw. After new, it is ready.


2. Default and overloaded constructors

If I write no constructor, Java gives a public no-arg constructor.

If I write any constructor, that default disappears.


public class User {
private final String name;

public User(String name) {
this.name = name;
}
}

// new User(); // compile error — no no-arg constructor

JPA and some proxies want a no-arg constructor. Frameworks then set fields. That is a framework constraint, not a good domain default.

I can overload constructors. I chain with this(...) as the first statement:


public User(String name) {
this(name, true);
}

public User(String name, boolean active) {
this.name = Objects.requireNonNull(name);
this.active = active;
}

this(...) calls another constructor of this class. super(...) calls the superclass (Week 2 Day 4).


3. Initialization order

For new Child(...) the JVM roughly does:

  1. Allocate heap memory, default fields (0, null, false)
  2. Superclass constructor chain, starting at Object
  3. Superclass instance initializers and field initializers
  4. Superclass constructor body
  5. Then the same for the child: field initializers, then constructor body

class Demo {
private final int n = compute(); // runs before constructor body
private final int extra;

Demo(int extra) {
this.extra = extra;
}

private int compute() { return 1; }
}

Calling overridable instance methods from a constructor is a trap: the subclass fields are not initialized yet, but the overridden method already runs.


4. Factory methods

Sometimes a constructor is not the clearest API:


public static Money eur(long cents) {
return new Money("EUR", cents);
}

Factories can name the intent, cache instances, or return a subtype. The constructor stays private if I want all creation to go through the factory.

Spring’s @Bean methods are factories the container calls.


5. Spring connection

Constructor injection is the default for a reason:

  • all dependencies are visible
  • fields can be final
  • the object cannot exist half-wired
  • unit tests call the same constructor

@Service
public class OrderService {
private final OrderRepository orders;

public OrderService(OrderRepository orders) {
this.orders = orders;
}
}

With a single constructor, Spring injects it without @Autowired on Spring Boot 2.2+.

Setter injection allows optional dependencies and circular references (usually a design smell). The object exists before the setter runs.

Field injection skips the constructor API entirely.

JPA entities: a protected or package no-arg constructor for the provider, plus a public constructor for my code that sets required state. I do not call the no-arg constructor in application code.

Memory sentence:

Spring calls a constructor. I should write the constructor I want tests to call.


6. Common traps

Trap 1: Business logic in setters that must run “after construction.”
Callers and frameworks can skip them. Put required work in the constructor or in an explicit factory.

Trap 2: Doing I/O in a constructor (open a DB connection, read files).
Construction should be cheap and local. Spring beans that need startup I/O use ApplicationRunner or lazy methods. Constructors that throw for missing arguments are fine.

Trap 3: Overridable methods in constructors.
The subclass is not initialized yet.

Trap 4: Assuming JPA’s no-arg constructor is my domain API.
It exists for the provider. My application uses the constructor that takes real data.


Practice Questions and Answers

Question 1

What happens if I write one constructor that takes arguments?

Answer:

Java no longer provides the public no-arg constructor. Callers must pass the arguments, or I add another constructor myself.


Question 2

Why is constructor injection preferred in Spring?

Answer:

Dependencies are required and visible, fields can be final, and the bean is complete when constructed. Tests instantiate the same way. I do not need @Autowired if there is only one constructor.


Question 3

What is the difference between this.orders and this(orders)?

Answer:

this.orders is the field on the current instance. this(orders) is a call to another constructor of the same class, and it must be the first statement.


Question 4

Why is calling an overridable method from a constructor dangerous?

Answer:

The overridden method in the subclass runs before the subclass constructor body and field initializers finish. It can read null or 0 and publish this too early.


Question 5

How do I handle a JPA entity that also needs a real constructor?

Answer:

I keep a no-arg constructor for the provider (protected/package) and a public constructor that sets required fields for application code. I do not leave the entity valid only after setters.


Memory sentences

After new, the object is valid — or the constructor threw.

One constructor is the Spring injection point and the test entry point.

this.field is a field. this(...) is another constructor.

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