Java Constructors and the this Keyword: Building Objects That Start Valid
Executive Summary
A class is a blueprint; an object is an instance created with new, initialized by Java constructors, allocated on the heap, and referenced by variables. Fields hold per-object state, instance methods operate on that state, and constructors initialize it, with validation, at the moment of birth. The this keyword names the current object: it disambiguates shadowed fields, chains constructors with this(…), and passes the object to other code. References, not objects, live in variables. Two variables can point at one object, so mutation through one is visible through the other. Compare objects with equals, not ==, and design constructors so invalid objects cannot exist in the first place.
Classes and Objects in Java: The Mental Model
Three sentences carry the whole model. A class describes what its objects look like: what fields they hold and what methods they run. An object is a concrete instance, created at runtime with new. A variable never holds an object. In fact, it holds a reference that points at one, as the official classes lesson frames it.
| Concept | What it is | How many exist |
|---|---|---|
| Class | The blueprint: fields plus methods | One per definition |
| Object | A live instance with its own field values | As many as you create |
| Field | Per-object state, one copy per object | One per object |
| Instance method | Behavior that reads and writes that object’s fields | Defined once, runs per object |
| Constructor | Initialization code that runs at creation | Runs once per object |
So, here is one complete class you can paste and run. Read it top to bottom: fields first, then constructors, then behavior. The private modifier and toString get their full treatment in the next two articles. For now, private means “only this class touches this field”.
public class BankAccount {
private String owner; // fields: per-object state
private long balanceCents;
BankAccount(String owner) { // one-argument constructor
this(owner, 0); // chains to the two-arg version
}
BankAccount(String owner, long openingCents) {
if (owner == null || owner.isBlank()) {
throw new IllegalArgumentException("owner is required");
}
if (openingCents < 0) {
throw new IllegalArgumentException("opening balance cannot be negative");
}
this.owner = owner;
this.balanceCents = openingCents;
}
void deposit(long amountCents) { // instance method: no static
if (amountCents <= 0) {
throw new IllegalArgumentException("deposit must be positive");
}
balanceCents += amountCents;
}
long balanceCents() { // access without mutation
return balanceCents;
}
@Override
public String toString() {
return owner + ": " + balanceCents + " cents";
}
public static void main(String[] args) {
BankAccount ada = new BankAccount("Ada", 1000);
ada.deposit(500);
System.out.println(ada); // Ada: 1500 cents
}
}
The call ada.deposit(500) is the whole point of objects: the method runs with this bound to the object ada refers to, so balanceCents means Ada’s balance, and no other account’s. In short, one method definition, isolated state per object. If you want the shorter, beginner-first version of this mental model, the Java Mini Series post on classes, objects, and the main method covers it with a Dog example.
Java Constructors: Birth with Rules
A constructor looks like a method with no return type and the class’s name. Java calls it exactly once, during new, before anything else can see the object. If you write no constructor at all, Java silently provides a no-argument default. The moment you write any constructor, the silent one disappears, which is a classic surprise on day one.
In practice, the example above shows the two moves that matter in production. First, constructors validate: an account without an owner or with a negative balance never comes into existence, so the rest of the code never checks again. Second, constructors chain with this(…), so the two-argument version is the single source of truth and the one-argument version just feeds it. Consequently, validation lives in one place, not three.
Java constructors overload like methods: same name, different parameter lists. Use that for convenience overloads that funnel into one fully validated constructor, and resist putting logic in two of them.
The this Keyword, Disambiguated
this is a reference to the current object, available inside instance methods and constructors. It has three jobs, and all three exist to remove ambiguity. Wrong code first, showing the bug this exists to prevent:
// WRONG: the parameter shadows the field; the deposit goes nowhere
class Wallet {
private long cents;
void deposit(long cents) {
cents = cents; // self-assignment of the parameter!
}
}
// RIGHT: this.cents names the field, unambiguously
void deposit(long cents) {
this.cents = cents; // parameter in, field updated
}
The delta: the compiler resolves cents to the nearest declaration, the parameter. this.cents forces the field. Some teams avoid this by never reusing names. The JDK’s own style guide, however, uses this when it clarifies, and IntelliJ flags bare self-assignment as a warning.
Job two is constructor chaining, this(arguments), which must be the first statement of the constructor. Job three is passing the current object along, for example listener.register(this), which you will meet for real in Part 5. All three uses are the same word: “this object, the one running the code”, as the this lesson summarizes.
References, Not Boxes: How Objects Move
Variables hold references, and this has consequences you can see in four lines:
BankAccount ada = new BankAccount("Ada", 1000);
BankAccount copy = ada; // no second object: a second reference
copy.deposit(200);
System.out.println(ada.balanceCents()); // 1200, not 1000
stack (variables) heap (the object)
--------------------- ---------------------------------
ada .------------------+
copy '------------------+--> BankAccount
owner "Ada"
balanceCents 1200
two references, one object, one shared state
In other words, assigning an object to a variable copies the reference, not the object. Therefore, mutation through either reference is visible through both, which is exactly the behavior the methods article demonstrated with StringBuilder. Passing ada to a method passes the same reference, so called code can mutate Ada’s state but cannot redirect your variable.
When no variable references an object anymore, the object becomes garbage, and the collector reclaims it; Part 6 covers that machinery. What matters here: you never free objects in Java, and a null check guards every reference that might point at nothing.
static and instance: Two Kinds of Members
Part 1 wrote static methods because no object existed yet. Now the distinction becomes a design decision: static members belong to the class and are shared by every object. Instance members belong to each object.
| static member | instance member | |
|---|---|---|
| Belongs to | The class itself | Each object |
| Called as | ClassName.method() | reference.method() |
| State | One shared copy (static fields) | One copy per object |
| Sees this? | No | Yes |
| Typical use | Utilities, factories, constants | Domain state and behavior |
A static method cannot read instance fields, because there is no object in scope. That is why the calculator’s apply method was static: it used only its parameters. However, the moment behavior needs per-object state, it becomes an instance method.
How Real Systems Do This
Indeed, production backends are built from classes exactly like this one. A payment service defines a Payment with fields, a validating constructor, and methods that change state legally, and the service code around it shrinks to coordination. When everything must go through the object’s methods, the object cannot drift into an invalid state quietly.
In my experience, constructor validation is the cheapest defect filter you will ever install. A team I joined stored balances as raw longs in maps. As a result, every consumer had to remember the sign rules and the null cases. We moved the rules into the constructor. As a result, three recurring bugs disappeared in one sprint, because the invalid states stopped compiling their way in.
Two production habits follow directly. First, keep mutable static fields rare: a static field is one copy shared by everything, which is a concurrency hazard once threads arrive in Part 5. Second, when a class is just data moving between layers, modern Java gives you records, which you will meet soon. The records article formalizes that boundary.
Decision Framework
- Does the concept have state that changes together? Give it a class, and let methods guard the changes.
- Is the logic a pure function of its arguments? Keep it static, like the calculator’s apply.
- Does the object need to be impossible to create wrongly? Put that rule in the constructor, not in a later check.
- Several constructors? Chain them with this(…) into one that validates everything.
- Is the class only carrying data between layers? That is a record, which Part 2 covers in a few articles.
- Are you tempted by a mutable static field for convenience? Reconsider: shared state is where concurrency bugs are born.
When NOT to Use This
- Do not create a class merely to namespace related methods. A stateless utility class with static methods, like java.util.Arrays, is the honest shape. Forcing an instance adds ceremony without value.
- Also, do not build classes that hold everything. A class named Manager or Context with twenty fields is a file-organizing failure, not a design. Split by responsibility, which the next article formalizes.
- Similarly, do not hand-write mutable state where the value is a fixed bundle of data. Records arrive shortly for exactly that, and they will make your data classes five lines long.
Common Mistakes
- Parameter shadowing without this: cents = cents assigns the parameter to itself, and the field never changes. So, IntelliJ warns; do not ignore it.
- Expecting the default constructor to survive. Writing any constructor removes the free no-argument one, breaking every new ClassName() call that exists.
- Comparing objects with ==. It compares references, so two equal-looking accounts are “different”; use equals, which the Object class article teaches you to override.
- Assuming assignment copies the object. Two references, one object: mutations are shared, and this is a feature you must design around.
- Reading instance fields from static methods. There is no this in a static context, and the compiler rejects it; that error is a design hint, not an obstacle.
- Leaving fields with default values that are invalid states, for example null owner with 0 balance. If null is illegal, make it impossible at birth.
Key Takeaways
- A class is the blueprint; objects are live instances created with new, and variables hold references to them.
- Fields are per-object state; instance methods operate on that state with this bound to the receiving object.
- Java constructors run once at birth: validate there, and chain overloads with this(…) so rules live in one place.
- this disambiguates shadowed fields, chains constructors, and passes the current object onward.
- Assignment copies references, not objects; two variables can share one object and its mutations.
- static members belong to the class and see no this; instance members belong to each object.
- Compare objects with equals, never ==, and keep mutable static fields rare before Part 5 makes them dangerous.
FAQ
What is the difference between a class and an object in Java?
The class is the blueprint, written once in source code; the object is a runtime instance of it, created with new, holding its own field values. One class, many objects, each with its own state.
What is a constructor in Java?
Special initialization code that shares the class name, has no return type, and runs once during object creation. Use it to validate arguments and set fields, so no object is ever born invalid.
When do I use the this keyword in Java?
Three cases: to disambiguate a field shadowed by a parameter (this.owner = owner), to chain constructors (this(…) as the first statement), and to pass the current object to other code.
What is the default constructor in Java?
The no-argument constructor the compiler generates when you write none. It initializes fields to type defaults. Writing any constructor of your own removes it, so dependent calls stop compiling.
Where do Java objects live?
On the heap, with variables holding references to them. When no references remain, the object becomes garbage and the collector reclaims it; you never free memory manually in Java.
Conclusion
Objects gave your variables a home and your methods a subject: state and behavior under one name, guarded by Java constructors that refuse invalidity at birth. From here, Part 2 polishes the craft: encapsulation makes objects honest about their data, inheritance composes them, and records strip the ceremony from pure data.
The next article makes your fields private on purpose, and the guessing game’s loose variables become the first refactor. A class you can hand to a stranger without a paragraph of explanation is the whole style guide.
Last updated on 10 September 2026.
