In the first chapter we started designing a hotel,
now that we know enough to actually start building something in Java,
though, that is a bit too ambitious for our first project.
So let's start with something simple you'll find in any hotel---lamps.
Every lamp must be able to at least turn on and turn off,
and know whether it's on or off.
To model the lamp's behavior we need methods to turn it on and off,
and to model the lamp's state (that of being on or off)
we need a variable, say, isOn
,
to remember its current state.
Since this variable only needs two states
it's reasonable to make it a boolean
,
with the understanding that when it's true
, the lamp is on,
and when it's false
, the lamp is off.
The program is in two parts.
First, here is a class defining what it means to be a lamp:
/*
Define a lamp that can turn on and off.
*/
class Lamp
{
//this lamp is either on or off
boolean isOn;
/*
Turn this lamp on.
*/
void turnOn()
{
isOn = true;
}
/*
Turn this lamp off.
*/
void turnOff()
{
isOn = false;
}
}
And here is a class that uses such lamps, perhaps a small part of a hotel:
import Lamp;
/*
Create some lamps and fiddle with them.
*/
class Lobby
{
public static void main(String[] parameters)
{
Lamp kandinsky;
kandinsky = new Lamp();
kandinsky.turnOn();
kandinsky.turnOff();
kandinsky.isOn = true;
Lamp bauhaus;
bauhaus = new Lamp();
bauhaus.turnOn();
bauhaus.turnOff();
}
}
Programs and Classes
Unlike our first program, SimpleRole
,
this program has two classes, rather than only one.
Further, class Lamp
doesn't have a main()
method,
while class Lobby
does.
Every Java program we write must have at least one class
where we declare a main()
method as follows:
public static void main(String[] parameters)
As we've seen, such a method is Act I, Scene I for the whole play.
Not every class, however, needs a
main()
method.
For example, if class
A
objects
can create class
B
objects,
and if class
B
objects
can create class
C
objects,
then neither class
B
nor class
C
needs a
main()
method.
Class
A
may not need one either,
if yet other classes of objects can create objects of its class.
Writing a Java program is like writing a play script
then separating each actor's lines and stage directions into separate chunks.
These chunks are Java classes;
they describe the actors' roles,
and we can create as many actors as we wish to play the same role.
Importing Classes
When defining a class that needs other classes to function,
we should precede the class definition
with a statement specifying any other class, or classes, the class needs.
We signal that to the Java interpreter with the word import
.
Class Lobby
needs class Lamp
to work properly, so we should import
class Lamp
,
like so:
import Lamp;
All
import
statements
must appear before the definition of the class
that needs the classes we're
import
ing.
Controlling Access
Class Lamp
isn't a good model of a real lamp.
For example, objects can turn a Lamp
object on or off
in two different ways.
One object could ask a lamp to execute its turnOn()
method,
while another could set the object's isOn
variable
to false
.
The first object thinks the lamp is on, the second thinks it's off.
Which is right?
Altering the lamp's isOn
variable
without going through either of the two methods
seems like hotwiring a car rather than using its key.
We can ensure that no non-Lamp
object
can turn a lamp on or off that way
by making its isOn
variable private
.
While we're at it, let's make both Lamp
methods
public
.
This guarantees that any object that can create a lamp
can also ask that lamp to execute either of its Lamp
methods.
class Lamp
{
private boolean isOn;
public void turnOn()
{
isOn = true;
}
public void turnOff()
{
isOn = false;
}
}
Objects playing other roles but using Lamp
objects
now cannot access any lamp's copy of its isOn
variable
(it's private
),
but they can always ask the lamp
to execute its turnOn()
and turnOff
methods
(they're public
):
import Lamp;
class Lobby
{
public static void main(String[] parameters)
{
Lamp kandinsky;
kandinsky = new Lamp();
kandinsky.turnOn();
kandinsky.turnOff();
//kandinsky.isOn = true; //this is now illegal
}
}
Understanding Privacy in Java
It's tempting to think that making a prop private
makes each copy of the prop private to its own specific lamp.
This is not so.
It's true that no non-Lamp
actor can alter the prop,
but any Lamp
actor alter the prop.
So, in terms of privacy,
the best we can do is disbar actors playing other roles
from altering a particular prop
that we define as private
for this role.
So the statement:
private boolean isOn;
declares that only
Lamp
objects
can access the
boolean
variable
isOn
.
We can also make methods private
, and with the same effect.
Normally, though, it's better to ask the stage manager
to let any actor at all request a lamp to execute a particular method,
whether the actor plays the Lamp
role or not.
We do that using the word public
, like so:
public void turnOn()
It's good style to make all variables
private
and all methods
public
.
Later, though, we'll discover some small modifications to that rule.
Reporting State
Now that we've made every copy of isOn
private
,
no non-Lamp
object can find out the internal state of any lamp.
The effect is to completely seal every lamp.
Except for another lamp,
the only object that can know whether a particular lamp is on or off
is the same object that turned the lamp on or off in the first place.
So if other objects need to know whether a particular lamp is on or off
we need some way for the lamp to report its internal state.
Let's add a new public
method to class Lamp
so that any object can query a lamp
to find out whether it's currently on or off:
class Lamp
{
private boolean isOn;
public void turnOn()
{
isOn = true;
}
public void turnOff()
{
isOn = false;
}
/*
Report whether this lamp is on or off.
*/
public boolean isOn()
{
return isOn;
}
}
The new method, isOn()
,
returns a boolean
value,
so we must declare its return type as boolean
,
just as we had to declare the return type of
divideApplesAmongPeople()
as double
.
The other two methods don't return values,
so we must declare them void
,
just as we declare main()
.
Encapsulating Lamps
It may seem silly to make the isOn
variable
private
then turn around and add a method to report its state,
but doing it this way gives the lamp
more control over who it reports its state to.
When its variables aren't private
,
any object at all can come along
and alter what should be its internal state.
By protecting its variables and providing well-defined access points
to them only through methods,
each object takes more control over its own state,
and so makes it less likely
that another object could incorrectly alter its state.
This encapsulation idea isn't that important
when the program is small,
but it becomes more and more important
as the program grows larger and more complicated.
The more complex a program becomes,
the more likely it is that someone, somewhere, will goof.
The more protection we can put in to prevent that goof,
the more likely it is that the goof won't harm the program's users.
Using boolean
Expressions
So far, we've seen two if
statements in SimpleRole
:
if (numberOfPeople <= 0)
return 0;
and
if (estragon.finishedDividing == false)
return;
The general form:
if (booleanExpression)
[some statement]
is all one statement, possibly spread over several lines,
and with no separate semicolon to terminate its parts.
It asks an actor to evaluate the
booleanExpression
,
and if it is
true
then to execute the enclosed statement
(the statement immediately following the
if
),
otherwise to jump past the whole
if
statement
and continue execution with the subsequent statement, if any.
We can also select which of two statements an actor executes next
using a statement of the following form:
if (booleanExpression)
[some statement]
else
[some other statement]
This says that if the
booleanExpression
is
true
then execute the first enclosed statement next,
otherwise execute the second enclosed statement next.
Just as with the first kind of if
statement,
an if-else
statement is all one statement
even though it's continued over several lines
and doesn't have its own separate semicolon to terminate it.
As with class and method definitions,
the Java interpreter can figure out where it ends for itself
since it's all in one fixed format.
We can use the if-else
statement
to ask objects of the new version of class Lamp
if they're on or not:
import Lamp;
class Lobby
{
public static void main(String[] parameters)
{
Lamp kandinsky;
kandinsky = new Lamp();
kandinsky.turnOn();
kandinsky.turnOff();
if (kandinsky.isOn() == true)
System.out.println("kandinsky is on.");
else
System.out.println("kandinsky is off.");
}
}
Reporting State Changes
Our lamps now respond when other objects ask them their current state,
but they don't tell anybody when they change state.
One day, though, we might decide to display our lamps on the screen.
If so, we'll need some way for them to signal the object displaying them
that they are changing state.
Let's add that capability next:
/*
Define a lamp that can turn on and off,
report its state, and report its state changes.
*/
class Lamp
{
private boolean isOn;
public void turnOn()
{
isOn = true;
reportState();
}
public void turnOff()
{
isOn = false;
reportState();
}
public boolean isOn()
{
return isOn;
}
/*
Report this lamp's status.
*/
public void reportState()
{
if (isOn == true)
System.out.println("I am on.");
else
System.out.println("I am off.");
}
}
Requesting Methods Within Methods
Having lamps report their state changes brings up a puzzle.
After altering the isOn
variable,
both of Lamp
's new
turnOn()
and turnOff()
methods
now request execution of the reportState()
method.
As we've seen, however, we can only execute a method
by asking an object of the class we define the method in
to execute the method.
So when an object is executing its turnOn()
method,
what object is it asking to execute the reportState()
method?
The answer is the same object that is currently executing
the turnOn()
method.
That object is asking itself
to execute its own reportState()
method.
The stage manager makes sure that each of our method execution requests
is a request to some particular object to execute the method.
If we don't specify an object to execute the method,
the stage manager secretly puts a self-reference
in front of the method execution request.
That self-reference is the word "this
".
It's the same as saying that whoever is presently executing the method
should also be the one to execute the new method request.
Think of this
as short for a reference variable named
theObjectExecutingThisParticularStatement
.
There is no such thing as an actorless action in Java.
An actor executes its scenes only when asked to by another actor
(or by itself, or by the stage manager).
So what looks like a bare request
to execute some generic reportState()
method
is a normal method execution request to an object,
just as we're used to so far.
In other words, the statement:
reportState();
is exactly the same as the statement:
this.reportState();
An actor executing a scene containing this statement
is asking itself to execute its
reportState()
scene next.
The same is true for accessing variables.
Thus, the following two methods are exactly the same:
public void turnOn()
{
isOn = true;
reportState();
}
|
public void turnOn()
{
this.isOn = true;
this.reportState();
}
|
If kandinsky
executes the turnOn()
method,
kandinsky
sets its copy
of the isOn
variable to true
then it asks itself to execute its reportState()
method.
Similarly, if bauhaus
executes the method,
everything is in terms of bauhaus
' variables and methods.
In each case, this
lets an object refer to itself.
Style Crime
Earlier we had two different ways to turn on a lamp---by
asking the lamp to execute its turnOn()
method
or by going in an monkeying with the lamp's isOn
variable
directly.
Having two different ways to do anything is usually a crime against good style
because, for one thing, it can lead to indeterminacy, as we saw earlier.
The reportState()
method
is another example of the same style crime.
It tests the value of the isOn
variable
to find out whether the lamp is on or not,
while the isOn()
method
already returns the value of the isOn
variable.
Instead of having the object that's executing reportState()
monkey with isOn
,
we should have it execute isOn()
to see if the lamp is on.
That makes isOn()
the sole access point for any object,
including the lamp itself, to find out if it's on or not.
If we then decide, say,
to change the name of isOn
to lampOn
,
we don't have to change it in reportState()
,
we only have to change it in isOn()
.
Forcing unique access points increase each lamp's encapsulation,
which decreases the chance that changing class Lamp
will break it.
Here is the modified code:
public void reportState()
{
if (this.isOn() == true)
System.out.println("I am on.");
else
System.out.println("I am off.");
}
We could, of course, use the form:
if (isOn() == true)
instead of:
if (this.isOn() == true)
but the second version is more stylish;
it's clearer just which lamp we're asking is on or not, namely:
we're asking
this lamp---the
lamp currently executing the
reportState()
method.
Initializing Variables
Now that we've added methods for a lamp to report its state,
another problem pops up.
Currently, when we create a lamp it's not automatically off.
It's not automatically on, either.
It's neither, because it doesn't set its isOn
variable
to true
or false
until someone asks it to execute
one of its turnOn()
or turnOff()
methods.
Oops.
We can ensure that when we create a lamp it will be off
by declaring the isOn
variable as follows:
private boolean isOn = false;
This says that, initially at least, every lamp will be off.
The Java interpreter lets us specify an initial value for any variable
when we declare its name and type.
As soon as the variable exists it will have its initial value.
If we don't specify a value,
the variable will still have a value,
but nothing that we can depend on.
In that case, the stage manager won't let us use the variable
until we give it a definite value.
Adding a Constructor
Initializing isOn
when declaring it
is a simple solution to our last problem, but it may not be the best one.
Suppose we had several variables to initialize
and some of them depended on values in other objects
for their correct initial state.
Really, what we want is some arbitrary amount of code
that executes as soon as we create an object.
Here's a more general solution to the initialization problem:
/*
Define a lamp that can turn on and off,
report its state, report its state changes,
and make sure it's off on creation.
*/
class Lamp
{
private boolean isOn;
/*
Set this lamp's initial state as being off.
*/
public Lamp()
{
isOn = false;
}
//remaining code goes here...
}
The new code looks like a method, but it isn't.
It's a
constructor.
We can ask an object to execute its methods whenever we want,
but we can't ask it to execute its constructor at all.
Nor can we specify return types for constructors, as we must for methods.
Further, we can't explicitly
return
from a constructor.
Finally, we must name a constructor the same as the class
it constructs objects for.
If we name a method the same as the class we define it in
(which is legal, but a really bad style crime)
the Java interpreter tells whether it's supposed to be a constructor or not
by whether it has a return type or not.
Despite its misleading name, a constructor doesn't construct objects;
it initializes them.
Constructors set the initial state of the object
to be something reasonable for a newly created object of its class.
When we write a ``new
'' statement,
we are asking the stage manager to create a new object
and then to execute its constructor (if any).
Adding Multiple Constructors
A class can have any number of constructors,
each specialized for a different kind of initialization of its objects.
Suppose, for example, that we wanted to give our lamps a wattage.
A lamp's wattage would be another part of its state,
just as whether it's presently on or off is.
First, we'd have to add a new variable to class Lamp
to remember each lamp's wattage.
Since we usually measure wattage in whole units,
it's reasonable to make this an int
,
and call it, say, wattage
.
Then, we'd need a way to set the wattage.
Having a setWattage()
method, say,
would be a style crime because a lamp's wattage won't ever change.
Once a lamp exists, it has a fixed wattage.
If we were to give class Lamp
a method to alter a particular lamp's wattage,
some object, somewhere, might ask a lamp to set its wattage incorrectly.
The best thing would be to set each lamp's wattage once and for all
when we create it.
So we should put that code in the constructor.
But the constructor we have now isn't specific to lamps with wattages.
We could, of course, change the constructor.
Instead, let's add another constructor;
one that takes a wattage as a parameter.
/*
Define a lamp that can turn on and off,
report its state, report its state changes,
have a wattage, and make sure it's off on creation.
*/
class Lamp
{
private boolean isOn;
//this lamp has a wattage;
private int wattage;
/*
Set this lamp's initial state
as being a 60-watt lamp in the off position.
*/
public Lamp()
{
isOn = false;
wattage = 60;
}
/*
Set this lamp's initial state
as being in the off position.
Set this lamp's wattage to the given wattage.
*/
public Lamp(int wattage)
{
isOn = false;
this.wattage = wattage;
}
//remaining code goes here...
}
The reference variable this
clarifies
which wattage
is which in the statement:
this.wattage = wattage;
The left-hand side specifies the
wattage
the lamp keeps,
and the right-hand side specifies the
wattage
that was passed into the constructor.
The statement thus says to copy the value of the parameter
passed into the constructor into the lamp's
wattage
variable.
More Style Crimes
We could have named the second constructor's parameter something else, say,
lampWattage
.
That would avoid us having to use this
since we could then write:
wattage = lampWattage;
inside the constructor, instead of
this.wattage = wattage;
But going to such lengths just to avoid using this
is a style crime.
A variable's name is an important thing;
we should chose it with great care
to remind ourselves exactly what the variable is for.
To then have to make up another name
for essentially the same thing is foolishness.
Further, other programmers reading our code
would then have to remember, and disambiguate,
two names for essentially the same thing.
That is a style crime.
We will see more of them as we continue coding.
Choosing Among Constructors
Class Lamp
now has two constructors.
To figure out which one to execute,
the Java interpreter examines the parameters, if any,
that we supply in the lamp's new
object creation request.
If we say:
Lamp someLamp;
someLamp = new Lamp();
we'll get a standard 60-watt lamp
since inside the constructor that takes no parameters
we set the lamp's default
wattage
to 60 watts.
If, however, we say:
Lamp someOtherLamp;
someOtherLamp = new Lamp(100);
we'll get a 100-watt lamp
since inside the constructor that takes one
int
parameter
we set the lamp's
wattage
to the value of the parameter.
Here's a class to test that:
import Lamp;
class Lobby
{
public static void main(String[] parameters)
{
//create a (normal) 60-watt lamp
Lamp kandinsky;
kandinsky = new Lamp();
kandinsky.turnOn();
kandinsky.turnOff();
//create a 100-watt lamp
Lamp bauhaus;
bauhaus = new Lamp(100);
bauhaus.turnOn();
bauhaus.turnOff();
}
}
The signature of a method or constructor
is the sequence, number, and types of parameters it takes.
Having two constructors with different signatures
is the same as having an if
test
embedded in the object creation request
that chooses among the constructors based on their signatures.
Two or more methods can have the same names
as long as they have different signatures.
The same goes for constructors.
Which clears up a little mystery from chapter one,
namely, why must we follow the role name with parentheses
in every new
object creation request?
It's because we're requesting a particular constructor
to initialize the object after its creation.
Declaring Constants
Suppose that we want to place a limit on the highest wattage a lamp can have.
From what we've seen so far,
we could create a new variable, say, MAXIMUM_WATTAGE
,
and set its value during its declaration,
as we once did for isOn
.
Then, inside the second constructor,
we can test the value of the wattage
parameter
against MAXIMUM_WATTAGE
.
This will work.
However, somewhere else we might accidentally change the value of
MAXIMUM_WATTAGE
and not realize it.
We need some way to make sure that
once we set its value in its declaration,
we cannot ever reset it.
We can use the word final
to ask the stage manager to disallow changes to the value inside a variable.
So the following statement asks the stage manager
to produce a private
box
containing the int
value 500,
and it also asks the stage manager to make sure
the value in the box never changes:
private final int MAXIMUM_WATTAGE = 500;
If we declare a variable without modifying it with the word
final
then we can change its value any time we want.
Here is the beginning of the latest version of the class:
class Lamp
{
private boolean isOn;
private int wattage;
//this lamp has a maximum possible wattage
private final int MAXIMUM_WATTAGE = 500;
/*
Set this lamp's initial state
as being a 60-watt lamp in the off position.
*/
public Lamp()
{
isOn = false;
wattage = 60;
}
/*
Set this lamp's initial state
as being in the off position.
Set this lamp's wattage to the given wattage.
Do not let any lamp have a wattage higher than MAXIMUM_WATTAGE.
*/
public Lamp(int wattage)
{
isOn = false;
if (wattage > MAXIMUM_WATTAGE)
this.wattage = MAXIMUM_WATTAGE;
else
this.wattage = wattage;
}
//remaining code goes here...
}
Naming Constants
Unfortunately, the Java interpreter lacks a special word
to signify that a box can only hold a constant value,
thereby forcing us to keep both final
and non-final
values in "variables",
even though one can't vary and the other can.
To emphasize the difference to ourselves at least,
it's customary to name final
variables
(variables-that-are-constant) differently than
non-final
variables (variables-that-can-vary)
(sigh; only computer science and politics allow this kind of nonsense).
The convention is to name constants with all-uppercase letters,
and to separate any words in the name with underscores.
So MAXIMUM_WATTAGE
will always have a constant value,
while wattage
can have a varying value.
Hopefully, a future language will make this distinction explicit.
Adding Class Variables
While the latest version of the program will work,
dragging in the idea of a maximum wattage
has brought with it yet another problem.
Recall that every Lamp
object we ever create
must have a copy of all the variables we declare in that class.
Well, that's fine for a lamp's
isOn
and wattage
variables,
since both are part of the lamp's state
(they can vary from lamp to lamp),
but it isn't so sensible for a lamp's MAXIMUM_WATTAGE
variable.
That isn't a "variable" at all---it's a constant.
And every lamp we ever create will have its own copy.
That's inefficient, but more than that, it's a style crime
because MAXIMUM_WATTAGE
shouldn't belong to any particular lamp,
it's a property of the idea of a lamp---it
should belong to the role (class),
not any particular actor playing the role (object belonging to the class).
We'd like some way to have only one copy of it
that no particular lamp contains, but that all lamps can access.
Declaring it as static
does that for us.
Here is the beginning of the latest version of the class:
/*
Define a lamp that can turn on and off,
report its state, report its state changes,
have a wattage, and make sure it's off on creation.
The class itself remembers the maximum possible wattage
of any lamp.
*/
class Lamp
{
private boolean isOn;
private int wattage;
//all lamps have a maximum possible wattage
private final static int MAXIMUM_WATTAGE = 500;
public Lamp()
{
isOn = false;
wattage = 60;
}
public Lamp(int wattage)
{
isOn = false;
if (wattage > Lamp.MAXIMUM_WATTAGE)
this.wattage = Lamp.MAXIMUM_WATTAGE;
else
this.wattage = wattage;
}
//remaining code goes here...
}
We access static
variables
with the name of the class they belong to,
not any particular object of that class.
They belong to the class as a whole,
not any particular object of that class.
All lamps can learn the maximum possible wattage any lamp can have,
but they don't each separately have copies of that value;
there is only ever one copy
no matter how many lamps exist---including none.
Since we access such variables through their class,
and not any specific object of that class,
they're class variables.
Adding Class Methods
Since non-Lamp
objects can create lamps,
they need to know what any lamp's maximum possible wattage is.
One simple way to allow this is to make
MAXIMUM_WATTAGE
public
.
This is the only reasonable exception
to making all variables private
.
Because the variable can't be changed at all,
it's quite safe to make it public.
No one can change it since it is unchangeable.
Suppose though that we wanted to do it through a method,
say, getMaximumWattage()
.
This isn't a good idea,
just as it wasn't a good idea
to make MAXIMUM_WATTAGE
a normal variable.
No single object of class Lamp
should have this method.
Further, since objects may need to know the maximum allowed wattage
before they create any lamps at all,
it's something that must be accessible even when there are no lamps at all.
So the method should belong to the class as a whole.
It should be a class method,
just as MAXIMUM_WATTAGE
is a class variable.
Once again, we signify that with the word static
.
Here's the new code:
/*
Define a lamp that can turn on and off,
report its state, report its state changes,
have a wattage, and make sure it's off on creation.
The class itself remembers the maximum possible wattage
of any lamp and provides a method to report it to objects.
*/
class Lamp
{
//some code goes here...
/*
Report the maximum wattage allowed for any lamp.
*/
public static int getMaximumWattage()
{
return Lamp.MAXIMUM_WATTAGE;
}
//remaining code goes here...
}
Just as we access class variable MAXIMUM_WATTAGE
through its class,
we also request execution of class method getMaximumWattage()
through its class,
like so:
Lamp.getMaximumWattage();
Unravelling the Last Puzzle
Using class variables and methods solves our previous problems,
but that, as usual, brings up a puzzler of its own.
What does it mean for a variable or method to be a class variable or method?
Since we access class variables through their class,
and since they don't exist in any particular object of that class,
they must exist before any particular object of that class exists.
So where do they exist?
So far, all we've ever seen are objects;
objects own variables and objects execute methods.
Nothing happens without an object there to mediate it.
So what object executes class methods and stores class variables?
The trick is this:
Every time we define a class,
the stage manager also secretly creates an unnamed object
for that class.
This class object is what we can ask to access class variables
or to execute class methods.
Since these class variables and methods
don't really belong to any particular object,
we access them by using the class name instead of any particular
object's name.
So, if we were to say, for instance:
Lamp.getMaximumWattage();
the object associated with class
Lamp
would execute
the method for us.
We've already seen several class methods---all the main()
methods
we've seen so far are static
, or class, methods.
We've also already seen lots of class variables.
Every variable declared inside any of the main()
methods
we've seen thus far has been a class variable.
They must be because when the stage manager is executing main()
there aren't yet any objects to have variables.
This seriously confuses beginners
because the Java interpreter forces us to mix class code and object code
together in one thing called a "class";
really they should be separated.
Anything labelled static
, whether a method or a variable,
and any variable declared in any static
method,
all belong to the class,
everything else belongs to the objects of that class.
So, kandisky
, for example, is a class variable.
It belongs to the class Lamp
as a whole,
not to any particular object of the class.
Recall that a reference variable like kandinsky
merely stores the name of an object;
although kandinsky
is a class variable,
the object it names is not a class object.
Finally, note that unlike MAXIMUM_WATTAGE
,
all those class variables do not have global scope
(that is, they do not exist across all methods of the class),
but only local scope
(they only exist inside a main()
method
as it's being executed by the (secret) class object).
One Last Mistake To Avoid
It's tempting to think that
the class name, in this case, Lamp
,
is the reference variable for the secret object.
This is not so.
The secret object has no---and can have no---reference variable at all.
We never create it, and we certainly don't name it,
so we can't ever access a reference variable holding its name.
With kandinsky
, say, we at least created and named the object
that kandinsky
refers to.
So even though we don't know, and never will know, that object's real name,
we at least know where to find its real name.
With the secret object, though, its name isn't stored anywhere at all.
We can do absolutely nothing at all with it.
All we can do is ask the Java interpreter
to please ask it to do class-related stuff for us.
That's all.
Starting the Play
Knowing about the secret object
gives us a much deeper understanding of what happens when the play starts.
What happens is this:
the stage manager reads the main role and immediately
creates a secret object associated with that role.
We haven't yet asked it to create any objects,
so it hasn't yet created any of our objects.
When we ask the stage manager to begin the play it sends the message:
[secret object's name].main(parameters);
and
the secret object then executes
the role's
main()
method.
This is why we must declare the
main()
method
as a class method using the word
static
.
Secret class objects can only execute class methods.
We cannot ask them to execute normal methods
because we have no way to
name the secret object
so that we can send it a message asking it to execute any method.
This gives us a pretty good understanding of the mystery declaration:
public static void main(String[] parameters)
This statement declares a method named
main()
.
The method accepts one parameter whose type is
String[]
(which is still a puzzlement).
It returns no value since its return type is
void
.
Further, it is
public
,
which makes it publically available to be executed by any object,
which is necessary if the stage manager is to see it
at the start of the play.
Finally, it is a
static
, or class, method,
so that it can be executed without having to ask an object of the class
to execute it,
which is also necessary at the start of the play
since only the secret object exists then.
To execute the
main()
method,
the stage manager sends a message to the secret class object
asking it to do so;
and that is what starts the play.
The Whole Program
/*
Define a lamp that can turn on and off,
report its state, report its state changes,
have a wattage, and make sure it's off on creation.
The class itself remembers the maximum possible wattage
of any lamp and provides a method to report it to objects.
*/
class Lamp
{
//this lamp is either on or off
private boolean isOn;
//this lamp has a wattage
private int wattage;
//all lamps have a maximum possible wattage
private final static int MAXIMUM_WATTAGE = 500;
/*
Set this lamp's initial state
as being a 60-watt lamp in the off position.
*/
public Lamp()
{
isOn = false;
wattage = 60;
}
/*
Set this lamp's initial state
as being in the off position.
Set this lamp's wattage to the given wattage.
Do not let any lamp have a wattage higher than
MAXIMUM_WATTAGE.
*/
public Lamp(int wattage)
{
isOn = false;
if (wattage > Lamp.MAXIMUM_WATTAGE)
this.wattage = Lamp.MAXIMUM_WATTAGE;
else
this.wattage = wattage;
}
/*
Report the maximum wattage allowed for any lamp.
*/
public static int getMaximumWattage()
{
return Lamp.MAXIMUM_WATTAGE;
}
/*
Turn this lamp on.
*/
public void turnOn()
{
isOn = true;
reportState();
}
/*
Turn this lamp off.
*/
public void turnOff()
{
isOn = false;
reportState();
}
/*
Report whether this lamp is on or off.
*/
public boolean isOn()
{
return isOn;
}
/*
Report this lamp's status.
*/
public void reportState()
{
if (this.isOn() == true)
System.out.println("I am on at wattage: " + wattage);
else
System.out.println("I am off.");
}
}
import Lamp;
/*
Create some lamps and fiddle with them.
*/
class Lobby
{
public static void main(String[] parameters)
{
//create a (normal) 60-watt lamp
Lamp kandinsky;
kandinsky = new Lamp();
kandinsky.turnOn();
kandinsky.turnOff();
//create a 100-watt lamp
Lamp bauhaus;
bauhaus = new Lamp(100);
bauhaus.turnOn();
bauhaus.turnOff();
if (kandinsky.isOn() == bauhaus.isOn())
System.out.println("The two lamp states are equal.");
else
System.out.println("The two lamp states are unequal.");
System.out.println("Maximum wattage of any lamp = " +
Lamp.getMaximumWattage());
}
}