Writing a Class: Fields, Constructors, and Encapsulation
Where you are: Week 0 review > Writing a class
Try This First
A Book class exposes its price as a public field, so any code can write book.price = -5;. Name the problem this creates before reading on.
Reveal
Nothing stops an invalid value. A public field has no gatekeeper, so a Book can hold a negative price. Making the field private and writing through a validating setter keeps every Book in a valid state.
Before You Start
Check each box you can do from memory. A box you cannot check yet is not a problem; it points you to a quick refresher, not a grade.
The Method Header and How a Method Works
Not sure? Take the 60-second self-check.
Try each from memory, then read the answer under it.
- A constructor, a getter, and a setter are all what? Methods, so the header rules (name, parameters, return type) apply to them.
- What does the return type
voidmean? The method hands nothing back; it does its work and returns.
What You Need To Walk In With
Walk into the next class able to state these:
- A class bundles data (fields) with the methods that operate on it. Every field should be
private. - A value constructor sets the fields and checks preconditions with
if-throw, so no object is built in an invalid state. - Outside code reaches private fields only through methods the class chooses to expose (getters and setters), which keeps the class the sole enforcer of its own rules.
- Override
toStringso printing an object shows its contents, and write@Overrideso the compiler confirms the override.
You should be able to: write a class with private fields, a validating constructor, getters and setters, and a toString.
How It Works
public class Book {
private final String title; // final: no setter, set once at construction
private double price; // mutable: guarded by a setter
public Book(String title, double price) {
if (title == null || title.isBlank()) {
throw new IllegalArgumentException("title must not be blank");
}
if (price < 0) {
throw new IllegalArgumentException("price must be at least 0");
}
this.title = title;
this.price = price;
}
public String getTitle() { return title; }
public double getPrice() { return price; }
public void setPrice(double price) {
if (price < 0) {
throw new IllegalArgumentException("price must be at least 0");
}
this.price = price;
}
@Override
public String toString() {
return String.format("Book[title=%s, price=%.2f]", title, price);
}
}
privatefields can be read or written only by code inside the class. Every outside access goes through a method, so the class controls its own rules.- The
if-throw checks in the constructor are the front door: noBookescapes with a blank title or a negative price. The setter repeats the rule for later writes. this.price = price;usesthisto tell the field apart from the parameter of the same name.- Without a
toStringoverride, printing aBookshows something likeBook@15db9742(the class name and a memory address), which is useful for nothing. The override prints the contents. @Overrideasks the compiler to confirm the method really overrides an inherited one. A typo liketostringthen fails to compile instead of silently becoming a dead method.
Worked Example: Predict, Then Check
Book b = new Book("", 20.0);
Predict what happens when this line runs.
Reveal
It throws IllegalArgumentException with the message “title must not be blank”. The constructor’s if-throw rejects the blank title before the object is ever built, so no invalid Book exists.
A Common Mistake
Leaving fields public (or skipping the constructor checks) allows outside code to put an object into an invalid state, such as a negative price, with nothing to catch it. Make fields private, validate in the constructor and setters, and the class guarantees its own rules. (Source: BJP (Reges and Stepp), Ch 8; Effective Java, Item 16.)
Go Deeper (optional)
For the curious: marking a field final (like title) says it is set once at construction and never changes. A class whose fields are all final is immutable: once built, it cannot change, so it is always valid and safe to share. Many bugs disappear simply by making a field final and removing its setter.
Check Yourself
Close the notes and answer each one from memory, then reveal it. Pulling an idea back from memory is one of the strongest ways to make it stick.
Check your understanding
Why make a class field private?
What do the if-throw checks in a constructor guarantee?
Without a toString override, what does System.out.println(book) print?
What does the @Override annotation do?