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