Learn Object-Oriented Java the Hard Way

Exercise 9: Private Fields and Constructors

Assuming you did the last exercise, you have seen that some classes won’t work properly if you change their fields directly instead of going through their methods. In this exercise, you’ll learn how to put a stop to that.

You’ll also learn about something that’ll make it easier for others to use your classes and make it safer, too.

SphereCalc3.java
 1 public class SphereCalc3 {
 2     private double radius, area, volume;
 3 
 4     public void setRadius( double r ) {
 5         radius = r;
 6         area = 4*Math.PI*r*r;
 7         volume = 4*Math.PI*Math.pow(r,3) / 3.0;
 8     }
 9 
10     public double getRadius()      { return radius; }
11     public double getSurfaceArea() { return area;   }
12     public double getVolume()      { return volume; }
13 }

This is basically SphereCalc2, just shrunk down. When all you’re doing in a method is returning the value of a single variable, Java programmers often write the “getter” methods all on one line like I did in lines 10-12.

So the only interesting change is at the beginning of line 2: the keyword private.

You’ve been making things public since your first Java program ever thanks to “public static void main”, so maybe you suspected.

Instance variables are typically made private. Almost always, as a matter of fact. (You can designate methods as private instead of public, too, but we won’t see an example of that for a while.)

Private means “DON’T TOUCH!” Any private variable can’t be accessed in any way outside of the class where it is defined.

Inside the class, private variables work just like the fields we have been using; any method inside the class is free to change or access private variables just the same.

“Public” and “private” are called “access level modifiers” in Java. When you leave them out (like we’ve been doing with our fields since exercise 4) the default access is called “package-private”, which means that they’re accessible to anything inside the same “package”. All the classes we’ve written so far are all inside the same package, but we won’t do anything about that until close to the end of the book.

So for our purposes, “public” variables and variables with no package modifier are equivalent: they can be accessed or changed from outside their class. And that’s a bad thing.

Java programmers are typically pretty strict about making fields private. In fact, on the Advanced Placement Computer Science exam, failing to mark an instance variable as private is so serious that it can cost you more than 10% of your score on a question, even if every other part of your solution is perfect!

Anyway, back to the main point. Now that the fields are private, code that uses the SphereCalc3 class has no choice; they can only change the radius through the setRadius() method. Attempts to do it directly won’t even compile.

SphereCalc3 sph = new SphereCalc3();  
sph.radius = 7;   // <-- This won't even compile.  
sph.setRadius(7); // This works just fine, of course.  
System.out.println( sph.radius ); // still won't compile

As you can see, this applies even if you’re not trying to change the instance variable. private doesn’t just prevent modifying the field, it prevents accessing it, too. That’s why you have to write public “getter” methods for every variable you want accessible.

Some programming languages (like C#) have a slightly different way of dealing with this problem; you can mark variables as read-only so they can’t be changed from outside the class (only through methods) but they can still be read. Other languages have a way of making it look like you’re accessing a variable directly, but they’re really secretly running a method to set or read the variable.

In Java, however, private fields with setters and getters are the only good solution.

Now, one more potential problem before we move on. It is a little bit annoying to have to always remember to call the setter method before doing anything else. Look at some examples from the past several exercises:

SphereCalc sph = new SphereCalc();  
sph.setRadius(5);  
// now it's safe to use the other methods in SphereCalc  
//
SquareRootFinder srf = new SquareRootFinder();  
srf.setNumber(n);  
// now it's safe to use the other methods in SquareRootFinder  
//
PhraseRepeater pr = new PhraseRepeater();  
pr.setValues(msg, n);  
// now it's safe to use the other methods in PhraseRepeater  
//
StringFunObject sfo = new StringFunObject();  
sfo.setMessage(msg);  
// now it's safe to use the other methods in StringFunObject

Not only is this annoying, it’s not safe. I won’t make you do it in the Study Drills, but if you accidentally forgot to call setNumber() or setValues() the driver would still compile, but it wouldn’t work properly. And as much as I hate compile-time errors, I hate it a lot more when I have code that compiles but doesn’t work.

Fortunately, there’s a solution! A special sort-of setter method called a “constructor”. Here’s an example.

SphereCalc4.java
 1 public class SphereCalc4 {
 2     private double radius, area, volume;
 3 
 4     public SphereCalc4( double r ) {
 5         radius = r;
 6         area = 4*Math.PI*r*r;
 7         volume = 4*Math.PI*Math.pow(r,3) / 3.0;
 8     }
 9 
10     public void setRadius( double r ) {
11         radius = r;
12         area = 4*Math.PI*r*r;
13         volume = 4*Math.PI*Math.pow(r,3) / 3.0;
14     }
15 
16     public double getRadius()      { return radius; }
17     public double getSurfaceArea() { return area;   }
18     public double getVolume()      { return volume; }
19 }

Lines 4 through 8 are the implementation of the constructor. Notice on line 4 that unlike the setRadius() setter/mutator method, the constructor is not void. Constructors have no return type specifier at all; it’s just missing.

Also notice that the constructor has the same name as the class itself. This is required. If you do those two things, then instead of having to remember to call some special method to pass in initial values for the instance variables, you get to pass them in while you’re instantiating the object. Which you would have to do anyway! Like so:

SphereCalc4 sc = new SphereCalc4( 5 );  
// it's safe right away to use the other methods in SphereCalc  
SquareRootFinder srf = new SquareRootFinder(n);  
// ditto  
PhraseRepeater pr = new PhraseRepeater(msg, n);  
StringFunObject sfo = new StringFunObject(msg);

This makes a little more work when implementing a class, because you usually have to write a constructor and also write your setter methods. But it makes it easier to work with your object and safer, too.

Okay, that’s enough for now. We’ll see plenty more constructors in the chapters to come.


“Learn Object-Oriented Java the Hard Way” is ©2015–2016 Graham Mitchell