The Truth About static


The Truth About static

The time has come to show you how classes, objects, static code, and non-static code all work together to create a complete object-oriented Java application program. To understand what's going on in an application, you need to be clear on the difference between static and non-static parts of a class. You have already learned that methods can be either static or not, and that most methods in most programs are non-static. Data, as well as methods, can be either static or not. Let's look at this distinction.

Static Data

The previous section explained that when a class defines data members, each instance of the class gets its own version of each data member. This is true for ordinary non-static data. If you add static to the declaration of a variable in a class, you defeat this one-version-per-instance mechanism. Instead of getting one version of the variable for each instance, you just get one version of the variable, period.

Here's a version of the Person class that has a static variable:

 1. public class Person  2. { 3.   static int    rev = 3;  4.          int    age;  5.          short  weight;  6.  7.   int ageInNYears(int n)  8.   {  9.     return age + n; 10.   } 11. 12.   void dump() 13.   { 14.      System.out.println("rev " + rev + 15.                         " age = " + age); 16.   } 17. }

Static variables have limited uses. One possible use is to keep track of the current revision of the class source. Here you set the rev to 3. (As with any other variable declaration, you can initialize a static class variable in the same line where you declare it.) The rev is 3 because version 1 from earlier in this chapter just had data, rev 2 from the previous section had data and a method, and that brings us to rev 3.

When the dump method executes, the version of age that gets printed out is of course the version belonging to whatever Person object is executing the method. The version of rev that gets printed out is... well, there is only one version, because the variable is static. Consider the following example:

Person thelma = new Person(); thelma.age = 28; Person louise = new Person(); louise.age = 38; thelma.dump(); louise.dump();

The output of this code is

rev 3 age = 28 rev 3 age = 38

The (static) rev does not change but the (non-static) age does.

Now consider the following code, which uses static data to get into trouble:

Person thelma = new Person(); thelma.age = 28; Person louise = new Person(); louise.age = 38; thelma.rev = 999;  // Change thelma's rev louise.dump(); 

Now the output is

rev 999 age = 38

If you didn't know that rev was static, you would be surprised by the output. The code seems to change the rev of thelma, not of louise. Of course, since rev is static, it doesn't belong to an individual object, so it isn't really meaningful to talk about the rev "of thelma" or "of louise." There is just the rev.

Java offers you a way to refer to static variables without risking the confusion of the previous example. Instead of saying thelma.rev or louise.rev, you can say Person.rev. In other words, instead of the reference-dot-staticVariableName syntax, you can use classname-dot-staticVariableName. This makes static variable usage more conspicuous, because typical class names begin with capital letters, while reference names begin with lowercase letters. (This is not a requirement of the language; it is a style convention. There is no benefit to violating this convention.)

Using the new syntax, the previous example can be rewritten as

Person thelma = new Person(); thelma.age = 28; Person louise = new Person(); louise.age = 38; Person.rev = 999; louise.dump();

The change makes it clear that the thing that's getting set to 999 is a static variable, so the output should now be no surprise to anyone. The classname-dot-staticVariableName notation reinforces the fact that the variable does not belong to any instance. It is convenient to think of statics as belonging to the class as a whole, rather than to an individual instance. By contrast, non-static variables are sometimes called instance variables.

We can now move from static data to the more subtle concept of static methods.

Static Methods

You have just seen that a static variable is not associated with an individual object. Similarly, a static method can be thought of as acting in a way that is not associated with an object.

In an instance method (that is, a non-static method), access to an instance variable meant access to the version of the variable owned by the currently executing object. Thus, in the Person class, the ageInNYears method used the age variable. Calling the method on thelma meant that the method would use thelma's age. Calling the method on louise meant that the method would use louise's age. In all cases, the method used the current object's version of the variable.

In a static method, there is no current object, so it would be meaningless for a static method to use a non-static variable. (Which object's version of that variable should be used? There's no good answer.) A static method is not allowed to read or write the non-static data of its class. Also, a static method may not call the non-static methods of its class.

Sort of.

To really understand what static code can and cannot do, you need to know about a useful Java feature called the this-reference notation.

Earlier in this chapter, you learned about the reference-dot-variableName and reference-dot-methodName notations. These constitute the grammar that lets Java be object-oriented. In object-oriented programming, you specify not only what data or method you want to access, but the object that owns the data or method. But within the instance methods we have seen, instance variables have been accessed without the reference-dot notation. The ageInNYears method returned age + n, and there is no reference-dot notation there.

In an instance method, any use of a variable without a reference-dot prefix is something like an abbreviation. For example, the method

int ageInNYears(int n) {   return age + n; }

can be thought of as an abbreviation of

int ageInNYears(int n) {   return this.age + n; }

The keyword this, also known as the this-reference, is a reference to the current object. So if you called thelma.ageInNYears(20), within the method, this would reference the same object thelma referenced.

A static method has no this-reference, so it cannot use the abbreviated notation enjoyed by non-static methods. A static method can indeed access non-static data and methods of its own class, or of any other class, but the method must explicitly provide a reference to the intended object. So the following would be perfectly legal:

static void printLouisesAge(Person louise) {   System.out.println("Louise is " +                       louise.age); } 

We can summarize all this static/non-static information as follows:

  • A non-static method may use non-static data and methods of its class without using the reference-dot notation. The current object is implied.

  • A static method must use the reference-dot notation. There is no current object.

  • A static method has no this-pointer.

Now at last, we can tie everything together and explain the role of the static main method.

The main Method

You have seen that static features of a class are a way of getting around the object-oriented requirement that data must live inside objects and methods must be called on objects. Ideally, an object-oriented program would be a federation of objects of many different classes that make method calls on one another, creating new objects as needed and allowing old ones to be garbage-collected when no longer needed. This image is fine once the application is up and running, but how does the process get started? If objects are constructed by other objects invoking new, how does the first object get created?

In Java, everything starts with the main method. Through the end of the last chapter, you patiently tolerated the presence of static in the main method's declaration. Now you know what it means: main is not called on any individual object. It is static, so it is just called. Within main, objects can be created and non-static calls can be made, so the object interactions quickly become highly object-oriented.

Every application is invoked by typing

 java ApplicationClassName 

(The animated illustration programs require a prefix and a dot before the class name. We will explain that notation in Chapter 9.) When you start up an application, a program called java is executed. This is the Java Virtual Machine. The JVM does not create an instance of the application class. It could have been designed to do so, but the creators of Java decided to let us programmers decide when and how to create objects.

After the JVM initializes itself, it makes use of one of its parts, called the class loader. The class loader is code that finds, reads, and interprets .class files. After the class loader processes a .class file, the JVM is changed in two ways:

  • The class defined in the file can be used by the JVM.

  • Any static data declared in the class is allocated and initialized.

Initially, the JVM uses the class loader to load the class specified in the command line. Later on, during the course of execution, any class used in the code that has not been loaded already is loaded as needed. Since the class loader allocates and initializes static data before any instances of the class are constructed, you can access a class's static data, and even call its static methods, even if no instances of the class exist.

That's good news, because the next thing the JVM does is call a static method of the class it just loaded. Of course, this is the main method. Presumably, main constructs objects that construct objects that construct objects, and the program enters its object-oriented phase. Static data and methods can still be used, but typically most accesses are non-static.

The ObjectLifeCycleLab animated illustration demonstrates how static code starts the chain of object-oriented interactions. Start the program by typing java objects.ObjectLifeCycleLab. At first the display only shows a star, representing the static main method of an application. This initial state is shown in Figure 7.9.

click to expand
Figure 7.9: ObjectLifeCycleLab

When you click the Run button, the static code constructs an object that is an instance of one of three classes: Triangle, Rectangle, and Oval. Each object contains its own data and methods. The static code makes a method call on the object, represented by an expanding arrow. The colored dots near the arrowhead represent method arguments.

Now the code in the first object's method constructs a second object, on which a method call is made. The call is returned (that colored dot near the shrinking arrowhead represents a return value), and the life cycle goes on and on and on. Sometimes objects vanish; this represents garbage collection. Figure 7.10 shows ObjectLifeCycleLab after running for several minutes.

click to expand
Figure 7.10: ObjectLifeCycleLab after running a while

Of course, ObjectLifeCycleLab is just a symbolic cartoon, but it illustrates several very important concepts. Watch the program until you observe the following behaviors:

  • Everything starts with static code, all alone.

  • At any moment, the static code or a single object is current (recognizable by a highlighted background and flashing data).

  • An object only gets garbage-collected if it is not in use.

Reference Data

Variables in a class don't have to be primitive. Classes may define variables that are references to objects or to arrays. Moreover, arrays may contain references rather than primitives. When an object with reference data is constructed, the references are all initialized to the null value, indicating that they do not yet point to any objects.

For example, suppose you are writing a Java program to control a weather station that has electronic access to a number of remote thermometers. You might model this situation with two classes, WeatherStation and Thermometer.

Writing the code that would enable the Thermometer class to read input from a physical device is far beyond the scope of this book. Let's just assume that somehow the class has a method called connect, which takes care of the connection, and another method called readTemp, which returns a float.

The WeatherStation class would include the following data declarations:

Thermometer[]     therms;

As with any other array, the declaration does not create the array. You would create the array in a method, with a line like the following (assuming there are 20 thermometers):

therms = new Thermometer[20];

Now the array exists, and all its components are null. You now have to construct and connect each Thermometer object, as follows:

for (int i=0; i<therms.length; i++) {   therms[i] = new Thermometer();   therms[i].connect(); }

Now you can write a method to compute the average temperature:

float getAverageTemp() {   float totalTemp = 0;   for (int i=0; i<therms.length; i++)     totalTemp += therms[i].readTemp();   return totalTemp / therms.length; }

Let's refine the connect method to illustrate the use of null. Sometimes hardware fails. Let's assume that connect can detect a failure of the thermometer belonging to the executing object. This failure will be indicated by the method's return value: true will mean connection was successful, and false will mean there was some kind of failure. You can rewrite the array initialization code like this:

for (int i=0; i<therms.length; i++) {   therms[i] = new Thermometer();   if (therms[i].connect() == false)     therms[i] = null; } 

Now you need to refine the getAverageTemp method so that it ignores all broken thermometers:

float getAverageTemp() {   float totalTemp = 0;   int nWorkingThermometers;   for (int i=0; i<therms.length; i++)   {     if (therms[i] != null)     {       totalTemp += therms[i].readTemp();       nWorkingThermometers++;     }   }   return totalTemp / nWorkingThermometers; }

You use the variable nWorkingThermometers to count Thermometer objects that actually contributed to the average.

A reference whose value is null may not be used for accessing an object. (After all, null means that there is no object pointed to by this reference.) For example, the following is illegal:

Thermometer thermo = null; Float temp = thermo.readTemp();

When the second line is executed, the program is terminated abruptly with an error message about a null pointer exception. You have already seen exceptions, but we will not discuss them in detail until Chapter 11, "Exceptions." The name "null pointer" is a throwback. Java's predecessor languages, C and C++, use pointers, which are like references but less secure. It isn't clear why the exception was named "null pointer exception" rather than "null reference exception."




Ground-Up Java
Ground-Up Java
ISBN: 0782141900
EAN: 2147483647
Year: 2005
Pages: 157
Authors: Philip Heller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net