A Class Is State Plus the Rules That Guard It
This is the rung between references and equality. In CSCD 210 you wrote a class with fields and a constructor (see Writing a class for a refresher). This lesson shows the one habit that turns a class from a bag of data into something CSCD 211 can build on: a constructor that refuses to make an invalid object, so a wrong Course never exists in the first place.
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.
Not sure? Take the 60-second self-check.
In 210 you made fields private and reached them through methods instead of touching them directly. In one sentence, what does making a field private stop other code from doing?
Check
private stops code outside the class from reading or writing the field directly, so every access has to go through the methods the class provides.
The essentials
If you only have a few minutes, walk away with these. They are the floor, not the whole lesson, but they are enough to stand on.
Bare-minimum takeaways
- A class is two things: its fields (the state) and the rule for what counts as a valid object (for example, a
Coursealways has a non-blank code and credits between one and six). - The fields must be
privateso the class is the one place the rule is checked; a public field lets outside code write a bad value directly and skip the rule. - A validating constructor checks every precondition (a condition that must be true before the code is allowed to continue, here a non-blank code and credits between one and six) before it assigns, so you get a valid object or an exception, never a broken one that compiles.
- This always-valid property is called an invariant: a statement about the object’s state that holds for as long as every object exists. The ordering and equality work later in CSCD 211 reasons over your objects trusting it.
Why the invariant matters
- When you implement ordering and equality on these objects, that code assumes every object it touches is already valid, so it never re-checks the data; that trust is the invariant you are establishing here.
- Validate-then-assign is the precondition-check habit you lean on for every constructor you write, so a wrong object can never reach the rest of your program.
The problem
A Course has a credit count, and a real course has between one and six credits. Nothing about an int field knows that:
Course c = new Course("CSCD 211", -5);
If the constructor just copies the number in, this builds a course with minus five credits. It compiles, it runs, and the broken value travels through every total, every sort, and every report until something far away fails on it. The mistake was allowed at the only moment it was cheap to stop.
Check yourself. The constructor copied -5 straight into the field. Why is catching this at construction better than catching it later when a total looks wrong?
Check
At construction you know exactly which value is wrong and can refuse it on the spot. Later, the bad value has spread into totals and sorts, so the symptom shows up far from the cause and is much harder to trace.
Atom 1: a class is state plus the rules that guard it
A class is two things at once: the state, its fields, and the rules about what counts as a valid object. In 210 it was easy to see only the first. The second is what makes a class trustworthy. The fields say what a Course holds; the rules say a Course always has a non-blank code and a credit count between one and six. A class that states its rules and enforces them is one you can build on without re-checking it everywhere.
Check yourself. Name the two parts of the Course class in this lesson: the state, and the rule that should always hold.
Check
The state is the fields, such as courseCode and credits. The rule is the validity condition: the code is non-blank and the credits are between one and six.
Atom 2: encapsulation is how the rule is enforced
For the rule to mean anything, no one can be allowed to set a field around it. That is what private is for. If credits were public, any code could write c.credits = -5 and the rule would be a suggestion. Private fields plus a constructor and methods that are the only way in make the class the single gatekeeper of its own state. Encapsulation (keeping the fields private so the class itself is the only code that can change them) is not about hiding for its own sake; it is about being the one place the rule is checked.
Check yourself. Why does the validity rule only hold if the fields are private?
Check
If a field were public, outside code could write a bad value directly and skip the check, so the rule can only be guaranteed when private fields force every write to go through the class.
Atom 3: a validating constructor means a wrong object never exists
Here is the habit 211 holds you to on every line. The constructor checks each precondition first, and assigns a field only after every check has passed. If a check fails, it throws, and no object is returned at all:
public Course(String courseCode, int credits) {
if (courseCode == null || courseCode.isBlank()) {
throw new IllegalArgumentException("courseCode must not be blank");
}
if (credits < 1 || credits > 6) {
throw new IllegalArgumentException("credits must be 1 to 6");
}
this.courseCode = courseCode;
this.credits = credits;
}
The payoff is sharp: there is no such thing as a half-built or invalid Course. Either you get a valid object or you get an exception, never a broken object that compiles. The property that every Course that exists is valid is called an invariant, a statement that is true for as long as every object of the class exists. The validating constructor is what establishes it, and because the fields are private (only the class can change them), nothing outside the class can break it afterward.
An invariant is the same idea as a loop invariant in math and logic.
An invariant is the programming form of an idea you meet in math and logic: a property preserved by every operation. The constructor establishes it and immutability (the object never changes after it is built) preserves it, so the property holds for as long as the object exists. That is the same shape as a loop invariant or an inductive invariant, one claim that stays true through every step.
Check yourself. Two calls: new Course("CSCD 211", 5) and new Course(" ", 5). What does each return or do?
Check
The first returns a valid Course, because both checks pass. The second throws IllegalArgumentException on the blank code and returns no object at all, so a blank-code Course never exists.
Atom 4: why ordering and equality cannot reason without this
When you order and compare Course objects, that code trusts that every Course it touches is valid: the order can compare credits knowing they are one to six, equality can compare codes knowing they are non-blank. That trust is the invariant from Atom 3. Without a validating constructor, every method would have to re-check the data or risk acting on garbage, and the clean reasoning of the ordering and equality work would collapse. This is why the given Course validates every field before it sets it, and why this prerequisite sits directly below the ordering and equality lessons.
Check Yourself
Close the notes and answer from memory, then reveal. Pulling an idea back from memory is one of the strongest ways to make it stick.
Check your understanding
A Course constructor copies a -5 credit value straight into the field with no check. What is the cleanest way to stop a -5 credit Course from ever existing?
Why does the credits-between-1-and-6 rule only hold if the field is private?
Summary
- A class is state plus the rules that say what a valid object is.
- Encapsulation, private fields, is how the rule is enforced: the class is the one gatekeeper of its state.
- A validating constructor establishes an invariant: a wrong object never exists, only a valid one or an exception.
- Ordering and equality reason over objects trusting they are valid, so the invariant is the floor under the order and the equality you build next.