Theater | Computer |
---|---|
actors | objects |
roles | classes |
props | variables |
actor names | reference values |
character names | reference variables |
lines | messages |
stage directions | statements |
scenes | methods |
scripts | programs |
audience | program users |
playwright and director | programmer |
stage manager and producer | java interpreter |
The only way to learn how to build Java programs is to build a Java program. So let's get to it.
Before we can create actors we have to describe their behavior and their state,
and to do that we must write a class.
To write a class we first have to give it a name.
That name could be RomeoAndJuliet
, Hamlet
,
TheCherryOrchard
,
or TheImportanceOfBeingEarnest
,
but we're not aspiring to art here, just simple engineering.
So let's just call it FredsScript
.
The names of Java classes look a little odd because all the words are capitalized and joined together. We have to remove all the spaces and punctuation to make the Java interpreter's job easier, but we also want to remember what we meant when we first wrote the script. So to name a class it's usual to describe it (briefly) then run all the words together.
Once we name our class,
we then specify the state and behavior of the objects
that will obey the class by listing a sequence of statements,
or requests to the Java interpreter,
all inside braces.
Defining a Class
We're ready for our first script. Here it is:
class FredsScript
{
}
Not too impressive, true. Right now our script is empty, but this is perfectly legal Java. It will work just fine. Of course, it also won't do anything since we haven't added any statements yet.
So let's put in a few props for any actors following FredsScript
to fiddle with.
Creating Variables
class FredsScript
{
int numberOfApples;
double applePortions;
boolean finishedProcessing;
}
This is another perfectly legal Java class.
First, all statements must end with a semicolon.
Second, all the statements inside the class definition are indented.
Third, variable names look just like class names
except that they start with a lower-case letter.
The case of a letter matters to the Java interpreter,
so FredsScript, Fredsscript, fredsScript
,
and fredsscript
are four different names.
The class now declares three props:
one int
variable,
one double
variable,
and one boolean
variable.
These three declaration statements
ask the stage manager to give each actor following this script
its own copies the above three props.
So far, though, the script doesn't specify anything for actors to do
using their copies of those three props.
So let's add some behavior:
Defining Methods
class FredsScript
{
int numberOfApples;
double applePortions;
boolean finishedProcessing;
double divideApplesAmongPeople()
{
int numberOfPeople;
numberOfApples = 6;
numberOfPeople = 2;
applePortions = numberOfApples / numberOfPeople;
finishedProcessing = true;
return applePortions;
}
}
We've now defined a method---a named sequence of actions---that
all actors following FredsScript
will know how to perform.
When asked to, an object enters a method defined for its class
and executes the method
(that is, follows the actions that the statements within the method specify)
then exits, or "returns from", the method
when we ask it to exit with a return
statement.
This method, like most other entities that we must describe to the Java interpreter, must have a name and a type.
First, its name is divideApplesAmongPeople
,
which looks just like a variable name.
To distinguish method names from variable names,
method names will appear with trailing parenthese, like this:
divideApplesAmongPeople()
,
to show that they're methods and not variables.
Second, its type is double
,
just like the variable applePortions
.
There is a difference though;
a variable's type specifies what values it can accept,
but a method's type specifies what values it can produce.
Here, the type of the value that an actor returns when exiting the method
will be double
.
Thus, all the values an actor can produce after executing this method
will each belong to the set of double
values.
So we can put any of those values into a double
variable.
Specifying the Scope of a Variable
We can declare variables inside methods
just as we can declare them inside classes.
For example, FredsScript
's first variable,
numberOfApples
,
appears at the beginning of FredsScript
,
but the numberOfPeople
variable
appears at the beginning of divideApplesAmongPeople()
.
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 divideApplesAmongPeople()
, for instance,
is numberOfPeople
's scope.
The scope of the other variables
(numberOfApples, applePortions
,
and finishedProcessing
),
which we declared outside divideApplesAmongPeople()
but inside the class, is the entire class, namely,
FredsScript
.
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 script as a whole
are global to all the script'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 script called MobyDick
.
An actor following this script,
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 following the script,
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.
Using Assignments and Operators
To understand divideApplesAmongPeople()
fully
we also need to know that we can copy
the contents of one variable x
to another variable y
by saying:
y = x;
This is an assignment statement;
it asks the stage manager (the Java interpreter)
to copy the value that's inside the box labelled with the name
on the right-hand side of the statement
then put that copy into the box labelled with the name on the left-hand side.
The effect is to copy (or assign)
the value currently in variable x
(that is, the value in the box named x
)
to the variable y
(the value in the box named y
).
This wipes out whatever value was previously in y
but leaves x
's value intact.
Besides copying the values of variables into other variables, we can also put values into variables directly, which is the effect of an assignment statement like:
numberOfApples = 6;
This puts the value 6, which is of type int
since it's a whole number and it lies well within the range
minus two billion to plus two billion,
into the variable named numberOfApples
,
whose type is int
.
We can also use various operators to compute new values given two or more variables (or operands) and put the result in yet another variable, which is the effect of the statement:
applePortions = numberOfApples / numberOfPeople;
This statement asks the stage manager to divide
the value in the int
variable numberOfApples
by the value in the int
variable numberOfPeople
and put the result in the double
variable
applePortions
.
No matter what's on the right-hand side, though,
the left-hand side of every assignment statement
must be the name of a variable
(that is, a box)
since the Java interpreter must have somewhere to put
the result of the right-hand side.
Creating an Actor
Although FredsScript
now specifies some state (variables) and behavior (methods),
it still does nothing.
It looks like it's storing the value 6
in the int
variable numberOfApples
,
the value 2 in the int
variable numberOfPeople
,
dividing the two of them,
storing the result
in the double
variable applePortions
,
then setting the boolean
variable finishedProcessing
to true
.
Then it's somehow returning the result of the division to somebody.
In fact, though, nothing at all happens
because we don't yet have an actor to ask to do anything.
So let's create an actor that can follow the script FredsScript
.
This isn't easy, however,
because at the beginning of the play there are no actors at all,
so we don't yet have an object that we can ask to create another object.
We get around this by adding a specially named method
that the stage manager responds to.
In it we ask the stage manager to create an actor for us
and so start the play.
class FredsScript
{
int numberOfApples;
double applePortions;
boolean finishedProcessing;
double divideApplesAmongPeople()
{
int numberOfPeople;
numberOfApples = 6;
numberOfPeople = 2;
applePortions = numberOfApples / numberOfPeople;
finishedProcessing = true;
return applePortions;
}
public static void main(String[] parameters)
{
FredsScript fred;
fred = new FredsScript();
}
}
The strangely declared main()
method
is special because it tells the stage manager where the play begins;
it's like Act I, Scene I of a script.
The stage manager will automatically execute the main()
method
of any script if we declare it exactly as above.
Although the method is short,
a lot has happened on this last step,
not all of which is important just this minute.
For now, just take away this lesson:
in any script, we can talk to the stage manager directly
by declaring a main()
method exactly as above.
Inside that method we can ask the stage manager
to create actors using the "new
" operator.
Think of new
as short for
createAnActorThatWillObeyTheFollowingScript
.
We follow the new
operator with the name of the script
we want the newly created actor to obey.
When the stage manager sees a new
operator,
which we must follow with some script's name followed by parentheses,
it will create an actor for us.
Then, it will give that actor a copy of the script we name.
Finally, it will create copies of each of the props the script specifies
and give them to the actor.
The new actor will then have props (variables) to remember its state
and actions (methods) to determine its behavior.
Naming the Actor
While creating an actor, we must also name it if we're going to refer to it in future, which is the purpose of the left-hand side of the assignment statement:
fred = new FredsScript();
Here we're calling the new actor fred
.
Or, more precisely, we're telling the stage manager
that we'll be referring to the newly created actor,
who will be following FredsScript
for the rest of its life,
by the reference variable fred
.
Or, even more precisely, we're asking the stage manager
to put a copy of the name of the new actor
(which we're simultaneously asking the stage manager to create)
into the reference variable named fred
.
In theater terms, there's a difference between
the name of the character that an actor plays---in
this case, the character's name is fred
---and
that actor's true name.
We, the playwright/directors, don't know---and could care less about---any
actor's true name;
all we need is the name of the character that actor plays.
When we say, for instance, "Romeo
kisses Juliet
",
we don't have to care that the real name of the actor
playing the Romeo
part is Leo DeCaprio.
Later on we'll see that we needn't even give an actor a character name.
Like extras in a crowd scene, some actors can play nameless roles.
Finally, just as with all the other variables declared earlier
(numberOfApples, numberOfPeople, applePortions
, and
finishedProcessing
)
we must declare the name and type of the new variable (fred
)
to the Java interpreter.
We do that as follows:
FredsScript fred;
This declaration tells the Java interpreter
that the new variable is a reference variable
(since it will contain a reference value,
that is a reference to an object),
and that its type is FredsScript
.
Further, since we declare it in main()
we can't use it in, say, divideApplesAmongPeople()
.
It's scope is main()
, so that's the only method we can use it in.
Sending Messages To the Actor
At last it looks like we finally have a working Java program. Alas, our program still does almost nothing.
To recap, here's what we've done so far:
We've asked the stage manager to create a new actor and to reference it with variableThis isn't quite enough, however. Although the object pointed to by reference variablefred
in Scene I of Act I (themain()
method).
We've given any such actor some behavior (dividing two values and storing some values) in adivideApplesAmongPeople()
method.
And we've even given any such actor some props to play with (three global ones right at the beginning of the script, and two local ones, one inside each of the two methods).
fred
exists,
and has some props, and knows how to divideApplesAmongPeople()
,
we haven't sent it a message asking it to
divideApplesAmongPeople()
.
It knows how to behave,
but we haven't asked it to behave.
So let's do that next:
class FredsScript
{
int numberOfApples;
double applePortions;
boolean finishedProcessing;
double divideApplesAmongPeople()
{
int numberOfPeople;
numberOfApples = 6;
numberOfPeople = 2;
applePortions = numberOfApples / numberOfPeople;
finishedProcessing = true;
return applePortions;
}
public static void main(String[] parameters)
{
FredsScript fred;
fred = new FredsScript();
double portionsPerPerson;
portionsPerPerson = fred.divideApplesAmongPeople();
}
}
In this version of the script,
after creating and naming an actor,
we send it a message asking it to execute
its divideApplesAmongPeople()
scene
(which, remember, is a method, not a variable,
even though it has a type---double
, in this case).
The actor, following its divideApplesAmongPeople()
method,
divides the number of apples by the number of people,
saves the result, assigns a value to a boolean
variable,
then returns the portions per person.
Then we ask the stage manager to save
a copy of that double
value in a new local variable,
portionsPerPerson
.
We can ask any actor to execute any of its methods with the dot operator, as in the statement:
[actor's name].[actor's method's name];
This statement sends the actor the message:
"please execute your such-and-so method."
We can ask any actor to send such a message to any other actor,
or even to itself,
or, as in the main()
method of FredsScript
,
we can ask the stage manager to send a message to an actor.
In this particular case,
the actor's true name (which is a reference value)
is hidden inside the reference variable named fred
,
and we wish that actor to execute
its divideApplesAmongPeople()
method.
So the message from the stage manager to fred
is:
fred.divideApplesAmongPeople();
An actor executes its methods only when asked to by another actor
(or by itself, or by the stage manager).
There is no such thing as an actorless action in Java.
Parameterizing Methods
Our program so far is fine as far as it goes, but it's pretty limited because if we want to change the number of people we're dividing apples among we'd have to go in and change the value 2 in the line:
numberOfPeople = 2;
Instead, the stage manager gives us the option of passing in
numberOfPeople
to the method 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
.
The method declaration would then become:
double divideApplesAmongPeople(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 divideApplesAmongPeople()
.
Parameterizing the method, however, makes it much more flexible
since the number of people is no longer fixed at two.
If we wanted fred
to divide apples among 15 people instead of two,
say, we would send it the message:
fred.divideApplesAmongPeople(15);
Since we declared numberOfPeople
as an int
in the method declaration
we can't ask fred
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 that the types match.
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 fred
's method
at least one more parameter---the number of apples.
That would make it yet more general
and would also avoid us from having to go in all the time
and make changes to the method directly.
We tell the stage manager 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 divideApplesAmongPeople(int numberOfApples,
int numberOfPeople)
{
applePortions = numberOfApples / numberOfPeople;
finishedProcessing = true;
return applePortions;
}
Here the declaration of divideApplesAmongPeople()
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
divideApplesAmongPeople(
int numberOfApples,
int numberOfPeople)
and it would have exactly the same effect.
With the new parameter,
we could ask fred
to divide six apples among two people
from within the main()
method,
by sending fred
the message:
fred.divideApplesAmongPeople(6, 2);
By freeing the method to handle any pair of int
values
we can now have any actor ask an actor of this class
to execute the method with any pair of int
values as parameters.
The method is no longer specific to the class we originally wrote it for.
Returning from Methods
Just as with the original
divideApplesAmongPeople()
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
Our program now works fine,
but by parameterizing divideApplesAmongPeople()
we've laid ourselves open to a problem we couldn't have had before:
namely, some actor might misuse the method
by asking fred
to try to divide some apples by zero or fewer people.
To prevent this we need some way for fred
to test the value of numberOfPeople
and avoid the division if that value is zero or less.
If it's zero or less,
fred
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 divideApplesAmongPeople(int numberOfApples,
int numberOfPeople)
{
finishedProcessing = false;
if (numberOfPeople <= 0)
return 0;
applePortions = numberOfApples / numberOfPeople;
finishedProcessing = true;
return applePortions;
}
The symbol sequence "<=
"
is the "less than or equal to" operator,
so the statement:
if (numberOfPeople <= 0)
asks whether the value currently in the variable numberOfPeople
is zero or less.
If that's true, then fred
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 fred
jumps past the return
statement,
executes the next statement in sequence (which is the division),
and continues on from there as before.
Consequently, whenever the value of numberOfPeople
is less than one,
fred
does nothing when asked to
divideApplesAmongPeople()
,
simply returning zero.
Whenever its value is one or more, though,
fred
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 fred
,
to tell us the result of the computation.
So let's modify the main()
method as follows
to ask the stage manager to print something to the screen:
public static void main(String[] parameters)
{
FredsScript fred;
fred = new FredsScript();
double portionsPerPerson;
portionsPerPerson = fred.divideApplesAmongPeople(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 divideApplesAmongPeople()
.
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:
fred.finishedProcessing;
asks fred
to let us access its copy of its
finishedProcessing
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 fred
to execute its method,
the computation would have been unsuccessful
and fred
's copy of finishedProcessing
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 fred
to divide some apples among some people
completed successfully or not
by testing fred
's copy
of the finishedProcessing
variable
as follows:
public static void main(String[] parameters)
{
FredsScript fred;
fred = new FredsScript();
double portionsPerPerson;
portionsPerPerson = fred.divideApplesAmongPeople(6, 2);
if (fred.finishedProcessing == false)
return;
System.out.println("Number of portions per person is: " +
portionsPerPerson);
}
}
Here we're asking the stage manager to ask fred
to divide six apples among two people.
Then we test the value of fred
's
finishedProcessing
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 fred
's computation failed,
otherwise the computation succeeded.
If fred
's computation were unsuccessful,
the stage manager immediately returns from main()
(which effectively 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 effectively 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.
Creating class FredsScript
was a big bite all at once,
so let's chew it over a bit before trying to digest it.
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's the entire script again, this time in slow motion replay with comments on each statement to help explain them.
/*
First, we tell the Java interpreter
that we're about to define a new class of objects.
*/
class FredsScript
{
/*
Next we declare the names and types of some variables
that all of those 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 numberOfApples;
double applePortions;
boolean finishedProcessing;
//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
//divideApplesAmongPeople(). To keep the line width down,
//its declaration spans two lines.
double divideApplesAmongPeople(int numberOfApples,
int numberOfPeople)
{
/*
Method definitions go inside braces,
just like class definitions.
The variables numberOfApples and numberOfPeople are
parameters of this method; they are local to the method
so we cannot use them outside it.
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.
*/
finishedProcessing = 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.
applePortions = numberOfApples / numberOfPeople;
finishedProcessing = 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, but it's good style.
return applePortions; //Return the value in applePortions
//to whoever asked an actor
//to execute this method.
}
//Next comes the definition of the (magic) main() method.
public static void main(String[] parameters)
{
//Create a local reference variable and name it fred.
FredsScript fred;
//Create an actor and stick its (true) name
//in variable fred.
fred = new FredsScript();
//Reference variable fred now contains a reference
//value that points to the newly created actor
//(whose true name we don't know).
//Now we can send that actor messages using fred
//as its name.
//Create a local double variable
//and name it portionsPerPerson.
double portionsPerPerson;
//Send a message to fred asking it to execute its
//divideApplesAmongPeople() method to divide 6 apples
//among 2 people and put the double result fred returns
//into portionsPerPerson.
portionsPerPerson = fred.divideApplesAmongPeople(6, 2);
//If the computation was unsuccessful
//(that is, if fred's copy of finishedProcessing
//is false), simply end this method.
if (fred.finishedProcessing == false)
return;
//Fred's computation must have completed successfully,
//otherwise this main() method would have already
//returned, so print the value of portionsPerPerson.
//The following 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.
}
}