it takes all types
last | | contents | | next


Our Lamp program is now quite respectably complex, but it's still more of a sketch of a lamp than a real lamp. The main problem now is how to model different types of lamps (desk, floor, ceiling, wall, portable, articulated, strobe, incandescent, fluorescent, ultraviolet, sun). This leads to a whole new scale of problem. The type of a lamp can completely alter both its state and behavior. To model that kind of radical change we need some way to switch between whole banks of variables and methods---we need some way to switch between different types.

We saw a little of the problem when we added a simple dimmer switch to our lamp model. That one change forced a lot of change in the class. Really, we should no longer call it class Lamp at all but rather class DimmerControlledLamp. The way we have it now, all lamps must be dimmer-controlled since all of them can respond to requests to brighten() or darken(). We no longer have such a thing as a plain old lamp. How can we have both? How can we have any type of lamp we choose?

Even with all the complexifying mechanisms we've seen so far, we've only scratched the surface of what's possible in Java, which in turn only scratches the surface of the universe's complexity.
 
Meeting the Machine

An inexperienced screenwriter's first meeting with a movie producer is often upsetting. It's the producer's job to tell the screenwriter that some of the neatest scenes in the script have to go, they're simply too expensive---it's one thing to write about blowing up the earth, it's quite another thing to film it happening. Similarly, as soon as you start thinking about implementing your hotel design from chapter one, you meet a whole new set of problems---this time because of the serious shortcomings of the primitive machine you're going to use to produce the solution you've designed.

Let's say you've identified most of the roles and role relationships you'll need, now you have to fill those roles---you have to cast your play. In a real hotel you'd just hire some people, tell them their roles and leave the details to them. It's not so easy in programming because the computer has no generally capable object like a human being that you can delegate all "the details" to. It has no idea how to do anything that you don't specifically tell it. You're now replacing the problem you started designing for with the problem of explaining the problem to the computer you're programming for. In programming terms, you must now implement your design.

You must now create a set of Java classes, each class corresponding to one of the roles you designed. Like the scenes a particular actor will play, each class needs methods specifying the various behaviors that actors playing that role will be capable of. Let's say, for instance, that all your actors must be able to respond to requests for their name. To make that happen you'll need to create a method called, say, getName(), that specifies how an object of a particular class responds to requests to produce its name. Most of the time, that behavior will be the same no matter what class the object belongs to. Instead of having to repeatedly specify it in every class, you can use inheritance by creating a StaffPerson class that has a generic getName() method and all other classes simply inherit that behavior from that "parent" class.

This is cool because you could then have classes like SleepStaffperson, PaperworkStaffPerson, FoodStaffPerson, and so on. Then more specialized classes can inherit from one of those general classes, saving you a lot of specification work. Class Waiter, say, could descend from FoodStaffPerson and thus inherit all the behaviors attached to all staff persons associated with food, (perhaps, findPlates(), addSalt(), sneer()), and also all behaviors attached to StaffPerson itself (say, workTooHard(), smile(), putUpWithCrazyBoss()). You've now begun to specify a class hierarchy, specifying not just the classes, but also how their internal structures relate to each other.


 
A Class Hierarchy
class hierarchy

 
Extending class Lamp

The universe has many complex types of things; but many of those types are related to each other. It's silly to try to model each one independently. Instead we can model one, then try to describe another in terms of the first, and so claw our way out of the complexity jungle. So instead of one monster class Lamp beginning as follows:


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

we could have two classes in package lamps: One, representing the traditional plain old vanilla lamp, beginning as follows:

package lamps;

public class Lamp
   {
   /*
   Define a lamp that can turn on and off,
   report its state, report its state changes,
   have a wattage, 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.
   */

And another, representing lamps with dimmer switches, beginning as follows:

package lamps;

import lamps.Lamp;

public class DimmerControlledLamp extends Lamp
   {
   /*
   Define a type of Lamp that can brighten and darken
   in stages, and compare its brightness with another lamp.
   */

Class Lamp will have the state and behavior we developed for plain old lamps. It will have a lampIsOn variable, a name variable, and a wattage variable. It won't have a brightness variable, since plain old lamps can only have a fixed brightness if they're on. It will also support the turnOn(), turnOff(), setName(), and getName() methods. Finally, it will have a class variable (a static variable) to hold every lamp's MAXIMUM_WATTAGE, and another to hold every lamp's DEFAULT_WATTAGE.

Class DimmerControlledLamp, on the other hand, only needs a brightness variable and three methods to report, alter, and compare its brightness: getBrightness(), setBrightness(), and isBrighterThan().

We connect the two with the word extends in the definition of class DimmerControlledLamp, as follows:


public class DimmerControlledLamp extends Lamp


 
Using an Extended Class

Once we create a DimmerControlledLamp object, how do we get it to do all the Lamp things it's supposed to? We would like to say:


   DimmerControlledLamp lamp;
   lamp = new DimmerControlledLamp();
   lamp.turnOn();
   lamp.turnOff();

just as if the new lamp, besides letting us use a dimmer switch, also let us turn it on and off like a normal non-dimmer-controlled lamp. Well, we can.

Although the new lamp is an object of class DimmerControlledLamp, it's also an object of class Lamp (since DimmerControlledLamp extends Lamp), so it can respond to all the normal Lamp requests defined in class Lamp. It inherits all those methods.

It, however, doesn't inherit a name variable, a lampIsOn variable, or a wattage variable since we defined those as private in class Lamp. Only objects of class Lamp get copies of those three variables.

We can treat the new DimmerControlledLamp object as if it were a Lamp object or as if it were a DimmerControlledLamp object. All the methods we previously defined only for Lamp objects are still available to us, as are the new methods we define in class DimmerControlledLamp.

If, however, we create a plain old Lamp object instead of a DimmerControlledLamp, like so:


   Lamp lamp;
   lamp = new Lamp();

we can expect it to do all the Lamp things we know Lamp objects can do, but we can't ask it to do DimmerControlledLamp things; it would have no idea how to do them. So the following is illegal:

   Lamp lamp;
   lamp = new Lamp();
   lamp.turnOn();
   lamp.brighten(); //error: normal lamps don't know how


 
The super Reference

There is one fly in this particular happy stew. Suppose we did the following:


   DimmerControlledLamp lamp;
   lamp = new DimmerControlledLamp(100);

expecting to get a 100-watt lamp that also has a dimmer switch. We would instead get a stern talking to from the Java interpreter. It would complain that there is no constructor that takes an int parameter in class DimmerControlledLamp.

The problem is this: we are here asking it to create a DimmerControlledLamp object, and it's perfectly happy to do that. Unfortunately, we're also asking it to then use the constructor that takes one int parameter defined in class Lamp to initialize that object's wattage to 100 watts. This it won't do.

Constructors, since they are named exactly the same as their class, can't be used directly by objects of other classes, even ones that extend the class. Constructors aren't methods---the Java interpreter executes one of them exactly once, and only when it creates the object. So if we need special initialization for objects of an extended class, we have to write new constructors.

Fortunately, we can refer to a class and its extended classes as a superclass and its subclasses. Using super in a subclass lets an object refer to the superclass portion of itself, just as using this lets an object refer to itself.

So here's a constructor that takes a wattage as a parameter for class DimmerControlledLamp:


   public DimmerControlledLamp(int wattage)
      {
      /*
      Initialize this lamp's superclass portion,
      Then set this lamp's initial brightness.
      */

      super(wattage);
      this.setBrightness(0);
      }

The strange statement "super(wattage)" asks the Java interpreter to execute the following constructor in the superclass, class 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.
      */

      lampIsOn = false;

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

      name = "Lamp";
      }

This Lamp constructor, which takes one int parameter, is in the Lamp class portion of the newly created object. That initializes the Lamp portion of the new DimmerControlledLamp object properly. After that, we also have to initialize the DimmerControlledLamp object's brightness variable, and that completes the initialization of the whole object.
 
The Secret Constructor

Every class has to have at least one constructor to initialize its objects. This brings up a puzzle: when we were writing SimpleRole, and again when we first started writing Lamp, we had no constructor of any kind. So if every class has to have at least one constructor why then did the Java interpreter let us get away without writing constructors for those classes?

The answer is that it didn't. Whenever we don't write any constructor for a class the Java interpreter secretly writes one for us. The secret constructor it writes is very simple though---it simply executes the zero-parameter constructor for the superclass portion of the object.

Here, for instance, is the secret constructor for SimpleRole:


   SimpleRole()
      {
      super();
      }

And here is the secret constructor for Lamp:

   public Lamp()
      {
      super();
      }

The Java interpreter always adds such a zero-parameter constructor to every class that we neglect to write a constructor for. This secret constructor simply requests execution of the object's superclass' zero-parameter constructor, whether one exists or not. If the superclass has such a constructor, this subclass request ensures that every object can create and initialize objects of each class we write unless we specifically disallow that (we'll find out how later).

The secret constructor for a class has the same access level as the class itself. So the secret constructor for SimpleRole has default, or package, access because its class, SimpleRole, has default access. On the other hand, the secret constructor for Lamp has public access because its class, Lamp, has public access.
 
A Confusing Error

If we write even one constructor for a class, the Java interpreter won't add the secret zero-parameter one---it assumes that we have covered all the different ways to initialize objects of that class ourselves. This can lead to an obscure error.

Here's the problem: suppose we add to class DimmerControlledLamp the above one-parameter constructor (which takes a wattage parameter), but we don't add any other constructors. Now when we try to do the following:


   DimmerControlledLamp lamp;
   lamp = new DimmerControlledLamp();

the Java interpreter complains.

This code worked just fine before we added any constructor at all, since the Java interpreter secretly added a zero-parameter constructor to class DimmerControlledLamp for us. But when we add even one constructor, the Java interpreter (foolishly) assumes that we know what we're doing and doesn't add the zero-parameter constructor it would have added if we hadn't bothered to add any constructor at all.

The Java interpreter is using the same assumption that it uses when it secretly prefixes this to method execution requests or variable accesses. It only does so when we don't state a specific object to execute the method or to access the variable. If we state an object to do the work, the Java interpreter doesn't try to second-guess us; it assumes that we know what we're doing. The same goes for constructor execution requests.

So when we try to execute the above innocent-seeming object creation request the Java interpreter complains because class DimmerControlledLamp doesn't have a zero-parameter constructor for the Java interpreter to execute after creating a new DimmerControlledLamp object, which is what we're asking it to do when we say:


   lamp = new DimmerControlledLamp();

Further, the Java interpreter will complain even though class Lamp, which class DimmerControlledLamp extends, already has a zero-parameter constructor. The Java interpreter does its best, but it isn't very bright.

So if we ever need to initialize DimmerControlledLamp objects with a zero-parameter constructor, we need to add one just like the secret constructor to class DimmerControlledLamp. It's good style to always add such a constructor to every class we write, since that avoids the above problem entirely.
 
Class Object

Every class that does not explicitly extend another class always extends class Object. The Java interpreter secretly adds the words "extends Object" to every class definition that does not already extend some class. So even though we say:


public class Lamp

class Lamp actually begins this way:

public class Lamp extends Object

and we can write it that way if we want. Declaring class Lamp that way makes it clear that it extends class Object.

Class DimmerControlledLamp, on the other hand, will always appear as:


public class DimmerControlledLamp extends Lamp

since it already extends a class. The Java interpreter won't let us write a class that extends two or more classes.

Although class DimmerControlledLamp doesn't extend class Object explicitly, it still extends class Object through class Lamp, since class Lamp secretly extends class Object. All classes extend class Object one way or the other.

In theater terms, class Object is like a generic class Actor that all classes extend. If such a thing existed, class Actor would specify all the things every actor must do. In the theater world, that might be things like being able to find the stage, knowing how to hold a prop, knowing how to speak, and so on. In the Java world, class Object specifies things like how to tell whether one object has the same variables and values for those variables as another object (the equals() method) and being able to report a String representation of an object (the toString() method).

Since class DimmerControlledLamp extends class Lamp and class Lamp extends class Object, every DimmerControlledLamp object is also an Object object. So it will know how to respond to requests to equals() or toString().

Every class (except class Object) extends some class. That superclass may extend some other class, and so on, but we must eventually arrive at some class that (secretly) extends class Object. So every class extends class Object one way or the other. So all objects, no matter their class, will know how to respond to equals() or toString().
 
Secret Summary

Here are the five Java secrets we've uncovered so far. The Java interpreter secretly:

* prefixes this to every method execution request or variable access whose object we don't already specify.
* creates an unnameable object associated with each class to execute class methods and to hold and access class variables.
* creates an unnamed package to hold all classes whose package we don't already specify.
* adds a zero-parameter constructor containing super(), with the same access level as its class, to every class whose constructors we don't already specify.
* extends from class Object every class whose superclass we don't already specify.
There will be more.
 
Overriding Methods

Suppose we want DimmerControlledLamp objects to turn on smoothly from zero brightness to some maximum initial brightness, which is, let's say, 3. We should name that constant to avoid littering our code with the number 3 then forgetting what it stands for. Let's call it, say, INITIAL_BRIGHTNESS. It should of course be a class constant for class DimmerControlledLamp (that is, it should be static and final), and, since it's a constant, it can be public as well.

A natural name for the new smooth on method is turnOn(). But we already have a method named turnOn() in class Lamp. Since class DimmerControlledLamp extends class Lamp, all DimmerControlledLamp objects already know how to turnOn().

To avoid a method name conflict we could do several things. For instance, we could name the new method differently, say, turnOnSmoothly(). Or we could write another turnOn() method, this time in class DimmerControlledLamp, that takes some parameter (perhaps the initial brightness). This time, the name is the same, but the signature is different.

Neither of those two ideas are good. Either way, we would then have no general way to turnOn() either type of lamp with one fixed method execution request. We would always have to remember which type of lamp we're trying to turnOn(). This is a lose.

It would be nice to tell either type of lamp to simply turnOn() and have the lamp itself decide how it's going to turn on based on its own particular type. Normal lamps would simply snap on, but dimmer-controlled lamps would come on smoothly. This would put the intelligence about how to turnOn() where it belongs---in the lamp.

We get exactly that behavior if we name the new DimmerControlledLamp method turnOn() and give it exactly the same signature as class Lamp's turnOn() method:


public class DimmerControlledLamp extends Lamp
   {
   //maximum initial brightness for each dimmer-controlled lamp
   public final static int INITIAL_BRIGHTNESS = 3;

   //some code

   public void turnOn()
      {
      /*
      Turn this lamp on gradually until it is at
      DimmerControlledLamp.INITIAL_BRIGHTNESS.
      */

      this.setBrightness(1);
      for (int index = 1;
         index < DimmerControlledLamp.INITIAL_BRIGHTNESS;
         index++)
         {
         brighten();
         }
      }

   //some more code
   }

Now there are two turnOn() methods, both with the same signature; one is in class Lamp and the other is in class DimmerControlledLamp.

When we create a DimmerControlledLamp object and ask it to turnOn(), it will execute the turnOn() method we just defined in class DimmerControlledLamp, not the one we earlier defined in class Lamp. The DimmerControlledLamp's turnOn() method overrides the Lamp's turnOn() method.

We must also override class Lamp's isOn() and reportState() methods in class DimmerControlledLamp otherwise all methods in DimmerControlledLamp will use the versions of those two methods it inherits from class Lamp. Those versions use the lampIsOn variable and the wattage variable instead of the brightness variable.
 
Keeping Objects Encapsulated

While the idea of overriding method turnOn() is good, the code itself is not. Here's a better way to implement the new turnOn() method:


   public void turnOn()
      {
      /*
      Turn this lamp on gradually until it is at
      DimmerControlledLamp.INITIAL_BRIGHTNESS.
      */

      this.setBrightness(1);
      brighten(DimmerControlledLamp.INITIAL_BRIGHTNESS - 1);
      }

Using the one-parameter brighten() method is better than using the zero-parameter brighten() method, since the one-parameter brighten() already uses the zero-parameter brighten(). That ensures unique access to the zero-parameter brighten() method, at least within class DimmerControlledLamp, and unique access helps ensure encapsulation in case we ever need to change either class.

Each object should be in sole control of its own state, and each variable should only have unique access methods. That's still true even for objects of subclasses accessing variables or executing methods of their superclass. We have to be ever vigilant about keeping our objects encapsulated and not let easy ways of coding seduce us into style crime.
 
Method Overloading versus Method Overriding

We overload a method when we add more than one method with the same name but each with different signatures, to a single class. We override a method in a class when we extend the class and create another method with exactly the same signature in the subclass. The subclass method overrides the superclass method.

For example, in the turnOn() method of class DimmerControlledLamp that overrides the version of the turnOn() method in class Lamp, a DimmerControlledLamp object can execute either version of the two overloaded brighten() method we defined in class Lamp since every such object inherits it from class Lamp. There are four methods in all---two overloaded versions of brighten(), with different signatures, in class DimmerControlledLamp, and one version of turnOn() in class Lamp, and another version, with the same signature, in class DimmerControlledLamp that overrides the one in class Lamp.

While we can override and overload methods as much as we want, and we can overload constructors as much as we want, we can't override constructors, since a subclass does not inherit its superclass' constructors. We'll find out why later.

In brief, overloading adds behavior to a class, overriding replaces behavior in a subclass. Method overloading and overriding add yet more layers of code complexification on top of the schemes we're already familiar with: if, if-else, while, for, method execution requests, and return statements. Now we have two more ways to control statement execution order.
 
Overriding toString()

Let's now override a method from class Object in class Lamp. Recall that every Lamp has a String name variable holding a string representing its name. We want to report that name in our toString() method for class Lamp. Here's one way to do it:


   public String toString()
      {
      /*
      Report this lamp's type and name.
      */

      return "Lamp[" + this.name + "]";
      }

If a lamp's name variable contains the string, say, "kandinsky", then asking it to execute this method results in a String object containing the string "Lamp[kandinsky]".

This new toString() method is not the same as class Lamp's getName() method. That method returns the string value of a Lamp object's String name variable. The toString() method, however, returns a string representation of the name and type of the lamp.
 
Fun with Strings

Here's how the above toString() method works. Because of all of the String shortcuts the Java interpreter allows us, the simple-looking statement:


   return "Lamp[" + this.name + "]";

is short for the much longer statement:

   return ((new String("Lamp[")).concat(this.name)).
      concat(new String("]"));

Let's break this down. First we create a new String object containing the string "Lamp[":


   new String("Lamp[")

Then we ask that String object to execute its concat() method with a String parameter that is the name of this object and produce another String object containing the two strings joined into one:


   (new String("Lamp[")).concat(this.name)

Then we ask that new String object to concat()enate the string contents of a new String object containing the string "]" and produce yet another String object containing the two previous strings:


   ((new String("Lamp[")).
   concat(this.name)).concat(new String("]"));

Finally, we ask the Lamp object executing this toString() method to return the final String object produced, which contains a concatenation of all three original strings:


   return ((new String("Lamp[")).concat(this.name)).
      concat(new String("]"));

We shorten that whole long series of requests to this:


   return "Lamp[" + this.name + "]";


 
Why Override toString()?

Class Object already has a toString() method. Why override it in class Lamp? The problem is that class Object's toString() is generic. It's like putting a method in our mythical class Actor that says "I am an Actor". Big whoop.

Each type of actor should at least fill in some particulars and say, for instance, "I am a Lead Actor", or "I am a Walk-On", or "I am a Butler". Further, it would be nice to distinguish between various actors all playing the same role, so that actors could say, for example: "I am a Butler and my name is Jeeves".

Our hypothetical class Actor can't know those particulars. Similarly, class Object can't either. Each class should have its own toString() to at least report the name of the role, and---even better---some unique way to refer to each particular actor playing that role.

Once every class has a toString() then given any reference variable we can always do the following:


   System.out.println([reference variable].toString());

and print a string representation of the name of the particular object that the reference variable points to. Of course, that name is just a String, it's not the same as the actor's secret name, which we can never know.
 
Overloading println()

The toString() operation is so common that we can shorten this:


   System.out.println([reference variable].toString());

to just this:

   System.out.println([reference variable]);

The println() method is smart enough to know that if it's given just a reference variable it should first ask the object that the variable names to execute its toString() method (every object must have one, if only the one derived from class Object). Then it prints the String the object returns.

What looks like one clever println() method is really only one of several overloaded versions, each of which takes a different type of parameter (that is, each has a different signature). Because they are all overloaded, we don't need to use a special name for each one of them, we simply say println() and it just works.

Both overloading and overriding help objects be smarter about what they should do without us having to always specify every last detail of what they should do. The smarter our objects are, the easier it is for us to combine them to do more complex things.
 
Using toString()

We're not finished with toString() yet because we have another class of objects to name, namely: class DimmerControlledLamp. Now, since class DimmerControlledLamp extends class Lamp, when given a DimmerControlledLamp object whose name is "kandinsky" we can always say:


   System.out.println([reference variable]);

and execute the toString() method that class DimmerControlledLamp inherited from class Lamp. (That toString() overrides the toString() that class Lamp itself inherited from class Object.) So this will print the string "Lamp[kandinsky]".

This is odd, though, because the String variable name is private in class Lamp. Since private variables and methods aren't passed on from a class to its subclasses, how could this possibly work if class DimmerControlledLamp didn't inherit a name variable from class Lamp?

Here again is the toString() method we added to class Lamp:


   public String toString()
      {
      /*
      Report this lamp's type and name.
      */

      return "Lamp[" + this.name + "]";
      }

This method clearly needs the name variable. Fortunately, that variable is accessible to the method since both the variable and the method are in class Lamp. That's the method we're really executing here, even though we're executing it with a DimmerControlledLamp object. All DimmerControlledLamp objects inherit the toString() method from class Lamp since it's declared as public in class Lamp.

To summarize, we're asking a DimmerControlledLamp object to execute a public method. That object is also a Lamp object since DimmerControlledLamp extends class Lamp. Any DimmerControlledLamp objects executing that method use the contents of the private name variable of class Lamp, then return a String object containing the string contents of the variable.
 
Overriding toString() Again

Our current toString() method in class Lamp might be enough for some purposes, but if we want more specific information about class DimmerControlledLamp objects, we should override toString() yet again. This time we're going to override the one in class Lamp with yet another one in class DimmerControlledLamp.

In class DimmerControlledLamp we'd like to write:


   public String toString()
      {
      /*
      Report this lamp's type and name.
      */

      return "DimmerControlledLamp[" + this.name + "]";
      }

as we did in class Lamp, but it doesn't work. The problem now is that the name variable is private to objects of class Lamp. Recall that private means that the variable or method is only available to objects of the same class. Even though class DimmerControlledLamp extends class Lamp, the Java interpreter won't let us directly refer to private variables of class Lamp in objects of class DimmerControlledLamp.

This is true even though objects of class DimmerControlledLamp can use the contents of the name variable while executing the public toString() they inherited from class Lamp. Simply being able to see the contents of variable name is a courtesy that class Lamp extends to any object since it supports a public getName() method to report it. It's quite another thing for class Lamp to let non-Lamp objects access the variable. Any object that can access the variable can change its contents. Only class Lamp objects can access that variable.

Fortunately, we can get around this because class Lamp also has a public getName() method that we can use to get a Lamp object's name. Even though name is private in class Lamp, objects of any class, including class DimmerControlledLamp, can ask a Lamp object to execute the method.

Now, here's the trick: a DimmerControlledLamp object is also a Lamp object, so it can ask itself to execute its getName() method, since it inherited one from class Lamp.

So here's a version of toString() that works for class DimmerControlledLamp:


   public String toString()
      {
      /*
      Report this lamp's type and name.
      */

      return "DimmerControlledLamp[" + this.getName() + "]";
      }

Now, when given a reference variable pointing to a DimmerControlledLamp object whose String name is "kandinsky" and we say:


   System.out.println([reference variable]);

it will print "DimmerControlledLamp[kandinsky]".

We now have three versions of toString(). One in class Object, one in class Lamp, and one in class DimmerControlledLamp. Each one overrides the last.
 
Using protected Variables and Methods

By declaring a class' variables as protected we let objects of the class' subclasses access those variables. Similarly, objects of a class' subclasses can execute the class' protected methods.

So we could solve our last problem of gaining access to the private name variable in class Lamp by changing its access modifer in class Lamp from private to protected. Then we could directly access it in a DimmerControlledLamp object.

Unfortunately, protected also means that objects of classes in the same package as the given class also have access. So an object's protected variables (or its methods) can be accessed directly by other objects of the class, by objects of subclasses of the class, or by objects of any class in the same package as the class. That's too much accessibility.
 
Modifier Classes Methods and Variables
private -undefined- objects from same class
-none-
[=package]
objects from same package objects from same class or
objects from same package
protected -undefined- objects from same class or
objects from same package or
objects from a subclass
public all objects all objects


 
Back To Encapsulation

It seems so much easier to declare the name variable of class Lamp as protected and not have to bother with accessor methods like setName() and getName(), but that would lead us directly into style crime. We would have broken the encapsulation of class Lamp.

We fell into the previous problem not so much because class Lamp's name variable was private but because we were seduced by our too casual use of the name variable inside class Lamp in the first place. Using a naked name variable inside class Lamp is a style crime because we already have a setName() and getName() method in that class. We should have been using those instead since that forces unique access to the variable.

Forcing unique access to the name variable even in class Lamp would have made it much easier to see how to create another toString() in class DimmerControlledLamp() since it too would have access to getName() (but not name) since getName() is public while name is private.

The problem isn't serious when we have only one variable and only one extended class, but imagine how hard it is to tell just where, when, and how we're changing any one of dozens of protected variables when we can access any of them from any subclass going down, say, ten levels deep.

The Java interpreter only makes this problem worse by also allowing objects of any class in the same package to access protected variables.

It's much safer to keep all non-final variables private and then add public or protected accessor methods to those that either every object (for the public methods) or every descendant or packaged object (for the protected methods) must have access to.

In sum, although protected gives more, ah, protection to variables than simply making them public, all non-final variables should still be declared private. We can, however, now modify the rule for methods. Instead of saying that all methods for external use should be public, we could distinguish between those everyone can use (which must be public), those solely for objects of subclasses, or objects of classes in the same package (protected methods), and those intended solely for objects of the class itself (private methods),

Consequently, although we'll still keep all our non-final variables private, we might make class DimmerControlledLamp's private setBrightness() and getBrightness() methods protected instead.
 
Interfaces

So far we've been going from class Lamp to more specialized types of lamps, what though about going from class Lamp to more general light sources? Suppose we wanted to add classes to describe matches, and cigarette lighters, and candles, and flashlights, and bonfires, and fireflies? Most of these don't have a wattage, a name, or anything like a dimmer switch, but they all can give light, as lamps do. To handle that the Java interpreter gives us interfaces.

An interface is a bit like a class except that we never specify any behavior for the methods, we simply state their signatures. First we define an interface that all light sources must implement, then we can implement the interface (that is, define the behavior of the interface's declared methods) in lots of different classes. None of those classes need to extend another.

We state that we're simply declaring a method but not specifying its implementation with the word abstract (as opposed to concrete). Here then is an interface that all Lamp objects could implement:


public interface Luminous
   {
   public abstract void turnOn();
   public abstract void turnOff();
   }

Every method in an interface must be public and abstract, although if we leave those off the Java interpreter will secretly add them for us. We need the semicolons after each method name because we're not defining the methods (that is, we're not specifying how to implement them) we're simply declaring methods of that particular signature. It's up any classes implementing the interface to specify complete methods with those signatures.

We could then say, for example:


import Luminous;

public class FireFly implements Luminous
   {
   //code to implement the turnOn() and turnOff() methods
   //declared in the Luminous interface
   //plus code to do other FireFly things (like fly())
   }

We could also say:

import Luminous;

public class Lamp implements Luminous
   {
   //code to implement the turnOn() and turnOff() methods
   //declared in the Luminous interface
   //plus code to do other Lamp things (like setName())
   }

From then on we could treat both lamps and fireflies as if they were objects of classes that extended some mythical Luminous class.
 
Using Interfaces

Interfaces help us make elegant programs. Suppose, for instance, we're building a display with a number of light sources on the screen. We might have fireflies flying around and turning on and off as they do so, fixed lamps of various fixed brightness, dimmer-controlled lamps, the sun rotating above, a bonfire on a beach, flashlights, flints, and so forth.

The behavior of each such object might be defined in a completely different class, some (or all) of which we may or may not have written, and some (or all) of which we may have no control over. Once each of those classes implement the Luminous interface, though, we can hide all of that messiness behind the interface. In our display program we can create a set of Luminous objects from any classes that implement the interface, then ask them to turnOn() and turnOff() as we wish.

Here's some sample code:


   //create an object to hold 4 Luminous reference variables
   Luminous[] lights;
   lights = new Luminous[4];

   //create 4 Luminous objects
   lights[0] = new FireFly();
   lights[1] = new Lamp();
   lights[2] = new DimmerControlledLamp();
   lights[3] = new Bonfire();

   //tell each of them to turnOn() and turnOff()
   for (int index = 0; index < lights.length; index++)
      {
      lights[index].turnOn();
      lights[index].turnOff();
      }

Their uniform interface let's use completely ignore how each of the classes implements those two methods. We don't have to care that fireflies can fly() or that cigarette lighters can burnOut(). All that matters is that they can both turnOn() and turnOff()---they both implement the Luminous interface.

We name interfaces the same way we name classes---by concatenating a collection of capitalized words that describe the interface. It's good style, however, to make interface names adjectives, or adjectival phrases, and to make class names nouns or noun phrases. That helps us distinguish between them while keeping the uniform structure of their names.
 
Contracts and Factories

Interfaces are elegant, but they present us with a serious dilemma. What exactly are they? So far, whenever we've declared a certain thing, whether it's a variable, a class, or a method, we've also defined it (that is, given instructions on how it should act). Interfaces, though, are pure declaration; there is no definition at all.

The key to the puzzle is this: A class is really two related things rolled into one. The first is its factoryness: it can direct the production of objects. The second is its contractness: it guarantees that those objects will know how to do certain things. These two aspects of classes are related, but not the same.

We can imagine having a contract with no factory attached, but we can't have a factory with no contract attached. Even an empty factory (a class with no methods or variables, or a package with no classes) has a contract: it's promising to do absolutely nothing. A contract with no factory attached, however, is imaginable. It's the same as a mask at a masquerade---it lets anything pretend to be whatever the mask portrays, so in that sense it lets objects change their roles. In Java, such masks are called interfaces.
 
Extending Interfaces

We can extend interfaces to produce new interfaces just as we can extend classes to produce new classes. Say, for instance we wanted to declare an interface Heroic.


public interface Heroic
   {
   public abstract void fly();
   public abstract void swim();
   public abstract void overAct();
   }

We could instead have an interface CanSwim


public interface CanSwim
   {
   public abstract void swim();
   }

and an interface CanFly

public interface CanFly
   {
   public abstract void fly();
   }

and have Heroic extend from CanSwim and CanFly:

public interface Heroic extends CanSwim, CanFly
   {
   public abstract void overAct();
   }

Unlike classes, interfaces can extend from multiple other interfaces. When extending from multiple interfaces we use commas to separate the superinterface names.

Any class implementing Heroic would have to define methods to fly(), overAct(), and swim(). Interface Heroic extends from interface CanSwim, and interface CanFly, so being able to fly() and swim() is part of the contract of all Heroic objects. They all promise to know how to do those things.

Although interfaces can extend from each other, and classes can extend from each other, classes cannot extend from interfaces. They can, however, implement them. Also, although interfaces can extend from multiple other interfaces, classes can only extend from one other class. Classes, however, can implement multiple interfaces.
 
Abstract Classes

An interface is purely abstract since we can't define any implementation for the behavior we're requiring. A class---at least all the ones we've seen so far---is the opposite, it's completely concrete. We can also have classes that are inbetween; these are abstract classes.

In theater terms, an abstract class is like a draft of a screenplay. Some parts of the screenplay may be fleshed out while others may still be vague, except that we know that there must be a huge explosion (or a great kiss) at the end. We can leave all the particulars to other writers to complete.

For example, suppose we were writing a collection of classes to model an orchestra. We might have a Musician class, a Violist class, a Violinist class, a Pianist class, and so on. When writing the Musician class we'd like to specify all the things that professional musicians do: getting paid, rehearsing, renting a costume, starving, and so on. Above all, though, we would want to specify that they can play an instrument. This, however, presents a problem.

There is no single instrument that all musicians play, so we can't implement the play() method when writing class Musician. Only each subclass of Musician, for example, Violist, can do that. So we should declare the play() method in class Musician as abstract. The class as a whole would then itself be abstract. We can't create a generic musician; we can, however, create violists, or pianists, and so forth. Each of them would be special kinds of musicians.

If class Musician can't implement its play() method why bother to have it in class Musician at all? Why not have each subclass implement a play() method, just as we must do if class Musician weren't abstract?

The answer is that there is then no guarantee that a subclass of class Musician will definitely have a play() method. It may, or it may not. We can't tell for sure until we look at the class. If, however, we declare play() in class Musician, and if we declare it as abstract, then every client class is guaranteed that each non-abstract subclass of class Musician must have implemented a play() method. It's part of the contractual obligations of every Musician object.

We can declare a class to be abstract yet still implement all its methods. That is, a class can be abstract without any of its methods being abstract. That's useful when we don't want objects to be able to create objects of the class, even though they might be able to create objects of subclasses of the class.
 
Reverting To Type

Each way of expanding the idea of class leads directly to the idea of type. When we define a class we also define a type; it's just that we also define a way for the class to implement the type (that is, construct objects having that type). When we define an abstract class we again define a type; this time though, all or some (or none) of the methods in the class might be abstract; in other words, we needn't bother to explain how to do everything. When we define an interface we are declaring a pure type; we add no hints whatsoever on how it should be implemented.

An interface is a contract that all classes implementing the interface promise to abide by. And that, in essence, is what a type is. It's something that specifies how things should behave, not necessarily what they have to do to behave that way.

It's now easy to see how to create complex Java programs: we merely decide what the behavior should be of each type of thing we need in the program. Then we declare interfaces, or abstract classes, or concrete classes, to describe those expected behaviors. Then we link related ones with extends or implements to cement the relations between them. That avoids us having to describe each one in massive detail, instead only describing each one in terms of their relation to others we have already defined. We keep on this way until everything is realizable as an object of a concrete class somewhere. At that point, we're done.

Of course, as we shall learn, nothing worthwhile is ever that simple.
 
The Whole Program---Again

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


package lights;

public interface Luminous
   {
   public abstract void turnOn();
   public abstract void turnOff();
   }


package lights;

import lights.Luminous;

public class Lamp extends Object implements Luminous
   {
   /*
   Define a lamp that can turn on and off,
   report its state, report its state changes,
   have a wattage, 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 is either on or off
   private boolean lampIsOn;

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

      lampIsOn = false;
      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.
      */

      lampIsOn = false;

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

      name = "Lamp";
      }

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

      lampIsOn = true;
      reportState();
      }

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

      lampIsOn = false;
      reportState();
      }

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

      return lampIsOn;
      }

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

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

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

      this.name = name;
      }

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

      return name;
      }

   public String toString()
      {
      /*
      Report this lamp's type and name.
      */

      return "Lamp[" + this.getName() + "]";
      }
   }


package lights;

import lights.Lamp;

public class DimmerControlledLamp extends Lamp
   {
   /*
   Define a type of Lamp that can brighten and darken
   in stages, and compare its brightness with another lamp.
   */

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

   //maximum initial brightness for each dimmer-controlled lamp
   public final static int INITIAL_BRIGHTNESS = 3;

   public DimmerControlledLamp()
      {
      /*
      Initialize this lamp's superclass portion,
      then initialize this lamp's brightness.
      */

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

   public DimmerControlledLamp(int wattage)
      {
      /*
      Initialize this lamp's superclass portion,
      then initialize this lamp's brightness.
      */

      super(wattage);
      this.setBrightness(0);
      }

   public void turnOn()
      {
      /*
      Turn this lamp on gradually until it is at
      DimmerControlledLamp.INITIAL_BRIGHTNESS.

      This method overrides the same method in the superclass.
      */

      this.setBrightness(1);
      brighten(DimmerControlledLamp.INITIAL_BRIGHTNESS - 1);
      }

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

      This method overrides the same method in the superclass.
      */

      darken(this.getBrightness());
      }

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

      This method overrides the same method in the superclass.
      */

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

   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();
      }

   protected void setBrightness(int brightness)
      {
      /*
      Let another lamp, any lamps of extended classes,
      or any objects in this package
      set this lamp's brightness.
      */

      this.brightness = brightness;
      }

   protected int getBrightness()
      {
      /*
      Report this lamp's brightness to another lamp,
      to any lamps of extended classes,
      or to any objects in this package.
      */

      return brightness;
      }

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

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

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

      This method overrides the same method in the superclass.
      */

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

   public String toString()
      {
      /*
      Report this lamp's type and name.

      This method overrides the same method in the superclass.
      */

      return "DimmerControlledLamp[" + this.getName() + "]";
      }
   }


import lights.Luminous;
import lights.Lamp;
import lights.DimmerControlledLamp;

public class FiddleWithLights
   {
   /*
   Create some lights and fiddle with them.
   */

   public final static int NUMBER_OF_LAMPS = 2;
   public final static int NUMBER_OF_LIGHTS = 2;

   public static void main(String[] parameters)
      {
      //create an array of lamps reference variables
      Lamp[] lampArray;
      lampArray = new Lamp[FiddleWithLights.NUMBER_OF_LAMPS];

      //create a 100-watt lamp
      lampArray[0] = new Lamp(100);
      lampArray[0].setName("Lamp number 0");

      //for every other Lamp reference variable in the array,
      //create a new DimmerControlledLamp and name it
      for (int index = 1; index < lampArray.length; index++)
         {
         lampArray[index] = new DimmerControlledLamp();
         lampArray[index].setName("Lamp number " + index);
         }

      //switch each lamp on and off
      for (int index = 0; index < lampArray.length; index++)
         {
         System.out.println(lampArray[index]);
         lampArray[index].turnOn();
         lampArray[index].turnOff();
         System.out.println();
         }

      //create an array of Luminous reference variables
      Luminous[] lightsArray;
      lightsArray =
         new Luminous[FiddleWithLights.NUMBER_OF_LIGHTS];

      //create some Luminous objects
      lightsArray[0] = new Lamp();
      lightsArray[1] = new DimmerControlledLamp();

      //switch each light on and off
      for (int index = 0; index < lightsArray.length; index++)
         {
         System.out.println(lightsArray[index]);
         lightsArray[index].turnOn();
         lightsArray[index].turnOff();
         System.out.println();
         }
      }
   }

last | | contents | | next