Writing a Class: Constructors, Getters, and Setters

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

What this lesson covers

The lesson, slide by slide

1. Writing a Class: Constructors, Getters, and Setters

Title

Think Java 2e · Chapter 11 · Designing Classes

Sections 11.1-11.4 · pp. 183-190

2. What you will be able to do

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.

3. Retrieve before you read

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.

4. A class is a blueprint

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

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.

5. Instance variables and information hiding

Section

Section 11.1

6. Declare the data at the top of the class

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;
}
decisionchoicewhy
what datahour, minute, secondeverything a time of day needs
hour and minuteintthey are whole numbers
seconddoubleto keep things interesting — fractional seconds
where declaredat the start of the class, outside any methodthey 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.

7. public class, private variables

Notation

Two visibility keywords in four lines, and they point in opposite directions. That contrast is the whole design.

Annotate

  • The class is public, which means it can be used in other classes — other code can declare Time variables and create Time objects.
  • The instance variables are private, which means they can be accessed only from inside the Time class. Reading or writing them from another class is a compiler error.
  • This is the opposite of Point and Rectangle, whose x and y were public — which is exactly why Lesson 10a could write blank.x.
  • Private instance variables keep classes isolated from each other, so a change in one class does not require changes in others.
  • It also simplifies what other programmers need to know to use your class: they need the methods, not the storage.

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.

8. Choosing the instance variables

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;
}
stepdecision
identify the datahour, minute, second
choose each typeint, int, double
choose visibilityprivate
where to declareat 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.

9. Instance variable, local, or parameter?

Definition probe

Where it is declared decides what it is.

Sort into buckets

Sort each variable in a class you might write.

instance variable
hour, declared at the top of the Time class; second, declared at the top of the class
parameter
dx, in the header of a move method
local variable
sum, declared inside an add method
inst
Declared at the class level, outside any method, so every object of the class has its own and it lasts as long as the object.
par
Declared in a method's header and given its value by the caller at the moment of the call.
loc
Declared inside a method body, created on invocation and discarded on return.

10. Instance variables against everything else

Concept

This is the third kind of variable from Lesson 10b, now being declared by you rather than read from the library.

local variableparameterinstance variable
declaredinside a method bodyin a method headerin the class, outside any method
createdon invocationon invocationwhen the object is created
destroyedon returnon returnwhen the object is
one percallcallobject

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.

11. Declaring the data inside a method

Trap

The 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 declaredbelongs tosurvives the method?
inside setUpthe method callno
at the class levelthe objectyes

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.

The fix

Instance variables go at the beginning of the class, outside every method.

public class Time {
    private int hour;
    private int minute;
    private double second;
}
questionanswer
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.

12. Does this compile?

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?

  • Yes — a class with only instance variables is legal
  • No — every class needs at least one method
  • No — every class needs a main method
  • No — instance variables cannot be private

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.

13. Declare the instance variables

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.

14. Why hide data 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.

15. Constructors

Section

Section 11.2

16. A special method that initialises the object

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;
}
differencein this constructor
the name is the class's nameTime
there is no return typenot even void
static is omittedit 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.

17. What `this` means here

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.
  • You can read and write its instance variables, and pass this as an argument to other methods.
  • But you do not declare this, and you cannot assign to it. It is supplied by the language.
  • No return statement. A common error when writing constructors is to put one at the end — like void methods, constructors do not return values.
  • And no 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.

18. What new actually does

Worked example

Two steps, and now you can see both of them.

public static void main(String[] args) {
    Time time = new Time();
}
stepwhat happens
1Java creates the object — memory is allocated
2your constructor runs, initialising the instance variables
3new returns a reference to the new object
4the 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.

19. Constructor or ordinary method?

Definition probe

Three syntactic differences distinguish them.

Sort into buckets

Sort each header.

a constructor
public Time(); public Time(int h, int m, double s)
an ordinary method
public void setHour(int hour); public static Time add(Time a, Time b)
ctor
Named after the class, with no return type at all and no static keyword. The missing return type is the decisive clue.
meth
It has a return type — void or a class name — which makes it an ordinary method however it is named.

20. A Time object in memory

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.

21. Using new inside the constructor

Trap

The trap

Infinite recursion. The constructor calls itself, forever.

public Time() {
    new Time();             // StackOverflowError
    this.hour = 0;
    this.minute = 0;
    this.second = 0.0;
}
stepwhat happens
new Time() is calledinvokes the constructor
the constructor runs new Time()invokes the constructor again
and againeach call adds a frame
eventuallyStackOverflowError

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 fix

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;
}
misconceptionreality
the constructor creates the objectnew creates it; the constructor initialises it
I need to make a Time herethis already IS the Time
a constructor returns the objectnew 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.

22. What is wrong with this constructor?

Prediction

It looks like an ordinary method.

public void Time() {
    this.hour = 0;
}
writtenwhat 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?

  • It stops being a constructor and becomes an ordinary method
  • Nothing — void is optional on constructors
  • It causes a compile error
  • It makes the constructor return nothing, which is correct

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.

23. Write a default constructor

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.

24. Why do constructors have no return type?

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.

25. Value constructors and shadowing

Section

Section 11.3

26. Overloading: several constructors, different parameters

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 writewhich constructor runsresult
new Time()the no-argument one00:00:00.0
new Time(11, 59, 59.9)the three-argument one11:59:59.9
new Time(11)neither — no matchcompile 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.

27. Shadowing, and why `this` is needed

Notation

The parameters have the same names as the instance variables. That is conventional, deliberate, and the reason this is not optional here.

Annotate

  • Java allows parameters and local variables to have the same names as instance variables. They do not have to, but it is common practice — the parameter for the hour is naturally called hour.
  • The right side refers to the parameter, because it was declared most recently. This is Lesson 10b's name resolution: locals and parameters are searched before attributes.
  • This situation is called shadowing, because the parameter hides the instance variable of the same name.
  • 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.
  • Without 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.

28. The complete class so far

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;
    }
}
callhourminutesecond
new Time()000.0
new Time(11, 59, 59.9)115959.9
new Time(18, 50, 0.0)18500.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.

29. What does this constructor store?

Prediction

this is missing.

private int hour;

public Time(int hour) {
    hour = hour;
}
// then: Time t = new Time(11);
nameresolves to
the left hourthe parameter — searched first
the right hourthe parameter
the instance variablenever mentioned

Predict first

What is the object's hour afterwards?

  • 0 — the instance variable was never assigned
  • 11
  • a compile error
  • undefined garbage

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.

30. How Java picks which constructor

Concept

Overload resolution is by the number and types of the arguments — the same rule that applied to methods in Lesson 6b's substring.

callmatcheswhy
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)nothingno 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.

31. Forgetting this in a value constructor

Trap

The 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;
}
writtenleft side refers toright side refers toeffect on the object
hour = hour;the parameterthe parameternone
this.hour = hour;the instance variablethe parameterthe 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.

The fix

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;
}
rulewhy
the compiler searches locals and parameters firstso a bare hour is the parameter
this.hour names the attribute explicitlyregardless of shadowing
so the assignment copies parameter into attributewhich 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.

32. Which constructor is invoked?

Prediction

Two constructors, matched by the arguments.

Time a = new Time();
Time b = new Time(9, 30, 0.0);
callargumentsmatches
new Time()noneTime()
new Time(9, 30, 0.0)int, int, doubleTime(int, int, double)

Predict first

What are a's instance variables?

  • 0, 0, 0.0
  • 9, 30, 0.0
  • undefined
  • a compile error

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.

33. Write a value constructor

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.

34. Could you avoid shadowing entirely?

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.

35. Clients, and why private

Section

Section 11.4

36. A client cannot reach private data

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
    }
}
messagemeaning
hour has private access in Timethe variable exists but the client may not touch it
not: cannot find symbolit 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.

37. Why not just make them public?

Notation

The first option is appealing because it is simple. The argument against it is about what happens later.

Annotate

  • When class A accesses the instance variables of class B directly, A becomes dependent on B. If anything in B changes later, it is likely A will have to change too.
  • If A uses only methods to interact with B, they are less dependent — you can change B without affecting A, as long as you do not change the method parameters.
  • So we generally avoid making instance variables public. That sentence is the chapter's design conclusion, and it is the reason Time differs from Point.
  • Option 3 is a real option, not a joke. Some classes genuinely should not expose their data — Chapter 12's Card is one.

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.

38. What a getter lets you change later

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.hourclients using time.getHour()
version 1workwork
version 2no longer compilestill work, unchanged
what the client knowshow Time stores its dataonly 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.

39. What error does the client get?

Prediction

Reaching a private instance variable from another class.

Time time = new Time(11, 59, 59.9);
System.out.println(time.hour);
possible messagecorrect here?
cannot find symbolno — it exists
hour has private access in Timeyes

Predict first

What does the compiler say?

  • hour has private access in Time
  • cannot find symbol: hour
  • nothing — it compiles
  • NullPointerException

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.

40. Public class, private data

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

declaredso that
the classpublicother classes can use the type at all
instance variablesprivateclients do not depend on how data is stored
constructorspublicclients can create objects
getters and setterspublicclients 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.

41. Making everything public to make the error go away

Trap

The 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 gainedwhat you gave up
the compiler error went awaythe freedom to change how Time stores data
one fewer method to writeevery client now depends on these three names
shorter client codea 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.

The fix

Provide methods, and keep the freedom to change your mind.

public class Time {
    private int hour;

    public int getHour() {
        return this.hour;
    }
}
askif 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.

42. public or private?

Definition probe

Data hidden, behaviour exposed.

Sort into buckets

Sort each member of a well-designed class.

public
the class itself; a constructor; getHour()
private
an instance variable holding the hour; an instance variable holding an internal counter
pub
Part of how other classes use this one — the type, the way to create it, and the behaviour it offers.
priv
An implementation detail. Keeping it hidden means you can change how the class stores its data without breaking any client.

43. Three ways to handle private data

Trade off

Fill the blanks from Section 11.4's three options.

Comparison matrix

optionclient writeswhat happens if the storage changes
make them publictime.hourevery client breaks
provide getterstime.getHour()nothing — the getter absorbs the change
allow no accessonly behaviour, never datanothing

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.

44. Explain information hiding without saying 'security'

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.

45. Getters and setters

Section

Section 11.4, continued

46. One method per instance variable

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;
}
conventionexample
the method that gets something is called getSomethinggetHour
it takes no parametersgetHour()
it returns the instance variable's typeint
it is public, and not statican 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.

47. Read-only, or read-write

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.

48. Adding setters

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;
}
gettersetter
namegetSomethingsetSomething
parametersnoneone, of the variable's type
returnsthe variable's typevoid
shadowing?noyes — 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.

49. Write a getter

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.

50. What a setter can do that a public variable cannot

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 cana public variable cannot
validate the valueanything can be assigned
reject or clamp bad inputno check is possible
update something else as wellno
log or count changesno
be removed later, making the class read-onlyremoving 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.

51. Getters and setters that expose everything anyway

Trap

The 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 variabletrivial getter + setter
clients can readyesyes
clients can write anythingyesyes
you can change the storagenoyes
you can validatenoyes — 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.

The fix

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 variableconsequence
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.

52. Getter, setter, or neither?

Definition probe

Naming conventions carry meaning.

Sort into buckets

Sort each method signature.

a getter
public int getHour(); public double getSecond()
a setter
public void setHour(int hour)
neither
public Time add(Time t); public boolean equals(Time that)
get
Named getSomething, takes no parameters, and returns the instance variable's type. Its whole job is to hand back a stored value.
set
Named setSomething, takes one parameter of the variable's type, and returns void.
other
A method that does real work rather than simply exposing a variable — which is what most of a well-designed class consists of.

53. What can a client do with this class?

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 writeslegal?
new Time(11)yes
time.getHour()yes
time.setHour(12)no such method
time.hour = 12private access

Predict first

Can a client change a Time's hour after creating it?

  • No — there is no setter and the variable is private
  • Yes, with time.hour = 12
  • Yes, with time.setHour(12)
  • Only from inside the same package

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.

54. Should every private variable get a getter?

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.

55. Time against Point

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 variablespublicprivate
a client reads the hour withp.x — direct accesstime.getHour() — a method
if the storage changesevery client breaksnothing breaks
who wrote itthe Java libraryyou

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.

56. The pattern to carry away

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;
    }
}
partthe decision it records
the instance variableswhat data every object carries
privateclients will not depend on how it is stored
the constructorshow an object can be created
which accessors existwhat clients may read and change

57. Check: constructor syntax

Check

Work it out before you click.

Check your understanding

Which of these is a constructor for the class Time?

  • A. public Time(int hour) { this.hour = hour; } (correct)
  • B. public void Time(int hour) { this.hour = hour; }
  • C. public static Time Time(int hour) { ... }
  • D. public Time time(int hour) { ... }

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.

Why B tempts people
The void makes this an ordinary method called Time, so new Time(11) would not invoke it.
Why C tempts people
It has both a return type and static, so it is a static method — not a constructor.
Why D tempts people
The name is time, not Time, so it is an ordinary method regardless of anything else.

58. Check: shadowing

Check

Work it out before you click.

private int hour;

public void setHour(int hour) {
    hour = hour;
}
// then: t.setHour(11);
nameresolves to
both occurrences of hourthe parameter
the instance variableuntouched

Check your understanding

What is the object's hour afterwards?

  • A. Unchanged — the parameter was assigned to itself (correct)
  • B. 11
  • C. A compile error
  • D. 0, because the assignment cleared it

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.

Why B tempts people
That would require the left side to name the instance variable, which needs this.
Why C tempts people
Self-assignment is legal Java; some compilers warn about it but it is not an error.
Why D tempts people
Nothing assigns zero — the instance variable simply keeps whatever it already held.

59. Check: private access

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());
linelegal from another class?
t.hourno — private
t.getHour()yes — public method

Check your understanding

Which line compiles?

  • A. Only t.getHour() (correct)
  • B. Only t.hour
  • C. Both
  • D. Neither

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.

Why B tempts people
This is exactly backwards: the variable is hidden and the method is exposed.
Why C tempts people
Private really does prevent access from other classes; it is enforced by the compiler.
Why D tempts people
The getter is public and returns an int, so printing its result is fine.

60. Why every professional Java class looks like this

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.

61. How sure are you?

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;?

  • Because the parameter shadows the instance variable, so a bare hour means the parameter on both sides
  • Because Java requires this in every constructor
  • Because instance variables cannot be assigned directly
  • Because the parameter is read-only

Correct: 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.

62. Explain it to someone else

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.

63. Exit ticket

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?

  • Its name matches the class, it has no return type, and static is omitted
  • It is always public, always void, and always first in the class
  • It takes no parameters, returns the object, and cannot be overloaded
  • It has no name, no parameters and no body

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.

64. Draw the whole lesson

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.

65. Recap

Recap

Four sections that turn you from a user of classes into an author of them.

if you remember one thingit is this
about constructorsno return type, not even void
about thisit gets past a shadowing parameter
about privateit is about changeability, not secrecy

Sources

  1. 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
  2. The Java Tutorials — Providing Constructors for Your Classes
  3. Think Java 2e — free online edition and source code

Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108