all together now
last | | contents | | next


The whole trick behind elegant programming is to take some simple stuff and some simple operators defined for that stuff and make more complex stuff out of it. Then we take that more complex stuff and make yet more complex stuff out of it. We keep building on what we have, creating ever more complex stuff, until the stuff is complex enough to express a solution to our present problem simply, clearly, beautifully.

So far we've seen ways to link statements into methods, to link operators and operands into expressions, and, of course, to link methods and variables into classes. Soon we'll learn how to link symbols into Strings, variables into arrays, and classes into packages. Before that, though, we should first find out more about linking statements; that is the armature that makes everything else possible. We'll start with simple grouping.
 
Grouping Statements

Methods aren't the only way we can group statements. We can group any sequence of statements with braces ("{}") to form blocks of statements. Wherever we can put a single statement in a method, we can put an entire block of statements.

A block is like a method except that it has no name, which means it has no type. Further, because blocks are nameless, an actor can only execute them when it gets to them sequentially, which means blocks can't have return statements, either. Lacking a name for a block, we can't ask an actor to execute it at any time, or return from it at any time, as we can with a method.

Inside a block we can declare variables and ask actors to do any computations we wish, just as if they were inside a method. Further, since wherever we can put a single statement we can put an entire block, we can put one block inside another, and another inside that subblock, and so forth.
 
Blocks and Scope

Declaring a variable in a block makes the extent of that block its scope, so we can access it in any subblocks of that block, but not outside the block we declare it in. Once we declare a variable in any block the Java interpreter won't let us redeclare it in a subblock.

For example,


class Blocks
   {
   //This is the start of a class;
   //it is also the start of a block.

   private int firstVariable;

   public void demonstrateBlocks()
      {
      //This is the start of a method;
      //it is also the start of a block.

      //Although we declared firstVariable
      //in an outer block (the class this method
      //belongs to), we can still access it inside
      //this block; its scope is global to all blocks
      //in the class.

      firstVariable = 0;

      int secondVariable;

      //Declaring secondVariable here makes it accessible
      //in this block (that is, this method), and inside
      //any subblocks we define inside this method;
      //its scope is global to all subblocks of this method.

      secondVariable = 1;

         {
         //This is the start of a block within the block
         //defining the method, which is a block inside
         //the block defining the class.

         int thirdVariable;

         thirdVariable = 2;

         //We can still access firstVariable
         //and secondVariable here since we declared
         //them in outer blocks of this block,
         //so they are still within their scopes.

         System.out.println(firstVariable);
         System.out.println(secondVariable);
         System.out.println(thirdVariable);

         firstVariable = firstVariable + 10;
         secondVariable = secondVariable + 10;
         }

      //Although we altered secondVariable in a subblock
      //of this block, it still has its altered value now.
      //It is still within its scope.
      //Because we declared thirdVariable in a subblock
      //of this block, is no longer accessible.
      //It is out of its scope.

      System.out.println(firstVariable);
      System.out.println(secondVariable);
      }

   public static void main(String[] parameters)
      {
      //This is the start of yet another block.
      //Neither secondVariable nor thirdVariable exist
      //in this block. They are out of their scope.
      //firstVariable doesn't exist either, even though
      //it's within its scope; it needs an object to exist
      //in and this is a class method so there is no object
      //to hold it. We could only use it directly here if
      //it were a static (or class) variable. We could also,
      //of course, create a Blocks object and access that
      //object's firstVariable.

      Blocks blocks;
      blocks = new Blocks();
      blocks.demonstrateBlocks();
      blocks.firstVariable = 100;
      System.out.println(blocks.firstVariable);
      }
   }

In general, a more local variable hides a more global variable with the same name. This is true whether the "more local" variable is a method parameter and the "more global" variable is global to all methods in the class, or whether "more local" means a variable we declare inside a subblock of a method and "more global" means a variable we declare inside the method as a whole. We will see other examples of this general rule later.
 
Classes, Methods, Blocks, and Scope

Like a method, a class is also a named block of statements since we must name it and define it inside braces. (Later we'll see that we needn't name a class if we don't want to.) However, while we can ask an object to execute one of its methods, we can't ask an object to execute a class.

Further, although we can nest blocks one inside another as much as we want, and, as we shall see later, we can nest classes inside other classes, we can't nest methods inside other methods.

Finally, any variables we define inside methods (or subblocks of methods) can be final but they cannot be private or public the way that variables we define inside classes as a whole can be. Variables we declare inside methods or blocks are transient, they only exist while some object is executing the method. Other objects can never see them at all, since they're not even visible from other methods of the same object. Variables we declare in the class as a whole, though, are permanent; once an object exists, it will always have its copies of those variables.
 
The Five Statement Linkers

To help us generate more complex requests from the simple requests we start with, the stage manager lets us sequence, select, group, name, and repeat statements. These five mechanisms let us link statements to make the performance of some dependent on the outcome of others. The linking statements determine when, which, if, and how many times, other statements should be executed.

We've already seen examples of four of the five mechanisms.

First, the stage manager executes our requests sequentially, unless we ask it to do otherwise with other linking mechanisms, so everything happens in sequence naturally. Second, the stage manager lets us select one statement, or choose between two statements, using if or if-else statements. Third, the stage manager lets us group a collection of statements into a block. Fourth, the stage manager lets us name a block and have it accept parameters and return from anywhere in it---a method. Naming it lets us ask objects to execute it with one request. That's essentially what a method is: a unified, named action made up of a collection of simpler actions. To that capability it adds a way for us to ask objects to execute their methods (by sending messages) and to stop executing those methods (the return statement).

The fifth linking mechanism is repetition: we need a way to repeat statements.
 
Repeating Statements

We can repeat a statement or block of statements with a while statement:


   while (booleanExpression)
      [statement or block]

An actor executing the while statement will repeat the enclosed statement for as long as booleanExpression is true. As soon as booleanExpression becomes false execution continues with the next statement following the while statement as a whole.

If booleanExpression isn't true when the while statement is first reached, the enclosed statement or block will not be executed at all and execution continues with the next statement following the while statement as a whole.

Finally, if booleanExpression never becomes false, the loop will repeat forever.

For instance, the following piece of code will print the numbers from 1 to 100:


   int number = 1;
   while (number <= 100)
      {
      System.out.println(number);
      number = number + 1;
      }

On the other hand, the following piece of code prints nothing at all:


   int number = 1;
   while (number >= 100)
      {
      System.out.println(number);
      number = number + 1;
      }

Finally, the following piece of code tries to print numbers forever:


   int number = 1;
   while (number >= 1)
      {
      System.out.println(number);
      number = number + 1;
      }

The Java interpreter never tries to figure out if we really meant to ask it to loop forever, even though we usually don't mean to. Computers aren't very smart; they do the heavy lifting---we tell them where to put it.

Often, we will know exactly how many times we want to repeat the enclosed code. In such cases it's common to use a for statement instead.


   for (initialStatement; booleanExpression; repeatedStatement)
      [statement or block]

There is no semicolon after repeatedStatement and no extra semicolon after the enclosed statement or block. As with the if and if-else statements, neither the while nor the for statement needs its own separate semicolon to terminate it. The Java interpreter can figure out where it ends for itself because it's a fixed form.

The for statement is roughly equivalent to the following code:


   initialStatement;
   while (booleanExpression)
      {
      [statement or block]
      repeatedStatement;
      }

For example, the following piece of code will print the numbers from 1 to 100:


   int number = 1;
   for (number = 1; number <= 100; number = number + 1)
      System.out.println(number);

Once we know the exact number of times to repeat the enclosed code it's usually better to have a for statement rather than a while statement. Using a for statement usually makes it more clear how many times the loop will repeat and also usually decreases the chance that we forget something and inadvertently ask the computer to loop forever.
 
Complexifying Lamps

Let's apply what we've learned so far to create a more realistic lamp. Suppose, for instance, that we wanted a variable-brightness lamp. A lamp now needs a dimmer knob to turn its brightness up or down in stages.

To model a lamp's current brightness we'll need a new variable, let's say an int, called, say, brightness. That takes care of the lamp's new state. For its behavior we need new methods to brighten or darken the lamp.

Here's some of the new code to add to class Lamp:


   //this lamp has a certain brightness, if it's on
   private int brightness;

   /*
   If this lamp is on, make it brighter.
   */
   public void brighten()
      {
      if (this.isOn() == true)
         brightness = brightness + 1;

      reportState();
      }

   /*
   If this lamp is on, make it darker.
   */
   public void darken()
      {
      if (this.isOn() == true)
         brightness = brightness - 1;

      reportState();
      }

   /*
   Make this lamp brighter in steps.
   */
   public void brighten(int brightnessIncrease)
      {
      int index = 1;
      while (index <= brightnessIncrease)
         {
         brighten();
         index = index + 1;
         }
      }

   /*
   Make this lamp darker in steps.
   */
   public void darken(int brightnessDecrease)
      {
      int index = 1;
      while (index <= brightnessDecrease)
         {
         darken();
         index = index + 1;
         }
      }


 
Method Overloading

Class Lamp now has two brighten() and two darken() methods. Although they both have the same name, one has an int as a parameter and the other does not. They have differing signatures---that is, either the number or the type, or both, of their parameters is different. That's enough of a clue for a Lamp actor to figure out which method we want it to execute.

If we say:


   bauhaus.brighten(6);

we're asking bauhaus to execute its brighten() method that takes an int parameter.

If we say:


   bauhaus.brighten();

we're asking bauhaus to execute its brighten() method that takes no parameters.

We've already seen an example of constructor overloading (having multiple constructors, each with its own unique signature); here is an example of method overloading.

The signature of a method or constructor is not just the number of parameters it takes, but also their type and their sequence. So three methods that take two int values and a boolean value in the following orders: (int, int, boolean), (int, boolean, int), and (boolean, int, int) all have different signatures.

The names of a method's parameters aren't part of its signature. So our Lamp class could never have both of the following two methods:


   public void brighten(int brightnessIncrease)
      {
      }

   public void brighten(int wattageIncrease)
      {
      }

Although the parameter names are different, the signatures are the same; so a Lamp actor can't figure out which method we want to execute if we ask it to, for example, brighten(15).
 
Using for Statements

Instead of using while statements for indexed loops it's usually better style to use for statements since we know how many times the loop will repeat. Here's what the second version of darken() looks like with a for statement:


   public void darken(int brightnessDecrease)
      {
      int index = 1;
      for (index = 1; index <= brightnessDecrease;
         index = index + 1)
         darken();
      }

The for statement is especially useful when we want to step through a set of things with an int index. That happens so often that the Java interpreter lets us declare the loop's index in the statement itself, like so:


   for (int index = 1; index <= brightnessDecrease;
      index = index + 1)

The variable index declared inside the for statement is local to the block the for statement controls. We can't access it outside that block. Once the for loop ends, variable index vanishes.

Further, the Java interpreter understands two special operators when we want to increase or decrease an int (or double) variable by one, since that is also very common. The statements:


   index1++; index2--;

are short for the statements:

   index1 = index1 + 1; index2 = index2 - 1;

Both the increment operator (++) and the decrement operator (--) only apply to int or double variables.

Now the above darken() method could look like this:


   public void darken(int brightnessDecrease)
      {
      for (int index = 1; index <= brightnessDecrease; index++)
         darken();
      }

Short and sweet.
 
Simplifying boolean Expressions

The second time we saw an if (which was in SimpleRole) we used it to compare estragon's boolean variable finishedDividing against the value false. Instead, we can do this more simply. Since all boolean expressions can only have the value true or the value false, we can use them directly in if statements. So, instead of testing the result of kandinsky.isOn() against true or false, we can simply test it directly, as in:


   if (kandinsky.isOn())
      System.out.println("kandinsky is on.")
   else
      System.out.println("kandinsky is off.")

This if-else statement asks if the value of kandinsky.isOn() is true. If it is, the first enclosed statement executes next, otherwise, the second enclosed statement executes next.
 
Declaring Constants public

Our present version of the Lamp class has a private class constant, MAXIMUM_WATTAGE, and a public class method getMaximumWattage() to report it. This is unnecessary since it's quite safe to make constants public. No one can change them, so making them public can do no harm. That would let us get rid of the class method getMaximumWattage() as well.

This is an amendment to our earlier style rule about making all variables private and all methods public. When a variable isn't "variable" at all, it's okay for it to be public. So far we haven't seen a case where a method shouldn't be made public, but we will. These small exceptions don't invalidate the style rule, however; generally speaking, all variables should be private and all methods public.

Finally, we should name (and make public) the constant representing the 60-watt default value for each lamp's wattage so that if we ever decide to change it we only have to change it in one place. This is another example of the "restricting access" style rule.
 
The Whole Program

Here is the entire program, including the new variables, constants, and methods, for statements instead of while statements, increment and decrement operators, and simplified boolean expressions:


/*
Define a lamp that can turn on and off,
report its state, report its state changes,
have a wattage, brighten and darken in stages,
and make sure it's off on creation.

The class itself remembers the maximum possible wattage
of any lamp and the default wattage of each lamp.
*/
class Lamp
   {
   private boolean isOn;
   private int brightness;
   private int wattage;
   public final static int MAXIMUM_WATTAGE = 500;
   public final static int DEFAULT_WATTAGE = 60;

   /*
   Set this lamp's initial state
   as being a Lamp.DEFAULT_WATTAGE-watt lamp
   in the off position.
   */
   public Lamp()
      {
      isOn = false;
      brightness = 0;
      wattage = Lamp.DEFAULT_WATTAGE;
      }

   /*
   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 Lamp.MAXIMUM_WATTAGE.
   */
   public Lamp(int wattage)
      {
      isOn = false;
      brightness = 0;

      if (wattage > Lamp.MAXIMUM_WATTAGE)
         this.wattage = Lamp.MAXIMUM_WATTAGE;
      else
         this.wattage = wattage;
      }

   /*
   Turn this lamp on.
   */
   public void turnOn()
      {
      isOn = true;
      brightness = 1;
      reportState();
      }

   /*
   Turn this lamp off.
   */
   public void turnOff()
      {
      isOn = false;
      brightness = 0;
      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())
         {
         System.out.println("I am on");
         System.out.println("My wattage is: " + wattage);
         }
      else
         System.out.println("I am off.");
      }

   /*
   If this lamp is on, make it brighter.
   */
   public void brighten()
      {
      if (this.isOn())
         brightness++;

      reportState();
      }

   /*
   If this lamp is on, make it darker.
   */
   public void darken()
      {
      if (this.isOn())
         brightness--;

      reportState();
      }

   /*
   Make this lamp brighter in steps.
   */
   public void brighten(int brightnessIncrease)
      {
      for (int index = 1; index <= brightnessIncrease; index++)
         brighten();
      }

   /*
   Make this lamp darker in steps.
   */
   public void darken(int brightnessDecrease)
      {
      for (int index = 1; index <= brightnessDecrease; index++)
         darken();
      }
   }


import Lamp;

class Lobby
   {
   public static void main(String[] parameters)
      {
      //create a (normal) Lamp.DEFAULT_WATTAGE-watt lamp
      Lamp kandinsky;
      kandinsky = new Lamp();
      kandinsky.turnOn();
      kandinsky.brighten(4);
      kandinsky.darken(2);
      kandinsky.turnOff();

      //create a 100-watt lamp
      Lamp bauhaus;
      bauhaus = new Lamp(100);
      bauhaus.turnOn();
      bauhaus.brighten(6);
      bauhaus.darken(1);
      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.MAXIMUM_WATTAGE);
      }
   }


 
Benefits of Encapsulation

Now that we've added brightness we could get rid of the isOn boolean variable if we choose, since isOn is true only when brightness > 0. The only methods we'd have to change would be isOn(), turnOn(), and turnOff(). We would also have to change the two constructors.

Thanks to our attempts to keep class Lamp encapsulated, even though we're thinking of removing a variable (a portion of a lamp's state) no object that uses lamps would have to change in any way.

Encapsulating lamps as much as possible protects us from having to change it a lot, if we ever have to change it. Further, it protects all other classes from having to change at all if class Lamp changes. Since they can in no way affect a lamp's internal state, internal changes in class Lamp cannot affect them. Encapsulation gives objects an inside and an outside, and that is of immense value when it comes to changing your program.

The less other classes know about class Lamp, and the less class Lamp knows about itself, the easier changes to class Lamp become. This makes building large, complex, flexible programs much easier than if everything depended on everything else. That is the point of encapsulation. Breaking encapsulation is a serious style crime.
 
Adding String Names

Now let's give each lamp a name. To do so we can add one new variable to hold the lamp's name, if any, and two methods to control access to the variable: one to save a name assigned from outside and the other to report the lamp's current name.

This seems to require us to build a new class of objects to hold strings, but fortunately the Java interpreter already knows of a class whose objects can hold and manipulate strings: class String.

Here are the additions to class Lamp:



   private String name;

   /*
   Name this lamp.
   */
   public void setName(String name)
      {
      this.name = name;
      }

   /*
   Report this lamp's name.
   */
   public String getName()
      {
      return name;
      }

And here is how we might use that new capability:


   Lamp kandinsky;
   kandinsky = new Lamp();
   kandinsky.setName("kandinsky");
   System.out.println(kandinsky.getName() + " here");
   kandinsky.turnOn();
   kandinsky.turnOff();

In Java we delimit strings with double quotes (").
 
Creating Strings

Just as class Lamp defines lamp objects, class String defines string objects. These objects can hold and manipulate strings---that is, sequences of symbols. Just as with lamps, we can create them with new, ask them to execute their methods, and ask them to modify their variables (if any were public). They also have constructors just as Lamp objects do.

Here, for example, are two pieces of code. One creates a new object of class Lamp and the other creates a new object of class String both using their default constructors:

   Lamp lamp;
   lamp = new Lamp();


   String string;
   string = new String();

Those statements create a Lamp.DEFAULT_WATTAGE-watt lamp that is in the off position and a String object containing the empty string (""), respectively.

We can also create String and Lamp objects using other constructors, for example:

   Lamp lamp;
   lamp = new Lamp(100);


   String string;
   string = new String("Hello");

Those statements create a 100-watt lamp that is in the off position and a String object containing the string "Hello", respectively.

Strings are special, however, because many Java programs use them. Consequently, the Java interpreter understands special shortcuts for String objects. For instance, the Java interpreter lets us create String objects this way:


   String string;
   string = "Hello";

The last assignment statement is short for the previous way of creating a String object. It has exactly the same effect.
 
Operating on Strings

We can operate on String objects since the class has many methods, all of which were written for us by someone long ago in Java's childhood. For example, we can concatenate two strings (that is, join one to another) as follows:


   String string1;
   string1 = "Hello";
   String string2;
   string2 = "Goodbye";
   String string3;
   string3 = string1.concat(string2);

The concat() method of class String takes a String parameter. A String object executing the method adds the string inside the String object sent in as a parameter to the end of the string that the String object executing the method holds. It then returns that new concat()enated string stored inside yet another String created inside the method. The effect is to make string3 into a String object that holds the string "HelloGoodbye".

Again, because this operation is so common, the Java interpreter also lets us specify string concatenation more simply, as follows:


   String string1;
   string1 = "Hello";
   String string2;
   string2 = "Goodbye";
   String string3;
   string3 = string1 + string2;

Or, even more simply, as:

   String string3;
   string3 = "Hello" + "Goodbye";

This overloads the meaning of the "+" operator (it has one meaning for numeric addition and another for string concatenation), just as the "/" operator is overloaded with two different meanings (one for double division and one for int division). Although constructor overloading and method overloading are common in Java, these are the only two examples of operator overloading.
 
Making Lamps Work Together

The concat() method of class String works by having one object (a String) operating on another object (another String). That may seem strange. Adopting an object's point of view, though, makes it's much more natural than asking some generic object to concatenate two strings.

For example, suppose we wanted to compare the current brightness of two lamps. One way would be to write a class method that takes two lamps, looks up their brightness variables, then compares them. That isn't quite a style crime, but it's still bad style. It places the action first, rather than the actors. A better style would be to use one lamp to directly tell whether another lamp is brighter. Here's a method to do so:


   public boolean isBrighterThan(Lamp lamp)
      {
      /*
      Report whether this lamp is brighter than
      the given lamp.
      */

      return (this.brightness > lamp.brightness);
      }

The two lamps are now working together.

We might use the new method as follows:


   lamp kandinsky;
   kandinsky = new Lamp(100);
   lamp bauhaus;
   bauhaus = new Lamp();
   if (kandinsky.isBrighterThan(bauhaus))
      System.out.println("kandinsky is brighter than bauhaus");


 
Encapsulating Lamps Further

The isBrighterThan() method works because making a variable private does not protect it against access by another object of the same class. Although the Java interpreter allows this kind of access it is a style crime to use it, since it breaks the encapsulation of each lamp. That can make changes to class Lamp harder than they should be.

Instead, it's better style to add two more methods to class Lamp, one to set a lamp's brightness to some value, and another to report a lamp's brightness, then use the second one in isBrighterThan(), as follows:


   private void setBrightness(int brightness)
      {
      /*
      Let another lamp set this lamp's brightness.
      */

      this.brightness = brightness;
      }

   private int getBrightness()
      {
      /*
      Report this lamp's brightness to another lamp.
      */

      return brightness;
      }

   public boolean isBrighterThan(Lamp lamp)
      {
      /*
      Report whether this lamp is brighter than
      the given lamp.
      */

      return (this.getBrightness() > lamp.getBrightness());
      }

All the other methods that either altered or accessed the brightness variable should also use one of the two private methods setBrightness() or getBrightness().

Why bother to do this? Isn't it the same as (but more work than) directly accessing one lamp's brightness variable from another lamp? Well, no. By forcing all accesses to the variable brightness to go through these two new methods, we control access to each lamp's brightness variable.

First, only lamps have access, since only they can ask other lamps to execute their getBrightness() method (it is private to class Lamp). Second, if we ever decide to change the name of the brightness variable to something else we only have to change it in three places: in its declaration and in the getBrightness() and setBrightness() methods. No other methods have to change at all.

Since any program that's worth anything has a fairly long life, it will likely go through a fair number of changes. Elegant programming is all about minimizing the impact of change. Encapsulating everything as much as possible makes that much easier. Even though it's more work in the short run, we leverage that work enormously in the long run.
 
Making Methods private

The setBrightness() and getBrightness() methods we just added to class Lamp are both private. This is one of the few cases where making a method private is better than making it public. We don't want non-Lamp to monkey with these methods, since setting the brightness should only happen as a consequence of executing one of the public brighten() or darken() methods. That gives unique public access to the variable. Non-Lamp objects should never have to know that Lamp objects even have a brightness variable.

Conversely, we've already seen one of the only cases where making a variable anything other than private is a good idea---namely when the variable is final (which means it's a constant). Since the variable is constant, no harm can come of letting any object see it; no object can alter it in any way.

There are only a few other cases where we break the style rule: "make all variables private and all methods public".
 
Making Arrays of Lamps

Suppose now that we want to create many lamps, so many that we can't be bothered to name them all specially. The following two statements create a single object that can hold the true (but forever hidden) names of 20 Lamp objects:


   Lamp[] lampArray;
   lampArray = new Lamp[20];

The first statement declares a reference variable that will contain the true name of an object. That object can contain any number of reference variables, each of which can contain the true name of a Lamp object. The second statement creates such an object, fixing the number of Lamp reference variables it can hold at 20. The 20 Lamp objects whose true names may or may not be stored in the variables of such an object don't exist yet. Initially, each of the 20 Lamp reference variables in the object contains the value null; none of them point to any object as yet.

If there were a class named LampArray that defines what it means for objects to contain arrays of lamp reference variables (there is no such class), then the above two statements would be the same as the following two:


   LampArray lampArray;
   lampArray = new LampArray(20);

This version is of course the same as the format we're used to so far, for example in:

   Lamp lamp;
   lamp = new Lamp(100);


   String string;
   string = new String("Hello");

The special array operator ([]) has three uses:

* First, we can use it after a type name to ask the Java interpreter to create a reference variable that can refer to an object capable of holding any number of variables, each of which must be of the named type.
* Second, we can use it in a new statement to ask the Java interpreter to make such an object that contains a specific number of variables.
* Third, we can use it to refer to one particular variable of the array of variables that the object contains.

Every array object contains a length variable that stores the number of variables (of some particular type) the object can hold. In our case, lampArray.length equals 20. The length variable is public, int, and final. In other words, every object can ask the array object to access its length variable; that variable will always hold an int value; and no object can assign new values to it.

The Java interpreter counts from zero, rather than one, so we can access the variables stored in the lampArray object using int values in the range 0 to lampArray.length - 1. So the expression lampArray[0] refers to the first variable in the Lamp array object. The expression lampArray[19] refers to the twentieth variable of the array.

Here is how we might use the new array of lamps type:


import Lamp;

class FiddleWithLamps
   {
   public final static int NUMBER_OF_LAMPS = 20;

   public static void main(String[] parameters)
      {
      //declare a reference variable that can refer to
      //an object capable of holding any number of
      //Lamp reference variables
      Lamp[] lampArray;

      //create an object holding some Lamp reference variables
      lampArray = new Lamp[FiddleWithLamps.NUMBER_OF_LAMPS];

      //for each Lamp reference variable in the object,
      //create a new Lamp, then switch it on and off
      for (int index = 0; index < lampArray.length; index++)
         {
         lampArray[index] = new Lamp();
         lampArray[index].turnOn();
         lampArray[index].turnOff();
         }

      if (lampArray[0].isOn())
         System.out.println("The first lamp is on.");
      else
         System.out.println("The first lamp is off.");
      }
   }


 
Starting the Play

Finally, we now have a pretty good understanding of the mystery declaration:


   public static void main(String[] parameters)

The only puzzle remaining from last time is the type of the parameter, String[]. We now know that it is an array of String objects. We still don't know, however, where those strings come from.
 
Packages

So far we've seen ways to link operators and operands into expressions, statements into blocks, blocks into methods, methods and variables into classes, symbols into Strings, and variables into arrays. There is one more kind of linking mechanism the Java interpreter understands: packages.

We can tell the Java interpreter that any set of classes belong in one package by starting each class with the word package followed by the name of the package. A collection of objects from a package of classes is like a troupe of actors; each one specializing in one particular role (its class) and all working together for the good of their troupe.

We can nest packages arbitrarily deeply: one package inside another, inside a third, inside a fourth, and so on. To name a nested package we first list all the names of all the packages it's in, starting from the outermost and going inward, separating each name with periods ("."). Each name should be all lowercase, with no spaces or punctuation. So, package fredsstuff inside package utilities inside package deparmenttools would be named:


   departmenttools.utilities.fredsstuff

If we then want package fredstuff to contain class Lamp, say, we would have to precede the definition of class Lamp with the following package declaration statement:


   package departmenttools.utilities.fredsstuff.Lamp;

Every class in that package would have to start with a similar statement. No class can belong to two or more packages. If we then want to use class Lamp in some class not in the same package we would say:

   import departmenttools.utilities.fredsstuff.Lamp;

Now that class Lamp is in package fredsstuff, that is its full name. Once we import it, however, we can refer to it just as Lamp, as we've always done before.

Finally, the Java interpreter secretly puts all classes that we don't declare as being in a named package in an unnamed package. So if we don't declare a class as being in any package, it's still in a package---the unnamed one. So when we used to say:


   import Lamp;

in class FiddleWithLamps we were telling the Java interpreter to go find class Lamp in the unnamed package. It's good style to always import all the classes we need, even if they're all in the unnamed package, or all in some particular named package.
 
Packages and Scope

We can make an object's variables and methods public, private, or give them no explicit access modifier. Those modifiers control whether objects of other classes can see the variables and methods.

* All objects can see an object's public variables and methods.
* No objects, except objects of the same class, can see an object's private variables and methods.
* Only objects from classes in the same package as the given class can see an object's variables and methods if they have no access modifier at all. Those variables and methods have package access.

To put the third access mechanism another way: objects of any class in a package have package access to all non-private variables and methods of objects of each class in the package. Unfortunately, unlike variables in blocks and subblocks, such package access does not extend to any enclosed packages. From the point of view of access, they are completely separate packages.

We can also make classes public, or give them no explicit access modifier, to control whether objects from classes outside the package can see an object of one of those classes.

* All objects can see an object of a public class, no matter whether they are objects of classes in the package the public class belongs to or not.
* Classes can't be private.
* All objects of all classes inside the package that a class with no access modifier belongs to can see an object of that class, but no objects of any classes outside the package can see it---including objects of classes in any packages nested inside the package.

Using no explicit access modification at all (for variables, methods, or classes) is the same as allowing package access. As a general rule, though, using package access for anything is a style crime. With small exceptions, all variables should be private, and all methods and classes should be public.
 
Modifier Classes Methods and Variables
public all objects all objects
private -undefined- objects from same class
-none- objects from same package objects from same package


 
The Whole Program---Again

Here is the entire program, including everything we've learned so far:


package lamps;

public class Lamp
   {
   /*
   Define a lamp that can turn on and off,
   report its state, report its state changes,
   have a wattage, brighten and darken in stages,
   compare its brightness with another lamp,
   have and report a name, and make sure it's off on creation.

   The class itself remembers the maximum possible wattage
   of any lamp and the default wattage of each lamp.
   */

   //this lamp has a certain brightness, if it's on
   private int brightness;

   //this lamp may have a name
   private String name;

   //this lamp has a wattage
   private int wattage;

   //all lamps have a maximum possible wattage
   public final static int MAXIMUM_WATTAGE = 500;

   //all lamps have a default wattage
   public final static int DEFAULT_WATTAGE = 60;

   public Lamp()
      {
      /*
      Set this lamp's initial state
      as being a Lamp.DEFAULT_WATTAGE-watt lamp
      in the off position.
      Give this lamp a generic name initially.
      */

      this.setBrightness(0);
      wattage = Lamp.DEFAULT_WATTAGE;
      name = "Lamp";
      }

   public Lamp(int wattage)
      {
      /*
      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
      Lamp.MAXIMUM_WATTAGE.
      Give this lamp a generic name initially.
      */

      this.setBrightness(0);

      if (wattage > Lamp.MAXIMUM_WATTAGE)
         this.wattage = Lamp.MAXIMUM_WATTAGE;
      else
         this.wattage = wattage;

      name = "Lamp";
      }

   public void turnOn()
      {
      /*
      Turn this lamp on.
      */

      this.setBrightness(1);
      reportState();
      }

   public void turnOff()
      {
      /*
      Turn this lamp off.
      */

      this.setBrightness(0);
      reportState();
      }

   public boolean isOn()
      {
      /*
      Report whether this lamp is on or off.
      */

      return (this.getBrightness() > 0);
      }

   public void reportState()
      {
      /*
      Report this lamp's status.
      */

      if (this.isOn())
         {
         System.out.println(this.name + " is on");
         System.out.println("My wattage is: " + wattage);
         System.out.println("My brightness is: " +
            this.getBrightness());
         }
      else
         System.out.println(this.name + " is off");
      }

   public void brighten()
      {
      /*
      If this lamp is on, make it brighter.
      */

      if (this.isOn())
         this.setBrightness(this.getBrightness() + 1);

      reportState();
      }

   public void darken()
      {
      /*
      If this lamp is on, make it darker.
      */

      if (this.isOn())
         this.setBrightness(this.getBrightness() - 1);

      reportState();
      }

   public void brighten(int brightnessIncrease)
      {
      /*
      Make this lamp brighter in steps.
      */

      for (int index = 1; index <= brightnessIncrease; index++)
         brighten();
      }

   public void darken(int brightnessDecrease)
      {
      /*
      Make this lamp darker in steps.
      */

      for (int index = 1; index <= brightnessDecrease; index++)
         darken();
      }

   public void setName(String name)
      {
      /*
      Name this lamp.
      */

      this.name = name;
      }

   public String getName()
      {
      /*
      Report this lamp's name.
      */

      return name;
      }

   private void setBrightness(int brightness)
      {
      /*
      Let another lamp set this lamp's brightness.
      */

      this.brightness = brightness;
      }

   private int getBrightness()
      {
      /*
      Report this lamp's brightness to another lamp.
      */

      return brightness;
      }

   public boolean isBrighterThan(Lamp lamp)
      {
      /*
      Report whether this lamp is brighter than
      the given lamp.
      */

      return (this.getBrightness() > lamp.getBrightness());
      }
   }


package lamps;

import lamps.Lamp;

public class FiddleWithLamps
   {
   /*
   Create some lamps and fiddle with them.
   */

   public final static int NUMBER_OF_LAMPS = 2;

   public static void main(String[] parameters)
      {
      //declare a reference variable
      //that can refer to a Lamp
      Lamp bauhaus;

      //create a 100-watt lamp
      bauhaus = new Lamp(100);

      //turn it on and off
      bauhaus.turnOn();
      bauhaus.turnOff();

      //declare a reference variable that can refer to an
      //object capable of holding any number of
      //Lamp reference variables
      Lamp[] lampArray;

      //create an object holding some Lamp reference variables
      lampArray = new Lamp[FiddleWithLamps.NUMBER_OF_LAMPS];

      //for each Lamp reference variable in the object,
      //create a new Lamp.DEFAULT_WATTAGE-watt Lamp,
      //name it, print its name, and switch it on and off
      for (int index = 0; index < lampArray.length; index++)
         {
         lampArray[index] = new Lamp();
         lampArray[index].setName("Lamp number " + index);
         System.out.println(lampArray[index].getName() + " here");
         lampArray[index].turnOn();
         lampArray[index].brighten(2);
         lampArray[index].darken(1);
         lampArray[index].turnOff();
         System.out.println();
         }

      if (lampArray[0].isOn())
         System.out.println("The first lamp is on.");
      else
         System.out.println("The first lamp is off.");

      if (lampArray[0].isBrighterThan(lampArray[1]))
         System.out.println("The first lamp " +
            "is brighter than the second");

      System.out.println("Maximum wattage of any lamp = " +
         Lamp.MAXIMUM_WATTAGE);
      }
   }


 
Adding a New Type

We've covered an awful lot of territory this time out. It's time to stop and take stock again. Now that we've defined a new class, we've added a new type to the stage manager's repertoire. From now on we can rely on the Java interpreter to understand the Lamp type, just as it previously understood the boolean, int, double, and reference types, and then the SimpleRole, and Blocks types. We can ask the stage manager to produce an object of the Lamp type any time we want to and it will know exactly what we want and how the object must behave.

Every class we write adds to the Java interpreter's vocabulary. That's why we can use this type to specify the type of the reference variable bauhaus in the main() method of the class. The Java interpreter will know exactly what the object that reference variable bauhaus names can and cannot be asked to do---it will know bauhaus's type.
 
Type Constraints
Lamp
Values int, String, int
Constructors Lamp() Lamp(int)
Operators turnOn() turnOff() isOn() reportState() brighten() brighten(int) darken() darken(int) setName(String) getName() setBrightness(int) getBrightness() isbrighterThan(Lamp)

Class Values int, int


last | | contents | | next