Defining a class of your own: instance variables kept private, constructors that initialise them, overloading so an object can be built two ways, and the getters and setters that let other classes reach the data without depending on how it is stored. Follows Think Java 2e, Chapter 11 (Designing Classes), Sections 11.1-11.4, pp. 183-190, cross-referenced against The Java Tutorials — Providing Constructors for Your Classes.
Subject: Java · 65 slides · code lesson
Open the interactive version of this deck
Title
Think Java 2e · Chapter 11 · Designing Classes
Sections 11.1-11.4 · pp. 183-190
Objectives
This lesson follows Think Java 2e, Chapter 11 (Designing Classes), Sections 11.1-11.4, pp. 183-190. Everything on these slides can be checked against those pages.
1. Define a class with private instance variables, and say what information hiding buys you.
2. Write a constructor, and name the three ways its syntax differs from an ordinary method.
3. Use this to refer to the object being constructed.
4. Overload a constructor, and explain shadowing.
5. Explain why making instance variables public creates a dependency between classes.
6. Write getters and setters following the naming conventions.
Warm-up
Two things you have used without being able to build.
Discussion prompt
You have created objects with new Point(3, 4) and new Scanner(System.in). What do you think happens between new running and getting your reference back — and where did the 3 and the 4 go?
Hint: Something has to put those values into the object.
Answer:
new allocates the object, and then something runs that puts 3 into its x and 4 into its y. That something is a constructor, and this lesson is about writing one.
Everything in Chapter 10 was about using classes someone else wrote. This chapter is the other side: defining a class creates a new object type with the same name, and you have been able to do it since Chapter 1 without knowing.
Concept
Whenever you create a class you are creating a new object type with the same name. Back in Section 1.3, creating the class Hello also created an object type named Hello — we simply never made one. A class definition is a template for objects: it specifies what attributes they have and what methods operate on them.
Figure (svg): A UML class diagram for Time showing three private instance variables and two public constructors
new operator instantiates objects — it creates new instances of a class.Note the minus signs in the diagram. Everything in Point and Rectangle was public; here the data is private, which is the first design decision this chapter makes.
Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 11 (Designing Classes), Sections 11.1-11.4, pp. 183-190 — Chapter 11 opens on printed page 183.
Section
Section 11.1
Concept
A common reason to define a class is to encapsulate related data in an object that can be treated as a single unit — so you can pass and return one object rather than several values. Point and Rectangle did this; Time is one we will build ourselves.
public class Time {
private int hour;
private int minute;
private double second;
}| decision | choice | why |
|---|---|---|
| what data | hour, minute, second | everything a time of day needs |
| hour and minute | int | they are whole numbers |
| second | double | to keep things interesting — fractional seconds |
| where declared | at the start of the class, outside any method | they belong to the object, not to a method |
instance variable — An attribute of an object; a non-static variable defined at the class level.
instance — A member of a class. Every object is an instance of a class.
instantiate — Create a new instance of a class in the computer's memory.
Attributes are also called instance variables, because each instance has its own — as opposed to class variables, which Chapter 12 introduces. By itself this fragment is already a legal class definition.
Notation
Two visibility keywords in four lines, and they point in opposite directions. That contrast is the whole design.
Annotate
blank.x.This kind of isolation has a name: information hiding. It is the first genuinely architectural idea in the book, and the rest of the chapter follows from it.
Worked example
Designing a class starts by deciding what data every object of that type must carry, and what type each piece should be.
// what does a time of day consist of?
// an hour 0 to 23
// a minute 0 to 59
// a second 0.0 to 59.999...
public class Time {
private int hour;
private int minute;
private double second;
}| step | decision |
|---|---|
| identify the data | hour, minute, second |
| choose each type | int, int, double |
| choose visibility | private |
| where to declare | at the beginning of the class, outside any method |
Ask what every object of this type must carry.
Why: Every Time has an hour, a minute and a number of seconds — which is why they are instance variables rather than parameters.
Decide the type of each.
Why: It seems clear that hour and minute should be integers. Making second a double allows fractions of a second.
Declare them outside any method.
Why: Instance variables are declared at the beginning of the class definition — that placement is what makes them belong to the object.
Make them private by default.
Why: You can always widen access later; narrowing it after other classes depend on it is much harder.
Verify: Compile the four-line class on its own. It should compile cleanly and do nothing.
Why: That is worth doing — a class with no methods at all is legal, and compiling it confirms the declarations are right before anything else is added. It is Lesson 4b's stub, applied to a class.
Definition probe
Where it is declared decides what it is.
Sort into buckets
Sort each variable in a class you might write.
Concept
This is the third kind of variable from Lesson 10b, now being declared by you rather than read from the library.
| local variable | parameter | instance variable | |
|---|---|---|---|
| declared | inside a method body | in a method header | in the class, outside any method |
| created | on invocation | on invocation | when the object is created |
| destroyed | on return | on return | when the object is |
| one per | call | call | object |
The bottom row is the one the name comes from: each instance has its own variables. Two Time objects have two separate hour variables, exactly as two frames had two separate copies of n in Lesson 8a.
Trap
A local variable disappears with the method. The object remembers nothing.
public class Time {
public void setUp() {
int hour = 11; // a LOCAL variable
int minute = 59;
}
}| where declared | belongs to | survives the method? |
|---|---|---|
| inside setUp | the method call | no |
| at the class level | the object | yes |
These variables are created when setUp is invoked and discarded when it returns, so the Time object has no data at all. Nothing warns you — the class compiles perfectly.
Instance variables go at the beginning of the class, outside every method.
public class Time {
private int hour;
private int minute;
private double second;
}| question | answer |
|---|---|
| must every object of this type have it? | yes — make it an instance variable |
| is it needed only during one method call? | no — make it a local variable |
| is it supplied by the caller for one call? | no — make it a parameter |
This is Lesson 10b's rule — let the lifetime decide where to declare it — now applied when writing the class rather than reading one. Data the object must remember belongs to the object.
Prediction
A class with data and no methods at all.
public class Time {
private int hour;
private int minute;
private double second;
}| does a class need | ? |
|---|---|
| at least one method? | no |
| a main method? | only to be run as a program |
| a constructor? | no — Java supplies a default if you write none |
Predict first
Is this a legal class definition?
Correct: Yes — a class with only instance variables is legal
Why: By itself this fragment is a legal class definition. A class needs a main method only if you intend to run it as a program, and Time is designed to be used by other classes rather than run. Compiling it at this stage is a useful checkpoint before adding anything.
Fill the middle
Three pieces of data, hidden from other classes.
Fill in the blanks
public class Time private} int hour;
private int minute;
private double second;
}
Why: private means the variables can be accessed only from inside the Time class, which is what keeps other classes from depending on how Time stores its data. Note the contrast with the class itself, which is public so that other classes can use the type at all.
Socratic
Point and Rectangle did not.
Discussion prompt
Point's x and y are public and Time's hour and minute are private. Both work. What does making them private actually protect, given that anyone can still read the hour through a method?
Hint: What is a getter free to do that a public variable is not?
Answer:
It protects how the data is stored, not the data itself. getHour() could return an instance variable, or compute the hour from a total number of seconds, or read it from somewhere else — and no client would notice.
With a public variable, every client is written against your storage decision. Change it and their code breaks. That is the dependency the next idea is about.
So information hiding is not about secrecy. It is about being able to change your mind later — which is why it also simplifies what other programmers need to know to use your class.
Section
Section 11.2
Concept
After declaring instance variables, the next step is to define a constructor — a special method that initialises the object. Its syntax is like any other method's, with three differences.
public Time() {
this.hour = 0;
this.minute = 0;
this.second = 0.0;
}| difference | in this constructor |
|---|---|
| the name is the class's name | Time |
| there is no return type | not even void |
| static is omitted | it operates on an object being created |
constructor — A special method that initialises the instance variables of a newly constructed object.
This constructor takes no arguments; each line initialises an instance variable to 0, which is midnight for a Time object.
Notation
this appeared in Lesson 10b as the object a method was invoked on. In a constructor it means the object being created.
Annotate
this is a keyword referring to the object we are creating. You can use it the same way you use the name of any other object.this as an argument to other methods.this, and you cannot assign to it. It is supplied by the language.static. A static method belongs to the class; a constructor is initialising one particular object, which is what this refers to.The absence of a return type is what tells the compiler this is a constructor rather than an ordinary method that happens to be named Time.
Worked example
Two steps, and now you can see both of them.
public static void main(String[] args) {
Time time = new Time();
}| step | what happens |
|---|---|
| 1 | Java creates the object — memory is allocated |
| 2 | your constructor runs, initialising the instance variables |
| 3 | new returns a reference to the new object |
| 4 | the assignment stores that reference in time |
new allocates the object.
Why: At this instant the instance variables exist but hold default values.
The constructor is invoked on it.
Why: Your three assignments run, with this referring to the object just allocated.
new returns a reference.
Why: When the constructor is done, new returns a reference to the new object.
The caller stores it.
Why: The reference gets assigned to the variable time, which has type Time.
Verify: Create a Time and confirm the program compiles and runs.
Why: You cannot yet print its contents — the instance variables are private and there is no toString. That is deliberate: Lesson 11b adds both, and until then the object is genuinely opaque from outside, which is what private means.
Definition probe
Three syntactic differences distinguish them.
Sort into buckets
Sort each header.
Concept
The picture is the same as every object diagram since Chapter 7. What is new is that you wrote the class that decides what is inside.
Figure (svg): A variable named time with an arrow to a Time object holding hour 11, minute 59 and second 59.9
Compare it with the Point diagram from Lesson 10a: a variable, an arrow, and boxes inside the object. Nothing about the model changed — which is the point of having a consistent one. The only difference is that these boxes are unreachable from outside the class.
Trap
Infinite recursion. The constructor calls itself, forever.
public Time() {
new Time(); // StackOverflowError
this.hour = 0;
this.minute = 0;
this.second = 0.0;
}| step | what happens |
|---|---|
| new Time() is called | invokes the constructor |
| the constructor runs new Time() | invokes the constructor again |
| and again | each call adds a frame |
| eventually | StackOverflowError |
This is Lesson 8a's missing base case, in a place you would not expect it: new invokes the same constructor, which uses new again, which invokes the constructor again, and nothing ever stops.
The object already exists by the time the constructor runs. Just initialise it.
public Time() {
this.hour = 0;
this.minute = 0;
this.second = 0.0;
}| misconception | reality |
|---|---|
| the constructor creates the object | new creates it; the constructor initialises it |
| I need to make a Time here | this already IS the Time |
| a constructor returns the object | new returns it; the constructor returns nothing |
The mental model that prevents this: by the time your constructor's first line runs, the object exists and this refers to it. Your job is only to fill it in.
Prediction
It looks like an ordinary method.
public void Time() {
this.hour = 0;
}| written | what Java sees |
|---|---|
| public void Time() | an ordinary method that happens to be called Time |
| public Time() | a constructor |
Predict first
What does adding void do?
Correct: It stops being a constructor and becomes an ordinary method
Why: A constructor has no return type at all, so writing void turns it into an ordinary method that merely shares the class's name. It compiles, and new Time() then finds no matching constructor — or silently uses the default one, leaving your initialisation code never called. This is a genuinely confusing bug precisely because nothing looks wrong.
Fill the middle
Initialise every instance variable to zero.
Fill in the blanks
public Time() this}.hour = 0;
this.minute = 0;
this.second = 0.0;
}
Why: A constructor is named after its class and has no return type — not even void. this refers to the object being created, so this.hour names that object's own instance variable rather than any local or parameter.
Explain it to yourself
Every other method declares one.
Discussion prompt
A constructor returns nothing, so you might expect void. Java requires no return type at all. What would be ambiguous if constructors were allowed to say void?
Hint: How does the compiler tell a constructor from a method?
Answer:
The absence of a return type is what identifies it as a constructor. If void Time() were also a constructor, there would be no way to write an ordinary method named Time — and no way for the compiler to tell which you meant.
So the missing return type is not an omission but a marker. It is why public void Time() compiles as an ordinary method rather than being flagged as a mistake — the compiler has no reason to think you meant a constructor.
And it is also honest: new returns the reference, not the constructor. The constructor genuinely produces no value at all.
Section
Section 11.3
Concept
Like other methods, constructors can be overloaded — you can provide several with different parameters. Java knows which to invoke by matching the arguments you provide against the parameters.
public Time(int hour, int minute, double second) {
this.hour = hour;
this.minute = minute;
this.second = second;
}| you write | which constructor runs | result |
|---|---|---|
| new Time() | the no-argument one | 00:00:00.0 |
| new Time(11, 59, 59.9) | the three-argument one | 11:59:59.9 |
| new Time(11) | neither — no match | compile error |
overloaded — Two or more methods (or constructors) with the same name but different parameters.
It is common to provide both a default constructor taking no arguments and a value constructor taking the initial values. Overloading gives you the flexibility to create an object first and fill in the attributes later, or to collect all the information before creating it.
Notation
The parameters have the same names as the instance variables. That is conventional, deliberate, and the reason this is not optional here.
Annotate
this gets past the shadow. Java provides the keyword so you can access instance variables regardless of shadowing — so this constructor copies the values from the parameters into the instance variables.this the line would be hour = hour;, which assigns the parameter to itself and leaves the object untouched.So this.hour = hour; reads as the object's hour gets the parameter's hour. Every value constructor you write will have this shape.
Worked example
Two constructors, three instance variables. Once you get the hang of it, writing constructors gets boring — you can write them just by looking at the list of instance variables.
public class Time {
private int hour;
private int minute;
private double second;
public Time() {
this.hour = 0;
this.minute = 0;
this.second = 0.0;
}
public Time(int hour, int minute, double second) {
this.hour = hour;
this.minute = minute;
this.second = second;
}
}| call | hour | minute | second |
|---|---|---|---|
| new Time() | 0 | 0 | 0.0 |
| new Time(11, 59, 59.9) | 11 | 59 | 59.9 |
| new Time(18, 50, 0.0) | 18 | 50 | 0.0 |
Write the default constructor from the variable list.
Why: One assignment per instance variable, each to a zero value.
Write the value constructor from the same list.
Why: One parameter per instance variable, and one assignment each.
Use this on the left of every assignment.
Why: Because the parameters shadow the instance variables — without it, nothing is stored.
Note that some IDEs generate these for you.
Why: Which is a fair signal that the code is mechanical rather than interesting.
Verify: Create one object each way. Both should compile and run.
Why: You still cannot see inside them, which is the right moment to notice that a class with data and constructors but no accessors is write-only — you can build objects and learn nothing from them. That is what the next two ideas fix.
Prediction
this is missing.
private int hour;
public Time(int hour) {
hour = hour;
}
// then: Time t = new Time(11);| name | resolves to |
|---|---|
the left hour | the parameter — searched first |
the right hour | the parameter |
| the instance variable | never mentioned |
Predict first
What is the object's hour afterwards?
Correct: 0 — the instance variable was never assigned
Why: Both sides of the assignment resolve to the parameter, because the compiler searches parameters before instance variables — so the object's hour is never touched and keeps its default of 0. Writing this.hour = hour; names the instance variable explicitly and fixes it.
Concept
Overload resolution is by the number and types of the arguments — the same rule that applied to methods in Lesson 6b's substring.
| call | matches | why |
|---|---|---|
| new Time() | Time() | no arguments |
| new Time(11, 59, 59.9) | Time(int, int, double) | three arguments of those types |
| new Time(11, 59, 0) | Time(int, int, double) | the int 0 is widened to 0.0 |
| new Time(11, 59) | nothing | no constructor takes two arguments |
The third row is Lesson 2b's automatic widening: an int argument is acceptable where a double is required, because nothing is lost. The fourth is a compile error, and a useful one — it tells you at build time that you meant to supply three values.
Trap
The assignment does nothing. The parameter is assigned to itself.
public Time(int hour, int minute, double second) {
hour = hour; // parameter = parameter
minute = minute;
second = second;
}| written | left side refers to | right side refers to | effect on the object |
|---|---|---|---|
| hour = hour; | the parameter | the parameter | none |
| this.hour = hour; | the instance variable | the parameter | the value is stored |
Every object comes out with all-zero instance variables however you construct it, and the class compiles without complaint. Some compilers warn about self-assignment; many do not.
this on the left, naming the instance variable through the shadow.
public Time(int hour, int minute, double second) {
this.hour = hour;
this.minute = minute;
this.second = second;
}| rule | why |
|---|---|
| the compiler searches locals and parameters first | so a bare hour is the parameter |
this.hour names the attribute explicitly | regardless of shadowing |
| so the assignment copies parameter into attribute | which is what a value constructor is for |
You could avoid the problem by naming the parameters differently — int h, int m — and some programmers do. The convention is to use the same names and write this, because the parameter for the hour really is best called hour.
Prediction
Two constructors, matched by the arguments.
Time a = new Time();
Time b = new Time(9, 30, 0.0);| call | arguments | matches |
|---|---|---|
| new Time() | none | Time() |
| new Time(9, 30, 0.0) | int, int, double | Time(int, int, double) |
Predict first
What are a's instance variables?
Correct: 0, 0, 0.0
Why: new Time() has no arguments, so Java selects the no-argument constructor, which sets every instance variable to zero — midnight. Overload resolution is by the number and types of arguments, exactly as it is for ordinary methods.
Fill the middle
Copy the parameters into the instance variables.
Fill in the blanks
public Time(int hour, int minute, double second) this}.hour = hour;
this.minute = minute;
this.second = second;
}
Why: The left side must name the instance variable, which requires this because the parameter shadows it. The right side is the bare parameter name — the value the caller supplied. Getting these the wrong way round, or omitting this, silently stores nothing.
Counterexample
The parameters do not have to share the instance variables' names.
Discussion prompt
Writing public Time(int h, int m, double s) would remove the shadowing and let you write hour = h; with no this. Why is the shadowing version the convention anyway?
Hint: Who reads the constructor's signature?
Answer:
Because the parameter names are part of the documentation. Time(int hour, int minute, double second) tells a caller what to pass; Time(int h, int m, double s) makes them guess or look it up.
The cost is having to write this. on three lines, which is mechanical and visible. The benefit is paid to every future reader and every IDE showing parameter hints.
It is the same trade as this.x in Lesson 10b: a few extra characters in exchange for code that says what it means. And here it is also the reason this exists at all — Java provides it specifically so you can reach an instance variable through a shadow.
Section
Section 11.4
Concept
A class that uses objects defined in another class is called a client. Because Time's instance variables are private, a client cannot read or write them.
public class TimeClient {
public static void main(String[] args) {
Time time = new Time(11, 59, 59.9);
System.out.println(time.hour); // compiler error
}
}| message | meaning |
|---|---|
| hour has private access in Time | the variable exists but the client may not touch it |
| not: cannot find symbol | it is not misspelled or missing — it is hidden |
client — A class that uses objects defined in another class.
information hiding — The practice of making instance variables private to limit dependencies between classes.
There are three ways to solve this problem: make the instance variables public; provide methods to access them; or decide it is not a problem and refuse to let other classes reach them at all.
Notation
The first option is appealing because it is simple. The argument against it is about what happens later.
Annotate
Note what the argument is not about: it is not about preventing malice or protecting secrets. It is about being able to change one class without editing another — which is the difference between a program you can maintain and one you cannot.
Worked example
Make the argument concrete. Suppose Time later stores a total number of seconds rather than three separate values.
// version 1
private int hour;
public int getHour() {
return this.hour;
}
// version 2 - the storage changed completely
private double totalSeconds;
public int getHour() {
return (int) (this.totalSeconds / 3600);
}| clients using time.hour | clients using time.getHour() | |
|---|---|---|
| version 1 | work | work |
| version 2 | no longer compile | still work, unchanged |
| what the client knows | how Time stores its data | only that a Time has an hour |
Notice the client code is identical in both versions.
Why: time.getHour() is unchanged — the client never knew there were three variables.
Notice what the getter absorbed.
Why: The whole change in storage is hidden behind one method body.
Notice what a public variable could not do.
Why: time.hour names a specific variable. If that variable stops existing, every mention of it breaks.
Draw the general conclusion.
Why: A method is an interface; a variable is an implementation detail. Clients should depend on the first.
Verify: Ask yourself which version you would rather be maintaining after fifty classes have used Time.
Why: That is the whole argument. It costs a few lines per instance variable now and saves a search-and-replace across an entire program later — and the larger the program, the more decisive it becomes.
Prediction
Reaching a private instance variable from another class.
Time time = new Time(11, 59, 59.9);
System.out.println(time.hour);| possible message | correct here? |
|---|---|
| cannot find symbol | no — it exists |
| hour has private access in Time | yes |
Predict first
What does the compiler say?
Correct: hour has private access in Time
Why: The variable exists and the compiler knows about it — the objection is to the access, not the name. That is a useful distinction: cannot find symbol means misspelled or missing, while a private-access message means you are looking at something real that is deliberately out of reach.
Concept
The two visibility decisions in a class definition point in opposite directions, and each has a reason.
Figure (svg): A UML diagram for Time with private instance variables marked by minus signs and public methods marked by plus signs
| declared | so that | |
|---|---|---|
| the class | public | other classes can use the type at all |
| instance variables | private | clients do not depend on how data is stored |
| constructors | public | clients can create objects |
| getters and setters | public | clients can reach the data through an interface |
Read the UML: minus signs inside, plus signs below. The data is hidden and the behaviour is exposed, which is the shape of nearly every well-designed class you will meet.
Trap
The quickest fix, and the one that causes the problem the chapter is about.
public class Time {
public int hour; // now the client compiles
public int minute;
public double second;
}| what you gained | what you gave up |
|---|---|
| the compiler error went away | the freedom to change how Time stores data |
| one fewer method to write | every client now depends on these three names |
| shorter client code | a change here means changes everywhere |
It works, and it is what Point and Rectangle do. The difference is that Point's design is fixed — a point has an x and a y and always will. Time's storage is a decision you might revisit.
Provide methods, and keep the freedom to change your mind.
public class Time {
private int hour;
public int getHour() {
return this.hour;
}
}| ask | if yes |
|---|---|
| will this representation certainly never change? | public may be acceptable |
| might I store it differently later? | private, with a getter |
| should clients be able to change it at all? | if not, no setter |
The third row is worth noticing: a getter without a setter makes a value read-only from outside. That is a design choice private makes available and public does not.
Definition probe
Data hidden, behaviour exposed.
Sort into buckets
Sort each member of a well-designed class.
Trade off
Fill the blanks from Section 11.4's three options.
Comparison matrix
| option | client writes | what happens if the storage changes |
|---|---|---|
| make them public | time.hour | every client breaks |
| provide getters | time.getHour() | nothing — the getter absorbs the change |
| allow no access | only behaviour, never data | nothing |
The middle option is the usual answer, and the third is more common than beginners expect — Chapter 12's Card class exposes its behaviour and very little of its data.
Explain it
It is the most common misunderstanding of the idea.
Discussion prompt
A classmate says private instance variables are about stopping other people meddling with your data. Explain what the practice is actually for, using an example of a change you might want to make later.
Hint: Anyone with your source can change private to public in a second.
Answer:
It is not about stopping anyone — anyone with the source could just edit it. It is about what you are free to change later. If clients write time.hour, that variable has to exist forever. If they write time.getHour(), you can store the time however you like and just make the getter compute it.
So private is a promise to yourself: nothing outside this class depends on how I store things.
The word to reach for is dependency, not security. Think Java's own phrasing is about classes being isolated so that changes in one do not require changes in others.
Section
Section 11.4, continued
Concept
The second option is to provide methods that access the instance variables. Methods that read them are formally called accessors but more commonly getters.
public int getHour() {
return this.hour;
}
public int getMinute() {
return this.minute;
}
public double getSecond() {
return this.second;
}| convention | example |
|---|---|
| the method that gets something is called getSomething | getHour |
| it takes no parameters | getHour() |
| it returns the instance variable's type | int |
| it is public, and not static | an instance method |
By convention, the method that gets a variable named something is called getSomething. The client's compiler error is fixed by writing time.getHour() instead of time.hour.
Picture it
Providing getters without setters makes a class's data read only from outside — a design that public variables cannot express.
Figure (svg): Two panels comparing a class with getters only against one with both getters and setters
We might want the instance variables to be read only — code in other classes should be able to read them but not write them. Deciding that is a real design choice, and it is Chapter 10's mutable/immutable question made concrete in a class you wrote.
Worked example
If clients should also be able to modify the data, provide methods for that too. These are formally mutators and commonly setters.
public void setHour(int hour) {
this.hour = hour;
}
public void setMinute(int minute) {
this.minute = minute;
}
public void setSecond(double second) {
this.second = second;
}| getter | setter | |
|---|---|---|
| name | getSomething | setSomething |
| parameters | none | one, of the variable's type |
| returns | the variable's type | void |
| shadowing? | no | yes — hence this |
Name it setSomething.
Why: The naming convention is similar to the getter's — the method that sets something is usually called setSomething.
Take one parameter of the right type.
Why: Conventionally named the same as the instance variable, which shadows it.
Assign through this.
Why: this.hour = hour; — exactly the same shape as the value constructor, and the same trap if this is omitted.
Return void.
Why: A setter performs an action rather than producing a value.
Verify: Write a client that creates a Time, sets the hour, and reads it back.
Why: Writing getters and setters can get boring, and many IDEs generate them from the instance variables. That is a fair signal that the code carries no decisions — the decisions were which variables to expose and whether to allow writing.
Fill the middle
Return the minute.
Fill in the blanks
public int getMinute() return} this.minute;
}
Why: A getter returns the instance variable's own type — int for minute — and takes no parameters. Note there is no static: it is an instance method, invoked on a particular Time to get that object's minute.
Concept
A setter's body is a method body, so it can do more than store a value — and that is the main practical argument for having one.
public void setHour(int hour) {
if (hour < 0 || hour > 23) {
System.err.println("invalid hour: " + hour);
return;
}
this.hour = hour;
}| a setter can | a public variable cannot |
|---|---|
| validate the value | anything can be assigned |
| reject or clamp bad input | no check is possible |
| update something else as well | no |
| log or count changes | no |
| be removed later, making the class read-only | removing a public variable breaks every client |
The first row connects to Lesson 5b: validate at the boundary. A setter is a boundary — it is where values from outside the class arrive — so it is exactly where a guard belongs. time.hour = 99 cannot be prevented; time.setHour(99) can.
Trap
A getter and setter for every variable recreates public access with more typing.
private int hour;
public int getHour() { return this.hour; }
public void setHour(int hour) { this.hour = hour; }
// ...for every single instance variable| public variable | trivial getter + setter | |
|---|---|---|
| clients can read | yes | yes |
| clients can write anything | yes | yes |
| you can change the storage | no | yes |
| you can validate | no | yes — but this one does not |
The bottom two rows are real benefits, so this is not worthless. But a class that mechanically exposes every variable both ways has made no design decisions — it has just written more code.
Decide, per variable, what clients actually need.
// clients need to read the hour
public int getHour() { return this.hour; }
// but should never set it directly - only through add()
// so: no setHour at all| question per variable | consequence |
|---|---|
| should clients read it? | if not, no getter |
| should clients change it? | if not, no setter |
| should a change be checked? | put the check in the setter |
| is it purely internal? | neither — keep it hidden |
The point of private is that you get to choose. Writing both accessors for everything by reflex gives that choice away — which is why IDEs generating them is a convenience and not a design.
Definition probe
Naming conventions carry meaning.
Sort into buckets
Sort each method signature.
Prediction
Getters but no setters.
public class Time {
private int hour;
public Time(int hour) { this.hour = hour; }
public int getHour() { return this.hour; }
}| client writes | legal? |
|---|---|
| new Time(11) | yes |
| time.getHour() | yes |
| time.setHour(12) | no such method |
| time.hour = 12 | private access |
Predict first
Can a client change a Time's hour after creating it?
Correct: No — there is no setter and the variable is private
Why: The variable is unreachable from outside and no method changes it, so once constructed the object's hour is fixed. That makes the class effectively immutable — the same property Strings have, achieved deliberately by choosing not to write a setter.
Edge cases
IDEs will generate them all. Ask whether that is right.
Discussion prompt
You have a class with five private instance variables, one of which is an internal counter nothing outside should know about. Should it have a getter? What is the test?
Hint: What does adding a getter commit you to?
Answer:
No. A getter is part of your public interface, so adding one commits you to providing that value forever — even if the counter stops existing when you rewrite the class.
The test is: does a client have a legitimate reason to know this? If the answer is no, the getter gives away exactly the freedom that making the variable private was meant to keep.
This is why generating accessors for everything is a habit worth resisting. Private is a decision; a getter partly undoes it, and it should be undone deliberately rather than by reflex.
Comparison
Two classes doing the same kind of job, with opposite decisions about visibility. Fill the blanks.
Comparison matrix
| Point (Chapter 10) | Time (Chapter 11) | |
|---|---|---|
| instance variables | public | private |
| a client reads the hour with | p.x — direct access | time.getHour() — a method |
| if the storage changes | every client breaks | nothing breaks |
| who wrote it | the Java library | you |
Point's design is not wrong — a point's x and y are never going to be stored differently. The choice depends on how likely the representation is to change, and for a class you are still designing, that likelihood is high.
Pattern
Every class you write in the rest of this book has the same four parts, in the same order.
public class Time {
// 1. private instance variables
private int hour;
private int minute;
private double second;
// 2. a default constructor
public Time() {
this.hour = 0;
this.minute = 0;
this.second = 0.0;
}
// 3. a value constructor
public Time(int hour, int minute, double second) {
this.hour = hour; // this. on the left, always
this.minute = minute;
this.second = second;
}
// 4. getters, and setters only where clients need them
public int getHour() {
return this.hour;
}
}| part | the decision it records |
|---|---|
| the instance variables | what data every object carries |
| private | clients will not depend on how it is stored |
| the constructors | how an object can be created |
| which accessors exist | what clients may read and change |
this reaches an instance variable through a shadowing parameter.Check
Work it out before you click.
Check your understanding
Which of these is a constructor for the class Time?
Answer: A
Why: A constructor has the same name as the class, no return type at all, and no static keyword. The absence of a return type is what identifies it — adding void turns it into an ordinary method that merely shares the class's name.
void makes this an ordinary method called Time, so new Time(11) would not invoke it.time, not Time, so it is an ordinary method regardless of anything else.Check
Work it out before you click.
private int hour;
public void setHour(int hour) {
hour = hour;
}
// then: t.setHour(11);| name | resolves to |
|---|---|
| both occurrences of hour | the parameter |
| the instance variable | untouched |
Check your understanding
What is the object's hour afterwards?
Answer: A
Why: The compiler searches parameters before instance variables, so both sides of the assignment refer to the parameter and the object is never touched. The fix is this.hour = hour;, which names the instance variable explicitly through the shadow — and this is exactly why the keyword this exists.
this.Check
Work it out before you click.
public class Time {
private int hour;
public int getHour() { return this.hour; }
}
// in another class:
Time t = new Time();
System.out.println(t.hour);
System.out.println(t.getHour());| line | legal from another class? |
|---|---|
| t.hour | no — private |
| t.getHour() | yes — public method |
Check your understanding
Which line compiles?
Answer: A
Why: A private instance variable can be accessed only from inside its own class, so the client's t.hour is a compile error reading hour has private access in Time. The public getter is the supported way in — and the point of routing access through it is that Time can later change how the hour is stored without breaking this client.
Real world
Private fields with public accessors is so common in Java that IDEs generate it with a keystroke. It is worth asking what problem made it universal.
Discussion prompt
You are one of ten people working on a program with two hundred classes. What happens if your class exposes its instance variables and you later need to change how one of them is stored?
Hint: Who else has written code against that variable?
Answer:
Every class that touched it has to be found and edited — by people who did not write it and may not be available. The change ripples outward in proportion to how popular your class is.
With accessors, the ripple stops at your class's boundary. That containment is the entire value, and it grows with the size of the program — which is why the practice looks like ceremony in a two-class exercise and like survival in a two-hundred-class system.
It is also why the getter-and-setter-for-everything habit is criticised: it provides the containment without making the design decision. The useful version asks, per variable, whether clients should read it, write it, or neither.
Commit first
Commit to an answer and to your confidence.
Predict first
In a value constructor, why must you write this.hour = hour; rather than hour = hour;?
hour means the parameter on both sidesthis in every constructorCorrect: Because the parameter shadows the instance variable, so a bare hour means the parameter on both sides
Why: The compiler resolves a name by searching local variables, then parameters, then instance variables — so when a parameter shares a name with an instance variable, the parameter wins. hour = hour; therefore assigns the parameter to itself and leaves the object untouched, silently, with no error. this.hour names the instance variable explicitly and gets past the shadow, which is precisely the reason Java provides the keyword. Note that this is not required in general — it is required here, because of the shadowing.
Explain it
Two minutes, out loud.
Discussion prompt
A classmate asks why they should write getHour() when time.hour is shorter and does the same thing. Give them a concrete scenario in which the two versions behave differently, and say what the getter costs.
Hint: The difference appears later, not now.
Answer:
Today they do the same thing. Suppose next month you decide a Time should store a single total number of seconds instead of three variables. With a getter, you change one method body and every client keeps working. With a public variable, time.hour refers to something that no longer exists, and every client has to be found and edited.
What it costs is three lines per variable and one extra pair of parentheses at each use. That is the whole price.
If they push back that they will never change the storage — that is a fair answer for a small program, and it is exactly why the Java library's Point has public fields. The right answer depends on how likely the representation is to change.
Exit ticket
One question before you close the deck.
Predict first
What are the three ways a constructor's syntax differs from an ordinary method's?
Correct: Its name matches the class, it has no return type, and static is omitted
Why: Those three are exactly what identify a constructor. The missing return type is the decisive one — writing public void Time() produces an ordinary method that shares the class's name, which compiles and is never invoked by new. Note also what is not on the list: constructors can take parameters, can be overloaded, and need not appear first in the class.
Connect it up
One page, from memory.
Draw it
Write the Time class from memory: three private instance variables, a default constructor and a value constructor, and one getter. Circle every this and write beside each one what it refers to. Then draw the UML class diagram for what you wrote, using minus for private and plus for public. Finally, write one sentence explaining what would break in a client if you made the instance variables public and later changed how the time is stored.
Recap
Four sections that turn you from a user of classes into an author of them.
| if you remember one thing | it is this |
|---|---|
| about constructors | no return type, not even void |
| about this | it gets past a shadowing parameter |
| about private | it is about changeability, not secrecy |
new instantiates it.this refers to the object being created, and cannot be declared or assigned to.this.hour = hour; is required.Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.