The thing germinated and bred itself. It proceeded according to its own logic. What did I do? I followed the indications. I kept a sharp eye on the clues I found myself dropping. The writing arranged itself with no trouble into dramatic terms. The characters sounded in my ears---it was apparent to me what one would say and what would be the other's response, at any given point. It was apparent to me what they would not, ever, say, whatever one might wish... When the thing was well cooked I began to form certain conclusions. The point is, however, that by that time the play was now its own world. It was determined by its own engendering image.
While we've now learned a lot about the Java interpreter's capabilities,
SimpleRole
is more of a germ of an idea,
rather than any kind of real play.
An actor comes onstage, divides two numbers, reports the result,
then goes offstage again.
It's the epitome of boredom, and it will stay that way for quite some time
as we add to it to better understand how the Java interpreter does its job.
Specifying a Variable's Scope
As we've seen,
we can declare variables inside methods
just as we can declare them inside classes.
For example, SimpleRole
's first variable,
numberOfTurnips
,
appears at the beginning of SimpleRole
,
but the numberOfPeople
variable
first appears at the beginning of divideTurnipsAmongPeople()
.
No matter where we declare a variable though
(that is, no matter where we ask the stage manager
to produce a variable of that name and type)
we can't use it until that point.
If we declare a variable inside a method we can only use it inside the method.
The method is the variable's scope.
The method divideTurnipsAmongPeople()
, for instance,
is numberOfPeople
's scope.
The scope of the other variables
(numberOfTurnips, turnipsPerPerson
,
and finishedDividing
),
which we declared outside divideTurnipsAmongPeople()
but inside the class, is the entire class, namely,
SimpleRole
.
Thus, since we declare the reference variable estragon
in SimpleRole
's main()
method
we can't use it in, say, divideTurnipsAmongPeople()
.
Its scope is main()
, so that's the only method we can use it in.
In theater terms, each method in a class is like a scene in a play.
In that scene an actor may have special props
(like numberOfPeople
)
as well as its usual props;
outside that scene it only has its usual props.
The props we define inside a scene
are local to that particular scene;
those we define inside the role as a whole
are global to all the role's scenes.
A method's local props
appear when an actor starts executing the method
and vanish when the actor finishes executing that method.
An actor's global props exist for as long as the actor exists.
Let's say we're writing a role called WhaleHunter
.
An actor playing this role,
let's call it Ishmael,
will always have its own copy of all the global props---say,
a sword, a snuff-box, and a wallet.
Our prop declarations ask the stage manager to give Ishmael,
along with every other actor playing the role,
its own copy of each of those props.
Within a particular method, though, Ishmael is acting specially
(perhaps it is method acting?)
and so may need special props---say, a boat, a white whale, and a harpoon.
Ishmael will always have its (global) sword, snuff-box, and wallet,
but for the duration of its time in the particular method
it will also have a (local) boat, white whale, and harpoon.
Those local props vanish once the actor exits the method.
Parameterizing Methods
If we want to change the number of people we're dividing turnips among
in SimpleRole
we'd have to go in and change the value 2
in the assignment statement:
numberOfPeople = 2;
Instead, we could pass in
numberOfPeople
to divideTurnipsAmongPeople()
as a parameter---something
that can vary in value while the method itself remains the same.
After all, within the method it doesn't much matter
what the number of people are,
as long as it's an int
and is one or more.
The method declaration would then become:
double divideTurnipsAmongPeople(int numberOfPeople)
This has the same effect as declaring numberOfPeople
inside the method as we've been doing so far.
The variable numberOfPeople
is still an int
and it is still local to the method divideTurnipsAmongPeople()
(that is, it only exists within the method).
Parameterizing the method, however, makes it more useful
since the number of people is no longer fixed at two.
If we wanted our lone SimpleRole
character, estragon
,
to divide turnips among 15 people
instead of two, say, we would send it the message:
estragon.divideTurnipsAmongPeople(15);
Since we declared numberOfPeople
as an int
in the method declaration
we can't ask estragon
to execute its method
with a non-int
value like 52.34 or true
.
The type of the value we send
and the type of the variable we declare as a parameter must be the same.
The Java interpreter will check.
Adding Multiple Parameters
We can declare any method
(except the special version of main()
)
as taking any number and type of parameters.
In particular, we should give SimpleRole
's method
at least one more parameter---the number of turnips.
That would make it yet more general
because then we wouldn't have to go in all the time
and make changes to the method directly.
We tell the Java interpreter that a method takes multiple parameters by listing the parameters separated by commas in the method declaration. As always, we must still declare the name and type of the parameters.
The method would then become:
double divideTurnipsAmongPeople(int numberOfTurnips,
int numberOfPeople)
{
turnipPortions = numberOfTurnips / numberOfPeople;
finishedDividing = true;
return turnipPortions;
}
Here the declaration of divideTurnipsAmongPeople()
spreads over two lines to keep each line's width down.
The Java interpreter doesn't care about lines---it cares about statements.
We can continue any statement across any number of lines we wish,
so we could just as easily say:
double
divideTurnipsAmongPeople(
int numberOfTurnips,
int numberOfPeople)
and it would have exactly the same effect
as far as the Java interpreter is concerned.
Only the human beings reading our code would hate us.
With the new parameter,
we could ask estragon
to divide six turnips among two people
from within the main()
method,
by sending the message:
estragon.divideTurnipsAmongPeople(6, 2);
By freeing the method to handle any pair of int
values
we can now have any actor ask any SimpleRole
actor
to execute the method with any pair of int
values as parameters.
The method is no longer useful only to the class we originally wrote it for.
Returning from Methods
Just as with the original
divideTurnipsAmongPeople()
method
that accepted no parameters,
methods may also return no values.
We tell the stage manager that with the word void
.
An actor executing a void
method will never return any values.
It will still return of course,
but the method it's executing will have no statement like:
return [variable name];
We can still force an actor executing a void
method to return
(but with no returned value)
by issuing a bare
return;
statement, with no variable name attached.
Also, since a void
method never returns a value,
an actor executing it can return for itself
once it runs out of statements to execute.
This can't happen in a non-void
method
since we must always tell the stage manager
which value the actor must return.
So we must always specify a return
explicitly
once the method isn't void
.
The main()
method, as now written,
is an example of a method that an object exits
when there are no more statements for the object to execute
rather than when we explicitly ask it to.
This clears up a little of the mystery of the declaration:
public static void main(String[] parameters)
This statement declares a method named main()
that accepts one parameter and returns no values at all.
Its sole parameter is passed in to the method from somewhere---from
where exactly is still a mystery---and
the parameter's type, String[]
, is also still a mystery.
Adding Conditional Behavior
Now that we've parameterized divideTurnipsAmongPeople()
we've laid ourselves open to a problem we couldn't have had before:
namely, some actor might misuse the method
by asking a SimpleRole
actor
to try to divide some turnips by zero or fewer people.
We need some way for actors playing SimpleRole
to test the value of numberOfPeople
and avoid the division if that value is zero or less.
If it's zero or less,
the actor should refuse to do anything at all
and simply return with a value of zero for the method.
We can do that with an if
statement, as follows:
double divideTurnipsAmongPeople(int numberOfTurnips,
int numberOfPeople)
{
finishedDividing = false;
if (numberOfPeople <= 0)
return 0;
turnipPortions = numberOfTurnips / numberOfPeople;
finishedDividing = true;
return turnipPortions;
}
The symbol sequence "<=
"
is the "less than or equal to" operator,
and the statement:
if (numberOfPeople <= 0)
asks whether the value currently in the variable numberOfPeople
is zero or less.
If that's true, then the SimpleRole
actor executes
the return
statement the if
statement encloses,
and, thus returns from the method right away.
If, on the other hand, numberOfPeople
's value is one or more,
then the actor jumps past the return
statement,
executes the next statement in sequence (the division),
and continues on from there as before.
Consequently, whenever the value of numberOfPeople
is less than one,
estragon
does nothing when asked to
divideTurnipsAmongPeople()
,
simply returning zero.
Whenever its value is one or more, though,
estragon
obligingly does the division and returns the result.
Because we declare the method as returning a double
,
every time we ask an actor executing the method to return from the method,
we must also specify which double
value to return.
An actor could never simply return
from a method that has a
declared return type;
it can only do so if the method is void
.
Printing Output
We're still not quite done.
We haven't yet asked anyone,
neither the stage manager nor estragon
,
to tell us the result of the division.
So let's modify the main()
method
to ask the stage manager to print something to the screen:
public static void main(String[] parameters)
{
SimpleRole estragon;
estragon = new SimpleRole();
double portionsPerPerson;
portionsPerPerson = estragon.divideTurnipsAmongPeople(6, 2);
System.out.println("Number of portions per person is: " +
portionsPerPerson);
}
}
Without going into exactly how the last statement works,
it's enough to know for now that it asks the stage manager
to print the number of portions per person.
Also, this statement covers two lines,
just as the declaration of divideTurnipsAmongPeople()
.
We can stretch any statement across as many lines as we wish.
It's wise, however, to indent any continuation lines
to make it clear that they're all part of one statement.
Accessing Variables
Just as we can send messages to objects asking them to execute their methods, we can also send messages asking them to access their variables. For example, the message:
estragon.finishedDividing;
asks estragon
to let us access its copy of its
finishedDividing
variable.
We can use this capability to improve our program's output.
Recall that if the value of numberOfPeople
were zero or less,
then after requesting estragon
to execute its method,
the computation would have been unsuccessful
and estragon
's copy of finishedDividing
would have the value false
.
If, however, the computation were successful,
it would have the value true
.
Thus we can tell whether our request to estragon
to divide some turnips among some people
completed successfully or not
by testing estragon
's copy
of the finishedDividing
variable
as follows:
public static void main(String[] parameters)
{
SimpleRole estragon;
estragon = new SimpleRole();
double portionsPerPerson;
portionsPerPerson = estragon.divideTurnipsAmongPeople(6, 2);
if (estragon.finishedDividing == false)
return;
System.out.println("Number of portions per person is: " +
portionsPerPerson);
}
}
Here we're asking the stage manager to ask estragon
to divide six turnips among two people.
Then we test the value of estragon
's
finishedDividing
variable (which is a boolean
)
to see if it's equal to the boolean
value false
using the equals operator,
the symbol sequence "==
".
If it's false
then estragon
's computation failed,
otherwise the computation succeeded.
(Note that the equals operator, like every other operator we've seen,
is just like a tiny method; this one takes two variables as parameters
and returns true
if their values match.)
If estragon
's computation were unsuccessful,
the stage manager immediately returns from main()
(which ends the program).
Otherwise the stage manager continues and prints the value of the division,
then runs out of statements in main()
to execute
(which also ends the program).
Since main()
's return type is void
,
we don't return a value from it,
so we can end it simply by running out of statements to execute.
Adding Comments
It's time to stop and take stock.
The Java interpreter ignores everything following the symbol sequence
"//
" until the end of the line,
and it ignores everything, including new lines,
between the symbol sequences "/*
" and "*/
".
So we can use those symbol sequences to comment to ourselves
to help us remember something,
or to explain to other programmers what we're doing.
Here then is the entire role again,
in slow motion replay with copious comments on each statement
to help explain them.
(Note: the following program is seriously overcommented;
ideally, comments should be rare since the code should speak for itself,
if well-written.)
/*
First, we tell the Java interpreter
that we're about to define a new class of objects.
*/
class SimpleRole
{
/*
Next we declare the names and types of some variables
that all such objects will have their own copies of.
All three subsequent declarations end with semicolons.
That's a Java rule: all statements must end in a semicolon;
except for braces, brackets, and parentheses since they
always come in matched pairs, so the Java interpreter can
figure out where they end for itself.
*/
int numberOfTurnips;
double turnipPortions;
boolean finishedDividing;
//We can separate the declaration statements above
//from the method definitions below with any number
//of empty lines. We can also add as many comment lines
//as we want. The Java interpreter will ignore them all.
//Next comes the definition of the method
//divideTurnipsAmongPeople(). To keep the line width down,
//its declaration spans two lines.
double divideTurnipsAmongPeople(int numberOfTurnips,
int numberOfPeople)
{
/*
Method definitions go inside matching braces,
just like class definitions.
The variables numberOfTurnips and numberOfPeople are
parameters of this method; they are local to the method
so we cannot use them outside it. We can, however, use
the three global ones, since they're declared in the class
and not any particular method, they're available to all
methods.
These comments are indented along with the method
definition they comment on. Everything inside a definition
should have the same indentation. This isn't a Java rule
but it's good style because it makes it easier to see
the overall structure.
*/
finishedDividing = false;
//If the value of numberOfPeople is impossible,
//simply return a value of 0 without doing anything.
if (numberOfPeople <= 0)
return 0; //return 0 to whoever asked
//an actor to execute this method.
//If we get to this point, the value of numberOfpeople
//must be one or more, so do the division.
turnipPortions = numberOfTurnips / numberOfPeople;
finishedDividing = true;
//We should separate the following line
//from the above lines because it's fundamentally
//different. No assignment or computation is taking
//place below, it's simply returning a value.
//Again, this isn't a Java rule, it's style.
return turnipPortions; //Return the value in turnipPortions
//to whoever asked a SimpleRole actor
//to execute this method.
}
//Next comes the definition of the mysterious main() method.
public static void main(String[] parameters)
{
//Create a local reference variable and name it estragon.
SimpleRole estragon;
//Create a SimpleRole actor and stick its (real) name
//in variable estragon.
estragon = new SimpleRole();
//Reference variable estragon now contains a reference
//value that points to the newly created actor
//(whose real name we don't know).
//Now we can send that actor messages using ``estragon''
//as its character's name.
//Create a local double variable
//and name it portionsPerPerson.
double portionsPerPerson;
//Send a message to estragon asking it to execute its
//divideTurnipsAmongPeople() method to divide 6 turnips
//among 2 people and put the double result estragon returns
//into portionsPerPerson.
portionsPerPerson = estragon.divideTurnipsAmongPeople(6, 2);
//If the computation was unsuccessful
//(that is, if estragon's copy of finishedDividing
//is false), simply end this method.
if (estragon.finishedDividing == false)
return;
//estragon's computation must have completed successfully,
//otherwise this main() method would have already
//returned, so print the value of portionsPerPerson.
//The following (magic) statement spans two lines
//to keep the overall line width reasonable.
System.out.println("Number of portions per person is: " +
portionsPerPerson);
//End the method by running out of statements to execute.
}
}