Section 2.6
The Structure of Java Programs
THIS CHAPTER IS CONCERNED with the sort of programming that you do inside a single subroutine. In the rest of the text, we'll be more concerned with the larger scale structure of programs. Before going on to cover that structure in detail, it would be a good idea for you to have a somewhat better overview of how large Java programs are put together and how they work.
A program is made up of classes. To write a program, you have to write at least one class, and that class must include a main() routine. Applets are similar, except that the class you write doesn't need a main() routine.
However, every non-trivial program or applet makes use of other classes as well. In this chapter, you have seen programs that use classes called TextIO and System. Whenever a program or applet makes use of a class, the Java interpreter will go off and try to locate that class. The first place it looks is in the same directory from which the main program or applet class was loaded. If it doesn't find it there, it will search elsewhere. Exactly where it looks depends on the particular version of Java that is running. You can depend on certain standard classes, such as System, being available in any version of Java. If your program uses non-standard classes, such as TextIO, you have to put them in a place where the Java interpreter will find them. Ordinarily, you put them in the same directory with the main class that uses them. However, it might also be possible to place them in the same location as the standard Java classes, or in some other location where your Java interpreter searches for classes.
One common version of Java is the one included with the JDK (Java Development Kit) from Sun Microsystems, the originator of Java. On my computer, the JDK is installed in a directory called jdk1.1.6. This directory has a subdirectory called bin that contains tools such as the java compiler and the java interpreter. Another subdirectory of jdk1.1.6 is called lib. This is the directory where the Java interpreter will look for standard Java classes.
However, if you look in the lib subdirectory of jdk1.1.6, you won't see any .class files! This is because all the .class files for the standard classes have been collected into a single file named "classes.zip". According to the book Java in a Nutshell, by David Flanagan, there are 503 standard classes in Java 1.1. A large program might include several hundred non-standard classes. Rather than force you to have hundreds of .class files lying around, Java allows many .class files to be collected into .zip and .jar files. (The ".jar" refers to "Java ARchive file." The .jar ending has been introduced recently because .zip files have a lot of other uses besides holding class files.) My copy of Netscape 4.0 stores its standard classes in several .jar files, including java40.jar and ifc11.jar. The file ifc11.jar contains a set of non-standard classes created by Netscape that are available to applets running in Netscape. However, applets that depend on those classes would not work in other browsers, which do not provide the same classes.
The search for classes is even a little more complicated than I've indicated here, since classes can be put into "packages." A package is a collection of classes and possibly other packages. The standard classes are divided among several packages, and you can make packages to hold the classes that you write yourself. If a class named MyClass is in a package named myPackage, then the full name of the class is myPackage.MyClass. The Java interpreter will expect to find the file MyClass.class in a directory named myPackage. Presumably, this directory would be in the directory that contains the main class file that uses myPackage.MyClass. It could also be in a .zip or .jar file, since such files have an internal directory structure (although you can't see this internal structure without a special tool). I will cover packages more fully in Section 3.5.
In addition to this large-scale structure, involving multiple classes and packages, there can be a lot more structure inside a single class than you've seen so far. As mentioned earlier in this chapter, a class can include variables and subroutines. We'll see later that a class can also have other classes nested inside it, and it can have things called "initializers" which consist of statements that are executed to initialize the class or objects belonging to the class.
However, the most basic level of structure in a class is a distinction between its static and non-static parts. Every variable, routine, nested class, and initializer in a class can be either static or non-static. The difference is profound and potentially confusing. The only syntactic difference is that static members of a class are marked with the word "static," as in the first line of a main routine:
public static void main(String[] args) {Non-static members can be recognized because they are not marked in this way.
Java is a fully object-oriented language. An object contains data (variables) and provides services (subroutines). In Java, pretty much everything is an object. Even a class is an object. A class is a very special kind of object, and thinking of a class as an object is not always the most useful point of view. However, it is a necessary point of view if you are to understand the meaning of static and non-static.
Most objects are created from classes. An object is created using a class as a template, and the object is then said to belong to that class. A class is not created in this way. A class is created by a Java compiler, and it is simply loaded by the Java interpreter when the class is used in a program.
Now, here is the point: When a class is used as a template to create an object, the non-static members of the class determine the structure of the object. For example, if the class includes a non-static variable declaration "int count;", then the object will include a variable called count. The class itself does not include a variable named count -- just a specification that objects belonging to the class will include such a variable. No variable named count even exists until an object has been created. A static member of a class, on the other hand, is an actual part of the class itself, considered as an object. A static member comes into existence as soon as the class is loaded, and it is accessed as part of the class itself rather than as part of an object belonging to that class.
To use an example you have seen, consider the TextIO class. I defined the subroutines getln(), putln(), and so on to be static members of the class TextIO. When you use TextIO.getln() in a program, the Java interpreter loads the class TextIO, and its static getln() subroutine becomes immediately available. You don't need to creat an object of type TextIO before using the subroutines.
I have also defined another class, Console, that defines exactly the same set of input/output subroutines as TextIO, but defines them as non-static members. Since getln() is a non-static member of Console, you can't call Console.getln(). Console.getln() doesn't exist! Instead, you have to create an object of type Console. (I'll tell you later how to do this.) That object will contain a getln() subroutine. If the name of the object is consoleWin, you could call that subroutine by saying consoleWin.getln().
(An object belonging to the class Console is actually a separate window on the screen, and that window is used for the input/output. I originally wrote the Console class for use on systems that don't support standard input and output. See the source code file ConsoleFrameThreeN1.java for an example and for more information.)
I'll be going over all this stuff -- packages, static vs. non-static, creating and using objects -- in the next few chapters. Once again, I'll ask you not to feel too worried if you are uncomfortable with the details just now.
Having just possibly frightened you with the complexities of Java programming, let me assure you that it's not as bad as it might look. Remember that one of the nice things about object-oriented programming is that you get to build on previous work without having to understand all the internal details of the work you are building on. You do this every time you use a class that was written by someone else.
As an example, let's look at a programming problem that is superficially very different from other examples in this chapter. The problem is to program an animation that shows a set of nested rectangles perpetually moving towards the center of the applet. Here's the applet:
The perpetual motion is, of course, an illusion. If you think about it, you'll see that the applet loops through the same set of images over and over. In each image, there is a gap between the borders of the applet and the outermost rectangle. This gap gets wider and wider until a new rectangle appears at the border. Only it's not a new rectangle. What has really happened is that the applet has started over again with the first image in the sequence.
Now, how can you possibly program an applet like this one, knowing nothing about how to program applets and nothing about how to draw images in applets? But the real question should be, how much do you have to learn before you can write this applet. If you have the right framework to build on, the answer is: not too much more than you already know about programming.
The applet shown above can be based on a animation framework that I've written. (See the source code, SimpleAnimationApplet.java.) To create the animation, you have to write two subroutines, just as you have to write the main() subroutine in a typical application. In each of the two subroutines, you get to use other subroutines that have already been written for you, just as you would use, for example, TextIO.getln(). Of course, you have to learn what subroutines are available and what they do, but you don't have to understand the details of how they work.
Any animation applet based on my framework has the following general form (except that you are free to add other variables and subroutines to the class):
import java.awt.*; public class name-of-class extends SimpleAnimationApplet { public void init() { statements // to initialize the animation } public void drawFrame(Graphics g, int width, int height) { statements // to draw one frame of the animation } }You get to fill in any name you want for the class, and you get to fill in the statements inside the two subroutines. The init() subroutine will be called once before the animation begins, and drawFrame() will be called each time a frame needs to be drawn. All you have to do is say what happens when these subroutines are called. If you look at the documentation in SimpleAnimationApplet.java, you'll find that several useful subroutines have already been defined for you to used in init() and drawFrame(). For example, you can call
setFrameCount(N);where N is an integer, to set the number of frames in the animation to be N. This is something you would ordinarily do in the init() subroutine. (There is another subroutine that could be used in init() to set the speed of the animation.) The function
getFrameNumber()can be called in drawFrame() to find out which frame you have to draw. This function returns a number in the range 0 to N-1, where N is the number of frames set by setFrameCount(). In our example, the value returned by getFrameNumber() will determine how far the outermost rectangle is inset from the edges of the applet.
Of course, you still need to know something about drawing pictures. In Java, drawing is done using subroutines provided by an object of type Graphics. In drawFrame(), you are given an object, g, of type Graphics to use for drawing. In this example, the only subroutines from g you will need are:
- g.setColor(c), where c is a color, often one of a set of standard colors such as Color.black, Color.red, and so on. This subroutine says what color is to be used for drawing.
- g.drawRect(x,y,w,h), where x, y, w, and h are integers. This draws the outline of a rectangle whose top-left corner is x pixels from the left edge of the applet and y pixels down from the top of the applet. The width of the rectangle is w pixels, and the height is h pixels.
- g.fillRect(x,y,w,h), which is similar to drawRect except that it fills in the inside of the rectangle. (You'll need this because you are responsible for filling in the red background of the applet before drawing the black rectangles.)
The Graphics class provides many other drawing subroutines. You could look them up in any Java reference, and you can read about many of them in Section 5.4.
When I wrote the above applet, I called it MovingRects. I decided to make the gap between one nested rectangle and the next 15 pixels. If you think about it, that means that the number of frames in the animation should also be 15. The number of frames can be set up in the init() method with the single statement "setFrameCount(15);". The drawFrame() method starts by filling in the applet with a red background using the two statements
g.setColor(Color.red); g.fillRect(0,0,width,height);(The values of width and height will be provided automatically when drawFrame() is called. They represent the size of the applet.) After that, drawFrame() sets the drawing color to Color.black and uses a while loop to draw the sequence of nested rectangles. The position and size of the first rectangle is calculated using the value of getFrameNumber(). After that, the top-left corner of each rectangle is moved 15 pixels over and 15 pixels down from the previous rectangle, and the width and height of the rectangle decrease by 30 (fifteen pixels on each side). The loop ends when either the width or height of the rectangle becomes less than zero. Here is the complete source code, MovingRects.java:
import java.awt.*; public class MovingRects extends SimpleAnimationApplet { public void init() { // initialize the applet by setting up the animation to have // 15 frames. setFrameCount(15); } protected void drawFrame(Graphics g, int width, int height) { // Draw one frame in the animation by filling in the background // with a solid red and then drawing a set of nested black // rectangles. The frame number tells how much the first // rectangle is to be inset from the borders of the applet. g.setColor(Color.red); // fill the frame with red g.fillRect(0,0,width,height); g.setColor(Color.black); // switch color to black int inset = getFrameNumber(); // Gap between borders of applet and // outermost rect. This goes from 0 to // 14 then back to 0, and so on. int rectWidth = width - 2*inset - 1; // size of the rect int rectHeight = height - 2*inset - 1; while (rectWidth >= 0 && rectHeight >= 0) { g.drawRect(inset,inset,rectWidth,rectHeight); inset = inset + 15; // rects are 15 pixels apart rectWidth = rectWidth - 30; // width decreases by 15 pixels on left and 15 on right rectHeight = rectHeight - 30; // height decreases by 15 pixels on top and 15 on bottom } } // end drawFrame() } // end class MovingRectsThe point here is that by building on an existing framework, you can do interesting things using the type of local, inside-a-subroutine programming that is covered in this chapter. As you learn more about programming and more about Java, you'll be able to do more on your own -- but no matter how much you learn, you'll always be dependent on other people's work to some extent.
By the way, SimpleAnimationApplet.java is not the only applet framework I've written. I used a similar framework to write the applets in the previous sections of this chapter that simulate programs which use standard, console-style input/output. The framework for these console applets is defined in ConsoleApplet.java. It would be no harder for you to write applets based on this framework than it would be to write the programs they simulate.
[ Next Section | Previous Section | Chapter Index | Main Index ]