Chapter 2. A First Application

CONTENTS
  •  2.1 HelloJava
  •  2.2 HelloJava2: The Sequel
  •  2.3 HelloJava3: The Button Strikes!
  •  2.4 HelloJava4: Netscape's Revenge

Before diving into our full discussion of the Java language, let's get our feet wet by jumping into some working code and splashing around a bit. In this chapter, we'll build a friendly little application that illustrates a number of concepts used throughout the book. We'll take this opportunity to introduce general features of the Java language and of Java applications. Look to subsequent chapters for more details.

This chapter also serves as a brief introduction to the object-oriented and multithreaded aspects of Java. If these concepts are new to you, we hope that encountering them here in Java for the first time will be a straightforward and pleasant experience. If you have worked with another object-oriented or multithreaded programming environment, you should especially appreciate Java's simplicity and elegance. This chapter is intended to give you a bird's eye view of the Java language and a feel for how it is used. If you have trouble with any of the concepts introduced here, rest assured they will be covered in greater detail later in the book.

We can't stress enough the importance of experimentation as you learn new concepts here and throughout the book. Don't just read the examples run them. Copy the source code from the accompanying CD-ROM or from our web site at http://www.oreilly.com/catalog/learnjava2. Compile the programs on your machine, and try them. Then, turn our examples into your examples: play with them, change their behavior, break them, fix them, and hopefully have some fun along the way.

2.1 HelloJava

In the tradition of introductory programming texts, we begin with Java's equivalent of the archetypal "Hello World" application, HelloJava.

We'll end up taking four passes at this example before we're done (HelloJava, HelloJava2, etc.), adding features and introducing new concepts along the way. But let's start with the minimalist version:

public class HelloJava {   public static void main( String[] args ) {     System.out.println("Hello, Java!");   } }

This five-line program declares a class called HelloJava and a method called main(). It uses a predefined method called println() to write some text as output. It is a command-line program, which means that it runs in a shell or DOS window and prints its output there. That's a bit old-school for our taste, so before we go any further, we're going to give HelloJava a graphical user interface (GUI). Don't worry about the code too much yet; just follow along with the progression here, and we'll come back for explanations in a moment.

In place of the line containing the println() method, we're going to use something called a JFrame object to put a window on the screen. We can start by replacing the println line with the following three lines:

JFrame frame = new JFrame( "Hello Java!" ); frame.setSize( 300, 300 ); frame.setVisible( true );

This snippet creates a JFrame object with the title "Hello Java!" The JFrame is a graphical window. To display it, we simply configure its size on the screen using the setSize() method and make it visible by calling the setVisible() method.

If we stopped here, we would see an empty window on the screen with our "Hello Java!" banner as its title. But we'd like our message inside the window, not just scrawled on the top of it. To put something in the window, we need a couple more lines. The following, complete example adds a JLabel object to display the text centered in our window. The additional line at the top is necessary to tell Java where to find the JFrame and JLabel classes (the definitions of the JFrame and JLabel objects that we're using).

import javax.swing.*;    public class HelloJava {   public static void main( String[] args ) {     JFrame frame = new JFrame( "Hello Java!" );     JLabel label = new JLabel("Hello Java!", JLabel.CENTER );     frame.getContentPane(  ).add( label );     frame.setSize( 300, 300 );     frame.setVisible( true );   } }

Now that we've got something presentable, let's take a moment to compile and run this example. Place the text in a file called HelloJava.java.

Now compile this source using the Java compiler, javac:

% javac HelloJava.java

This produces the Java bytecode binary class file HelloJava.class.

You can run the application with the Java virtual machine by specifying the class name (not the filename) as an argument:

% java HelloJava

You should see the proclamation shown in Figure 2-1. Congratulate yourself: you have written your first application! Take a moment to bask in the glow of your monitor.

Figure 2-1. The HelloJava application

figs/lj2.0201.gif

Be aware that when you click on the window's close box, the window goes away, but your program is still running. To stop Java and return control to the command line, type Ctrl-C or whatever key sequence stops a running application on your platform. We'll remedy this shortcoming in a later version of the example.

HelloJava may be a small program, but there is quite a bit going on behind the scenes. Those few lines represent the tip of an iceberg. What lies under the surface are the layers of functionality provided by the Java language and its foundation class libraries. Remember that in this chapter, we're going to cover a lot of ground quickly in an effort to show you the big picture. We'll try to offer enough detail for a good understanding of what is happening in each example, deferring full explanations until the appropriate chapters. This holds for both elements of the Java language and the object-oriented concepts that apply to them. Later chapters provide more detailed cataloging of Java's syntax, components, and object-oriented features.

2.1.1 Classes

The previous example defines a class named HelloJava.

public class HelloJava { ...

Classes are the fundamental building blocks of most object-oriented languages. A class is a group of data items, with associated functions that can perform operations on that data. The data items in a class are called variables or sometimes fields; in Java, functions are called methods. The primary benefits of an object-oriented language are this association between data and functionality in class units and also the ability of classes to encapsulate or hide details, freeing the developer from worrying about low-level details.

In an application, a class might represent something concrete, such as a button on a screen or the information in a spreadsheet, or it could be something more abstract, such as a sorting algorithm or perhaps the sense of ennui in a character in a role-playing game. A class representing a spreadsheet might, for example, have variables that represent the values of its individual cells and methods that perform operations on those cells, such as "clear a row" or "compute values."

Our HelloJava class is an entire Java application in a single class. It defines just one method, main(), which holds the body of our program:

public class HelloJava {   public static void main( String[] args ) {   ...

It is this main() method that is called first, when the application is started. The bit labeled String [] args allows us to pass command-line arguments to the application. We'll walk through the main() method in the next section. Finally, we'll note that although this version of HelloJava does not define any variables as part of its class, it does use two variables, frame and label, inside its main() method. We'll have more to say about variables soon as well.

2.1.2 The main() Method

As we saw when we ran our example, running a Java application means picking a particular class and passing it as an argument to the Java virtual machine. When we did this, the java command looked in our HelloJava class to see if it contained the special method named main() of just the right form. It did, and so it was executed. If it had not been there we would have received an error message. The main() method is the entry point for applications. Every standalone Java application includes at least one class with a main() method that performs the necessary actions to start the rest of the program.

Our main() method sets up a window (a JFrame) that will contain the visual output of the HelloJava class. Right now, it's doing all the work in the application. But in an object-oriented application, we normally delegate responsibilities to many different classes. In the next incarnation of our example we're going to perform just such a split creating a second class and we'll see that as the example subsequently evolves, the main() method remains more or less the same, simply holding the startup procedure.

Let's quickly walk through our main() method, just so we know what it does. First, main() creates a JFrame, the window that will hold our example:

JFrame frame = new JFrame("Hello Java!");

The word new in this line of code is very important: JFrame is the name of a class that represents a window on the screen. But the class itself is just a template, like a building plan. The new keyword tells Java to allocate memory and actually create a particular JFrame object. In this case, the argument inside the parentheses tells the JFrame what to display in its title bar. We could have left out the "Hello Java" text and used empty parentheses to create a JFrame with no title.

When frame windows are first created, they are very small. So before we show the JFrame, we set its size to something reasonable:

frame.setSize( 300, 300 );

This is an example of invoking a method on a particular object. In this case the setSize() method is defined by the JFrame class, and it affects the particular JFrame object we've placed in the variable frame. Like the frame, we also create an instance of JLabel to hold our text inside the window:

JLabel label = new JLabel("Hello Java!", JLabel.CENTER );

JLabel is much like a physical label. It holds some text at a particular position, in this case on our frame. This is a very object-oriented concept: using an object to hold some text, instead of simply invoking some method to "draw" the text and moving on. The rationale for this will become clearer later.

Next we have to place the label into the frame we created:

frame.getContentPane(  ).add( label );

Here we're calling a method named getContentPane() and using the result to attach our label. You can think of the JFrame as having several "pages" to it, and this effectively causes our label to be placed on top.

main()'s final task is to show the frame window and its contents, which otherwise would be invisible. An invisible window makes for a pretty boring application.

frame.setVisible( true );

That's the whole main() method. As we progress through the examples in this chapter, it will remain mostly unchanged as the HelloJava class evolves around it.

2.1.3 Garbage Collection

We've told you how to create a new object with the new operator, but we haven't said anything about how to get rid of an object when you are done with it. If you are a C programmer, you may be wondering why not. The reason is that you don't have to do anything to get rid of objects when you are done with them.

The Java runtime system uses a garbage collection mechanism to deal with objects no longer in use. The garbage collector sweeps up objects not referenced by any variables and removes them from memory. Garbage collection is one of the most important features of Java. It frees you from the error-prone task of having to worry about details of memory allocation and deallocation. Now back to elaborating on our HelloJava class.

2.1.4 Classes and Objects

A class is a blueprint for a part of an application; it holds methods and variables that make up that component. Many individual working copies of a given class can exist while an application is active. These individual incarnations are called instances of the class or objects. Two instances of a given class may contain different data, but they always have the same methods.

As an example, consider a Button class. There is only one Button class, but an application can create many different Button objects, each one an instance of the same class. Furthermore, two Button instances might contain different data, perhaps giving each a different appearance and performing a different action. In this sense, a class can be considered a mold for making the object it represents, something like a cookie cutter stamping out working instances of itself in the memory of the computer. As you'll see later, there's a bit more to it than that a class can in fact share information among its instances but this explanation suffices for now. Chapter 5 has the whole story on classes and objects.

The term object is very general and in some other contexts is used almost interchangeably with class. Objects are the abstract entities all object-oriented languages refer to in one form or another. We will use "object" as a generic term for an instance of a class. We might, therefore, refer to an instance of the Button class as a Button, a Button object, or, indiscriminately, as an object.

The main() method in the previous example creates a single instance of the JLabel class and shows it in an instance of the JFrame class. You could modify main() to create many instances of JLabel, perhaps each in a separate window.

2.1.5 Variables and Class Types

In Java, every class defines a new type (data type). A variable can be declared to be of this type and then hold instances of that class. A variable could, for example, be of type Button and hold an instance of the Button class, or of type SpreadSheetCell and hold a SpreadSheetCell object, just as it could be any of the simpler types such as int or float representing numbers. The fact that variables have types and cannot simply hold any kind of object is another important feature of the language that ensures safety and correctness of code.

Ignoring the variables used inside the main() method for the moment, there is only one other variable declared our simple HelloJava example. It's found in the declaration of the main() method itself:

public void main( String [] args ) {

Just like functions in other languages, a method in Java declares a list of variables that it accepts as arguments or parameters, and it specifies the types of those variables. In this case the main method is requiring that when it is invoked, it be passed a list of String objects in the variable named args. The String is the fundamental object representing text in Java. As we hinted earlier, Java uses the args parameter to pass any command-line arguments supplied to the VM into your application. (We don't use them here.)

To this point we have loosely referred to variables as holding objects. In reality, variables that have class types don't so much contain objects as point to them. Class-type variables are references to objects. A reference is a pointer to or a name for an object. If you declare a class-type variable without assigning it an object, it doesn't point to anything. It's assigned the default value of null, meaning "no value." If you try to use a variable with a null value as if it were pointing to a real object, a runtime error, NullPointerException, occurs.

Of course, object references have to come from somewhere. In our example, we created two objects using the new operator. We'll examine object creation in more detail a little later in the chapter.

2.1.6 HelloComponent

Thus far our HelloJava example has contained itself in a single class. In fact, because of its simple nature it has really served as just a single large method. Although we have used a couple of objects to display our GUI message, our own code does not illustrate any object-oriented structure. Well, we're going to correct that right now by adding a second class. To give us something to build on throughout this chapter we're going to take over the job of the JLabel class (bye bye JLabel!) and replace it with our own graphical class: HelloComponent. Our HelloComponent class will start simple, just displaying our "Hello Java!" message at a fixed position. We'll add capabilities later.

The code for our new class is very simple, just a few more lines:

import java.awt.*;    class HelloComponent extends JComponent {   public void paintComponent( Graphics g ) {     g.drawString( "Hello, Java!", 125, 95 );   } }

You can add this text to the HelloJava.java file, or you can place it in its own file called HelloComponent.java. If you put it in the same file, you must move the new import statement to the top of the file, along with the other one. To use our new class in place of the JLabel, simply replace the two lines referencing the label with:

HelloComponent hello = new HelloComponent(  ); frame.getContentPane(  ).add( hello );

This time when you compile HelloJava.java, you will see two binary class files: HelloJava.class and HelloComponent.class. Running the code should look much like the JLabel version, but if you resize the window, you'll notice that our class does not automatically adjust to center the code.

So what have we done, and why have we gone to such lengths to insult the perfectly good JLabel component?! We've created our new HelloComponent class, extending a generic graphical class called JComponent. To extend a class simply means to add functionality to an existing class, creating a new one. We'll get into that in the next section. Here we have created a new kind of JComponent that contains a method called paintComponent(), responsible for drawing our message. Our paintComponent() method takes one argument named (somewhat tersely) g, which is of type Graphics. When the paintComponent() method is invoked, a Graphics object is assigned to g, which we use in the body of the method. We'll say more about paintComponent() and the Graphics class in a moment. As for why, you'll see when we add all sorts of new features to our new component later on.

2.1.7 Inheritance

Java classes are arranged in a parent-child hierarchy in which the parent and child are known as the superclass and subclass, respectively. We'll explore these concepts fully in Chapter 6. In Java, every class has exactly one superclass (a single parent), but possibly many subclasses. The only exception to this rule is the Object class, which sits atop the entire class hierarchy; it has no superclass.

The declaration of our class in the previous example uses the keyword extends to specify that HelloComponent is a subclass of the JComponent class:

public class HelloComponent extends JComponent {

A subclass may inherit some or all the variables and methods of its superclass. Through inheritance, the subclass can use those variables and methods as if it has declared them itself. A subclass can add variables and methods of its own, and it can also override or change the meaning of inherited variables and methods. When we use a subclass, overridden variables and methods are hidden (replaced) by the subclass's own versions of them. In this way, inheritance provides a powerful mechanism whereby a subclass can refine or extend the functionality of its superclass.

For example, the hypothetical spreadsheet class might be subclassed to produce a new scientific spreadsheet class with extra mathematical functions and special built-in constants. In this case, the source code for the scientific spreadsheet might declare methods for the added mathematical functions and variables for the special constants, but the new class automatically has all the variables and methods that constitute the normal functionality of a spreadsheet; they are inherited from the parent spreadsheet class. This also means the scientific spreadsheet maintains its identity as a spreadsheet, and we can use it anywhere the simpler spreadsheet is used. That last sentence has profound implications, which we'll explore throughout the book. It means that specialized objects can be used in place of more generic objects, customizing their behavior without changing the underlying application.

Our HelloComponent class is a subclass of the JComponent class and inherits many variables and methods not explicitly declared in our source code. This is what allows our tiny class to serve as a component in a JFrame, with just a few customizations.

2.1.8 The JComponent Class

The JComponent class provides the framework for building all kinds of user interface components. Particular components, such as buttons, labels, and list boxes, are implemented as subclasses of JComponent.

We override methods in such a subclass to implement the behavior of our particular component. This may sound restrictive, as if we are limited to some predefined set of routines, but that is not the case at all. Keep in mind that the methods we are talking about are ways to interact with the windowing system. We don't have to squeeze our whole application in there. A realistic application might involve hundreds or thousands of classes, with legions of methods and variables and many threads of execution. The vast majority of these are related to the particulars of our job (these are called domain objects). The JComponent class and other predefined classes serve only as a framework on which to base code that handles certain types of user interface events and displays information to the user.

The paintComponent() method is an important method of the JComponent class; we override it to implement the way our particular component displays itself on the screen. The default behavior of paintComponent() doesn't do any drawing at all. If we hadn't overridden it in our subclass, our component would simply have been invisible. Here, we're overriding paintComponent() to do something only slightly more interesting. We don't override any of the other inherited members of JComponent because they provide basic functionality and reasonable defaults for this (trivial) example. As HelloJava grows, we'll delve deeper into the inherited members and use additional methods. We will also add some application-specific methods and variables just for the needs of HelloComponent.

JComponent is really the tip of another iceberg called Swing. Swing is Java's user interface toolkit, represented in our example by the import statement at the top; we'll discuss it in some detail in Chapter 15 through Chapter 18.

2.1.9 Relationships and Finger Pointing

We can correctly refer to HelloComponent as a JComponent because subclassing can be thought of as creating an "is a" relationship, in which the subclass is a kind of its superclass. HelloComponent is therefore a kind of JComponent. When we refer to a kind of object, we mean any instance of that object's class or any of its subclasses. Later, we will look more closely at the Java class hierarchy and see that JComponent is itself a subclass of the Container class, which is further derived from a class called Component, and so on, as shown in Figure 2-2.

Figure 2-2. Part of the Java class hierarchy

figs/lj2.0202.gif

In this sense, a HelloComponent object is a kind of JComponent, which is a kind of Container, and each of these can ultimately be considered to be a kind of Component. It's from these classes that HelloComponent inherits its basic GUI functionality and (as we'll discuss later) the ability to have other graphical components embedded within it as well.

Component is a subclass of the top-level Object class, so all these classes are types of Objects. Every other class in the Java API inherits behavior from Object, which defines a few basic methods, as you'll see in Chapter 7. We'll continue to use the word object (lowercase o) in a generic way to refer to an instance of any class; we'll use Object to refer specifically to that class.

2.1.10 Package and Imports

We mentioned earlier that the first line of our example tells Java where to find some of the classes that we've been using:

import javax.swing.*;

Specifically, it tells the compiler that we are going to be using classes from the Swing GUI toolkit (in this case, JFrame, JLabel, and JComponent). These classes are organized into a package called javax.swing. A Java package is a group of classes that are related by purpose or by application. Classes in the same package have special access privileges with respect to one another and may be designed to work together closely.

Packages are named in a hierarchical fashion with dot-separated components, such as java.util and java.util.zip. Classes in a package must follow conventions about where they are located in the classpath. They also take on the name of the package as part of their "full name" or, to use the proper terminology, their fully qualified name. For example, the fully qualified name of the JComponent class is javax.swing.JComponent. We could have referred to it by that name directly, in lieu of using the import statement:

public class HelloComponent extends javax.swing.JComponent {...}

The statement import javax.swing.* enables us to refer to all the classes in the javax.swing package by their simple names. So we don't have to use fully qualified names to refer to the JComponent, JLabel, and JFrame classes.

As we saw when we added our second example class, there may be one or more import statements in a given Java source file. The imports effectively create a "search path" that tells Java where to look for classes that we refer to by their simple, unqualified names. The imports we've seen use the dot star (.*) notation to indicate that the entire package should be imported. But you can also specify just a single class. For example, our current example uses only the Graphics class from the java.awt package. So we could have used import java.awt.Graphics instead of using the wildcard * to import all the AWT package's classes. However, we are anticipating using several more classes from this package later.

The java. and javax. package hierarchies are special. Any package that begins with java. is part of the core Java API and is available on any platform that supports Java. The javax. package normally denotes a standard extension to the core platform, which may or may not be installed. However in recent years many standard extensions have been added to the core Java API without renaming them. The javax.swing package is an example; it is part of the core API in spite of its name. Figure 2-3 illustrates some of the core Java packages, showing a representative class or two from each.

Figure 2-3. Some core Java packages

figs/lj2.0203.gif

java.lang contains fundamental classes needed by the Java language itself; this package is imported automatically and that is why we didn't need an import statement to use class names such as String or System in our examples. The java.awt package contains classes of the older, graphical Abstract Window Toolkit; java.net contains the networking classes.

2.1.11 The paintComponent( ) Method

The source for our HelloComponent class defines a method, paintComponent(), that overrides the paintComponent() method of the JComponent class:

public void paintComponent( Graphics g ) {     g.drawString( "Hello, Java!", 125, 95 ); }

The paintComponent() method is called when it's time for our example to draw itself on the screen. It takes a single argument, a Graphics object, and doesn't return any type of value (void) to its caller.

Modifiers are keywords placed before classes, variables, and methods to alter their accessibility, behavior, or semantics. paintComponent() is declared as public, which means it can be invoked (called) by methods in classes other than HelloComponent. In this case, it's the Java windowing environment that is calling our paintComponent() method. A method or variable declared as private is only inaccessible from inside its own class.

The Graphics object, an instance of the Graphics class, represents a particular graphical drawing area. (It is also called a graphics context.) It contains methods that can be used to draw in this area, and variables that represent characteristics such as clipping or drawing modes. The particular Graphics object we are passed in the paintComponent() method corresponds to our HelloComponent's area of the screen, inside our frame.

The Graphics class provides methods for rendering shapes, images, and text. In HelloComponent, we invoke the drawString() method of our Graphics object to scrawl our message at the specified coordinates. (For a description of the methods available in the Graphics class, see Chapter 19.)

As we've seen earlier, a method of an object is accessed by appending a dot (.) and its name to the object that holds it. We invoked the drawString() method of the Graphics object (referenced by our g variable) in this way:

g.drawString( "Hello, Java!", 125, 95 );

It may be difficult to get used to the idea that our application is drawn by a method that is called by an outside agent at arbitrary times. How can we do anything useful with this? How do we control what gets done and when? These answers will be forthcoming. For now, just think about how you would begin to structure applications that respond on command instead of by their own initiative.

2.2 HelloJava2: The Sequel

Now that we've got some basics down, let's make our application a little more interactive. The following minor upgrade, HelloJava2, allows us to drag the message text around with the mouse.

We'll call this example HelloJava2 rather than cause confusion by continuing to expand the old one. But the primary changes here and further on will be in adding capabilities to the HelloComponent class and simply making the corresponding changes to the names to keep them straight, e.g., HelloComponent2, HelloComponent3, etc. Having just seen inheritance at work, you might wonder why we aren't creating a subclass of HelloComponent and exploiting inheritance to build upon our previous example and extend its functionality. Well, in this case, that would not provide much advantage, and for clarity we will simply start over.

Here is HelloJava2:

//file: HelloJava2.java import java.awt.*; import java.awt.event.*; import javax.swing.*;    public class HelloJava2 {   public static void main( String[] args ) {     JFrame frame = new JFrame( "HelloJava2" );     frame.getContentPane(  ).add( new HelloComponent2("Hello Java!") );     frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );     frame.setSize( 300, 300 );     frame.setVisible( true );   } }    class HelloComponent2 extends JComponent      implements MouseMotionListener  {   String theMessage;   int messageX = 125, messageY = 95; // Coordinates of the message      public HelloComponent2( String message ) {     theMessage = message;     addMouseMotionListener(this);   }      public void paintComponent( Graphics g ) {     g.drawString( theMessage, messageX, messageY );   }      public void mouseDragged(MouseEvent e) {     // Save the mouse coordinates and paint the message.     messageX = e.getX(  );     messageY = e.getY(  );     repaint(  );   }      public void mouseMoved(MouseEvent e) { } }

Two slashes in a row indicates that the rest of the line is a comment. We've added a few comments to HelloJava2 to help you keep track of everything.

Place the text of this example in a file called HelloJava2.java and compile it as before. You should get new class files, HelloJava2.class and HelloComponent2.class as a result.

Run the example as before:

% java HelloJava2

Feel free to substitute your own salacious comment for the "Hello, Java!" message and enjoy many hours of fun, dragging the text around with your mouse. Notice that now when you click the window's close button, the application exits; we'll explain that later when we talk about events.

2.2.1 Instance Variables

We have added some variables to the HelloComponent2 class in our example:

int messageX = 125, messageY = 95; String theMessage;

messageX and messageY are integers that hold the current coordinates of our movable message. We have crudely initialized them to default values that should place the message somewhere near the center of the window. Java integers are 32-bit signed numbers, so they can easily hold our coordinate values. The variable theMessage is of type String and can hold instances of the String class.

You should note that these three variables are declared inside the braces of the class definition, but not inside any particular method in that class. These variables are called instance variables, and they belong to the class as a whole. Specifically, copies of them appear in each separate instance of the class. Instance variables are always visible to (and usable by) all the methods inside their class. Depending on their modifiers, they may also be accessible from outside the class.

Unless otherwise initialized, instance variables are set to a default value of zero, false, or null, depending on their type. Numeric types are set to zero, boolean variables are set to false, and class type variables always have their value set to null, which means "no value." Attempting to use an object with a null value results in a runtime error.

Instance variables differ from method arguments and other variables that are declared inside the scope of a particular method. The latter are called local variables . They are effectively private variables that can be seen only by code inside the method. Java doesn't initialize local variables, so you must assign values yourself. If you try to use a local variable that has not yet been assigned a value, your code generates a compile-time error. Local variables live only as long as the method is executing and then disappear, unless something else saves their value. Each time the method is invoked, its local variables are recreated and must be assigned values.

We have used the new variables to make our previously stodgy paintComponent() method more dynamic. Now all the arguments in the call to drawString() are determined by these variables.

2.2.2 Constructors

The HelloJava2 class includes a special kind of a method called a constructor. A constructor is called to set up a new instance of a class. When a new object is created, Java allocates storage for it, sets instance variables to their default values, and calls the constructor method for the class to do whatever application-level setup is required.

A constructor always has the same name as its class. For example, the constructor for the HelloJava2 class is called HelloJava2(). Constructors don't have a return type, but you can think of them as creating an object of their class's type. Like other methods, constructors can take arguments. Their sole mission in life is to configure and initialize newly born class instances, possibly using information passed to them in these parameters.

An object is created with the new operator specifying the constructor for the class and any necessary arguments. The resulting object instance is returned as a value. In our example, a new HelloComponent2 instance is created in the main() method by this line:

frame.getContentPane( ).add( new HelloComponent2("Hello, Java!") );

This line actually does three things. We could write them as three separate lines which are a little easier to understand:

HelloJava2 newobj = new HelloComponent2("Hello, Java!"); Container content = frame.getContentPane(  ); content.add( newobj );

The first line is the important one, where a new HelloComponent2 object is created. The HelloComponent2 constructor takes a String as an argument and, as we have arranged it, uses it to set the message that is displayed in the window. With a little magic from the Java compiler, quoted text in Java source code is turned into a String object. (See Chapter 9 for a complete discussion of the String class.) The second and third lines simply take our new component and add it to the frame to make it visible, as we did in the previous examples.

While we're on the topic, if you'd like to make our message configurable, you can change the constructor line to the following:

HelloJava2 newobj = new HelloComponent2( args[0] );

Now you can pass the text on the command line when you run the application:

% java HelloJava2 "Hello Java!"

args[0] refers to the first command-line parameter. Its meaning will be clearer when we discuss arrays later in the book.

HelloJava2's constructor then does two things: it sets the text of theMessage instance variable and calls addMouseMotionListener(). This method is part of the event mechanism, which we discuss next. It tells the system, "Hey, I'm interested in anything that happens involving the mouse."

public HelloJava2(String message) {   theMessage = message;   addMouseMotionListener( this ); }

The special, read-only variable called this is used to explicitly refer to our object in the call to addMouseMotionListener(). A method can use this to refer to the instance of the object that holds it. The following two statements are therefore equivalent ways of assigning the value to theMessage instance variable:

theMessage = message;

or:

this.theMessage = message;

We'll normally use the shorter, implicit form to refer to instance variables. But we'll need this when we have to explicitly pass a reference to our object to a method in another class. We often do this so that methods in other classes can invoke our public methods (a callback, explained later in this chapter) or use our public variables.

2.2.3 Events

The last two methods of HelloJava2, mouseDragged() and mouseMoved(), let us get information from the mouse. Each time the user performs an action, such as pressing a key on the keyboard, moving the mouse, or perhaps banging his or her head against a touch screen, Java generates an event. An event represents an action that has occurred; it contains information about the action, such as its time and location. Most events are associated with a particular GUI component in an application. A keystroke, for instance, can correspond to a character being typed into a particular text entry field. Pressing a mouse button can activate a particular button on the screen. Even just moving the mouse within a certain area of the screen can trigger effects such as highlighting or changing the cursor's shape.

To work with these events we've imported a new package, java.awt.event, which provides specific Event objects that we use to get information from the user. (Notice that importing java.awt.* doesn't automatically import the event package. Packages don't really contain other packages, even if the hierarchical naming scheme would imply that they do.)

There are many different event classes, including MouseEvent, KeyEvent, and ActionEvent. For the most part, the meaning of these events is fairly intuitive. A MouseEvent occurs when the user does something with the mouse, a KeyEvent occurs when the user presses a key, and so on. ActionEvent is a little special; we'll see it at work later in this chapter in our third version of HelloJava. For now, we'll focus on dealing with MouseEvents.

GUI components in Java generate events for specific kinds of user actions. For example, if you click the mouse inside a component, the component generates a mouse event. Objects can ask to receive the events from one or more components by registering a listener with the event source. For example, to declare that a listener wants to receive a component's mouse-motion events, you invoke that component's addMouseMotionListener() method, specifying the listener object as an argument. That's what our example is doing in its constructor. In this case, the component is calling its own addMouseMotionListener() method, with the argument this, meaning "I want to receive my own mouse-motion events."

That's how we register to receive events. But how do we actually get them? That's what the two "mouse" related methods in our class are for. The mouseDragged() method is called automatically on a listener to receive the events generated when the user drags the mouse that is, moves the mouse with any button pressed. The mouseMoved() method is called whenever the user moves the mouse over the area without pressing a button. In this case, we've placed these methods in our HelloComponent2 class and had it register itself as the listener. This is entirely appropriate for our new "text dragging" component. But more generally good design usually dictates that event listeners be implemented as adapter classes that provide better separation of GUI and "business logic." We'll discuss that in detail later in the book.

So, our mouseMoved() method is boring: it doesn't do anything. We ignore simple mouse motions and reserve our attention for dragging. mouseDragged() has a bit more meat to it. This method is called repeatedly by the windowing system to give us updates on the position of the mouse. Here it is:

public void mouseDragged( MouseEvent e ) {   messageX = e.getX( );   messageY = e.getY( );   repaint( ); }

The first argument to mouseDragged() is a MouseEvent object, e, that contains all the information we need to know about this event. We ask the MouseEvent to tell us the x and y coordinates of the mouse's current position by calling its getX() and getY() methods. We save these in the messageX and messageY instance variables for use elsewhere.

The beauty of the event model is that you have to handle only the kinds of events you want. If you don't care about keyboard events, you just don't register a listener for them; the user can type all she wants, and you won't be bothered. If there are no listeners for a particular kind of event, Java won't even generate it. The result is that event handling is quite efficient.[1]

While we're discussing events we should mention another small addition we slipped into HelloJava2:

frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

This line tells the frame to exit the application when its close button is pressed. It's called the "default" close operation because this operation, like almost every other GUI interaction, is governed by events. We could register a window listener to get notification of when the user pushes the close button and take whatever action we like. But this convenience method handles the common cases.

Finally, we've danced around a couple of questions here: how does the system know that our class contains the necessary mouseDragged() and mouseMoved( ) methods (where do these names come from)? And why do we have to supply a mouseMoved() method that doesn't do anything? The answer to these questions has to do with interfaces. We'll discuss interfaces after clearing up some unfinished business with repaint().

2.2.4 The repaint( ) Method

Since we changed the coordinates for the message (when we dragged the mouse), we would like HelloJava2 to redraw itself. We do this by calling repaint(), which asks the system to redraw the screen at a later time. We can't call paintComponent() directly, even if we wanted to, because we don't have a graphics context to pass to it.

We can use the repaint() method of the JComponent class to request that our component be redrawn. repaint() causes the Java windowing system to schedule a call to our paintComponent() method at the next possible time; Java supplies the necessary Graphics object, as shown in Figure 2-4.

Figure 2-4. Invoking the repaint( ) method

figs/lj2.0204.gif

This mode of operation isn't just an inconvenience brought about by not having the right graphics context handy. The foremost advantage to this mode of operation is that the repainting behavior is handled by someone else while we are free to go about our business. The Java system has a separate, dedicated thread of execution that handles all repaint() requests. It can schedule and consolidate repaint() requests as necessary, which helps to prevent the windowing system from being overwhelmed during such painting-intensive situations as scrolling. Another advantage is that all the painting functionality must be encapsulated in our paintComponent() method; we aren't tempted to spread it throughout the application.

2.2.5 Interfaces

Now it's time to face the question we avoided earlier: how does the system know to call mouseDragged() when a mouse event occurs? Is it simply a matter of knowing that mouseDragged() is some magic name that our event handling method must have? Not quite; the answer to the question touches on the discussion of interfaces, which are one of the most important features of the Java language.

The first sign of an interface comes on the line of code that introduces the HelloComponent2 class: we say that the class implements the MouseMotionListener interface.

class HelloComponent2 extends JComponent      implements MouseMotionListener  {

Essentially, an interface is a list of methods that the class must have; this particular interface requires our class to have methods called mouseDragged() and mouseMoved(). The interface doesn't say what these methods have to do; indeed, mouseMoved() doesn't do anything. But it does say that the methods must take a MouseEvent as an argument and return void (i.e., no return value).

An interface is a contract between you, the code developer, and the compiler. By saying that your class implements the MouseMotionListener interface, you're saying that these methods will be available for other parts of the system to call. If you don't provide them, a compilation error will occur.

But that's not the only way interfaces impact this program. An interface also acts like a class. For example, a method could return a MouseMotionListener or take a MouseMotionListener as an argument. When you refer to an object by an interface name in this way it means that you don't care about the object's actual class; the only requirement is that the class implements that interface. addMouseMotionListener() is such a method: its argument must be an object that implements the MouseMotionListener interface. The argument we pass is this, the HelloComponent2 object itself. The fact that it's an instance of JComponent is irrelevant; it could be a Cookie, an Aardvark, or any other class we dream up. What's important is that it implements MouseMotionListener, and thus declares that it will have the two named methods. That's why we need a mouseMoved() method, even though the one we supplied doesn't do anything: the MouseMotionListener interface says we have to have one.

The Java distribution comes with many interfaces that define what classes have to do. This idea of a contract between the compiler and a class is very important. There are many situations like the one we just saw where you don't care what class something is, you just care that it has some capability, such as listening for mouse events. Interfaces give us a way of acting on objects based on their capabilities without knowing or caring about their actual type. They are a tremendously important concept in how we use Java as an object-oriented language, and we'll talk about them in detail in Chapter 4.

We'll also see shortly that interfaces provide a sort of escape clause to the Java rule that any new class can extend only a single class ("single inheritance"). A class in Java can extend only one class but can implement as many interfaces as it wants; our next example implements two interfaces, and the final example in this chapter implements three. In many ways, interfaces are almost like classes, but not quite. They can be used as data types, can extend other interfaces (but not classes) and can be inherited by classes (if class A implements interface B, subclasses of A also implement B). The crucial difference is that classes don't actually inherit methods from interfaces; the interfaces merely specify the methods the class must have.

2.3 HelloJava3: The Button Strikes!

Now we can move on to some fun stuff. HelloJava3 brings us a new graphical interface component: the JButton .[2] In this example we will add a JButton component to our application that changes the color of our text each time the button is pressed. The draggable-message capability is still there, too. Our new code looks like this:

//file: HelloJava3.java import java.awt.*; import java.awt.event.*; import javax.swing.*;    public class HelloJava3 {   public static void main( String[] args ) {     JFrame frame = new JFrame( "HelloJava3" );     frame.getContentPane(  ).add( new HelloComponent3("Hello Java!") );     frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );     frame.setSize( 300, 300 );     frame.setVisible( true );   } }    class HelloComponent3 extends JComponent      implements MouseMotionListener, ActionListener {   String theMessage;   int messageX = 125, messageY = 95;  // Coordinates of the message      JButton theButton;      int colorIndex;  // Current index into someColors   static Color[] someColors = {      Color.black, Color.red, Color.green, Color.blue, Color.magenta };      public HelloComponent3( String message ) {     theMessage = message;     theButton = new JButton("Change Color");     setLayout( new FlowLayout(  ) );     add( theButton );     theButton.addActionListener( this );     addMouseMotionListener( this );   }      public void paintComponent( Graphics g ) {     g.drawString(theMessage, messageX, messageY);   }      public void mouseDragged( MouseEvent e ) {     messageX = e.getX(  );     messageY = e.getY(  );     repaint(  );   }      public void mouseMoved( MouseEvent e ) {}      public void actionPerformed( ActionEvent e ) {     // Did somebody push our button?     if (e.getSource(  ) == theButton)       changeColor(  );   }      synchronized private void changeColor(  ) {     // Change the index to the next color, awkwardly.     if (++colorIndex == someColors.length)       colorIndex = 0;     setForeground( currentColor(  ) ); // Use the new color.     repaint(  );   }      synchronized private Color currentColor(  ) {     return someColors[colorIndex];   } }

Compile HelloJava3 in the same way as the other applications. Run the example, and you should see the display shown in Figure 2-5. Drag the text. Each time you press the button the color should change. Call your friends! Test yourself for color-blindness!

Figure 2-5. The HelloJava3 application

figs/lj2.0205.gif

So what have we added this time? Well, for starters we have a new variable:

JButton theButton;

The theButton variable is of type JButton and is going to hold an instance of the javax.swing.JButton class. The JButton class, as you might expect, represents a graphical button, like other buttons in your windowing system.

Three additional lines in the constructor create the button and display it:

theButton = new JButton("Change Color"); setLayout( new FlowLayout( ) ); add( theButton );

In the first line, the new keyword creates an instance of the JButton class. The next line affects the way our component will be used as a container to hold the button. It tells HelloComponent3 how it should arrange components that are added to it for display in this case to use a scheme called a FlowLayout (more on that coming up). Finally, it adds the button to our component, just like we added the HelloComponent3 to the content pane of the JFrame in the main() method.

2.3.1 Method Overloading

JButton has more than one constructor. A class can have multiple constructors, each taking different parameters and presumably using them to do different kinds of setup. When there are multiple constructors for a class, Java chooses the correct one based on the types of arguments used with them. We call the JButton constructor with a String argument, so Java locates the constructor method of the JButton class that takes a single String argument and uses it to set up the object. This is called method overloading. All methods in Java, not just constructors, can be overloaded; this is one aspect of the object-oriented programming principle of polymorphism.

Overloaded constructors generally provide a convenient way to initialize a new object. The JButton constructor we've used sets the text of the button as it is created:

theButton = new JButton("Change Color");

This is shorthand for creating the button and setting its label, like this:

theButton = new JButton( ); theButton.setText("Change Color");

2.3.2 Components

We have used the terms component and container somewhat loosely to describe graphical elements of Java applications. But these terms are the names of actual classes in the java.awt package.

Component is a base class from which all Java's GUI components are derived. It contains variables that represent the location, shape, general appearance, and status of the object, as well as methods for basic painting and event handling. javax.swing.JComponent extends the base Component class and refines it for the Swing toolkit. The paintComponent() method we have been using in our example is inherited from the JComponent class. HelloComponent is a kind of JComponent and inherits all its public members, just as other GUI components do.

The JButton class is also derived from JComponent and therefore shares this functionality. This means that the developer of the JButton class had methods such as paintComponent() available with which to implement the behavior of the JButton object, just as we did when creating our example. What's exciting is that we are perfectly free to further subclass components such as JButton and override their behavior to create our own special types of user-interface components. JButton and HelloJava3 are, in this respect, equivalent types of things.

2.3.3 Containers

The Container class is an extended type of Component that maintains a list of child components and helps to group them. The Container causes its children to be displayed and arranges them on the screen according to a particular layout strategy.

Because a Container is also a Component, it can be placed alongside other Component objects in other Containers, in a hierarchical fashion, as shown in Figure 2-6. Our HelloComponent3 class is a kind of Container (by virtue of the JComponent class) and can therefore hold and manage other Java components and containers such as buttons, sliders, text fields, and panels.

Figure 2-6. Layout of Java containers and components

figs/lj2.0206.gif

In Figure 2-6, the italicized items are Components, and the bold items are Containers. The keypad is implemented as a container object that manages a number of keys. The keypad itself is contained in the GizmoTool container object.

Since JComponent descends from Container, it can be both a component and a container. In fact, we've already used it in this capacity in the HelloComponent3 example. It does its own drawing and handles events, just like a component. But it also contains a button, just like a container.

2.3.4 Layout

Having created a JButton object, we need to place it in the container (HelloComponent3), but where? An object called a LayoutManager determines the location within the HelloJava3 container at which to display the JButton. A LayoutManager object embodies a particular scheme for arranging components on the screen and adjusting their sizes. You'll learn more about layout managers in Chapter 18. There are several standard layout managers to choose from, and we can, of course, create new ones. In our case, we specify one of the standard managers, a FlowLayout. The net result is that the button is centered at the top of the HelloJava3 container.

To add the button to the layout, we invoke the add() method that HelloJava3 inherits from Container, passing the JButton object as a parameter:

add( theButton );

add() is a method inherited by our class from the Container class. It appends our JButton to the list of components the HelloJava3 container manages. Thereafter, HelloJava3 is responsible for the JButton: it causes the button to be displayed and it determines where in its window the button should be placed. Unlike the more complex JFrame, a regular JComponent doesn't require the getContentPane() method; it has only a single container, and we use the simple add() method.

2.3.5 Subclassing and Subtypes

If you look up the add() method of the Container class, you'll see that it takes a Component object as an argument. But in our example we've given it a JButton object. What's going on?

As we've said, JButton is a subclass of the Component class. Because a subclass is a kind of its superclass and has, at minimum, the same public methods and variables, Java allows us to use an instance of a subclass anywhere we could use an instance of its superclass. This is a very important concept, and it's another aspect of the object-oriented principle of polymorphism. JButton is a kind of Component, so any method that expects a Component as an argument will accept a JButton.

2.3.6 More Events and Interfaces

Now that we have a JButton, we need some way to communicate with it, that is, to get the events it generates. We could just listen for mouse clicks within the button and act accordingly. But that would require customization, via subclassing of the JButton, and we would be giving up the advantages of using a pre-fab component. Instead, we have the HelloComponent3 object listen for higher level events, corresponding to button presses. A JButton generates a special kind of event called an ActionEvent when someone clicks on it with the mouse. To receive these events, we have added another method to the HelloComponent3 class:

public void actionPerformed( ActionEvent e ) {   if ( e.getSource( ) == theButton )     changeColor( ); }

If you followed the previous example, you shouldn't be surprised to see that HelloComponent3 now declares that it implements the ActionListener interface in addition to MouseMotionListener. ActionListener requires us to implement an actionPerformed() method that is called whenever an ActionEvent occurs. You also shouldn't be surprised to see that we added a line to the HelloJava3 constructor, registering itself (this) as a listener for the button's action events:

theButton.addActionListener( this );

Note that this time we're registering our component as a listener with a different object the button whereas previously we were asking for our own events.

The actionPerformed() method takes care of any action events that arise. First, it checks to make sure that the event's source (the component generating the event) is what we think it should be: theButton. This may seem superfluous; after all there is only one button, what else could possibly generate an action event? In this application, nothing. But it's a good idea to check because another application may have many buttons, and you may need to figure out which one has been clicked. Or you may add a second button to this application later, and you don't want it to break something. To check this, we call the getSource() method of the ActionEvent object, e. We then use the == operator to make sure the event source matches theButton.

In Java, == is a test for identity, not equality; it is true if the event source and theButton are the same object. The distinction between equality and identity is important. We would consider two String objects to be equal if they have the same characters in the same sequence. However, they might not be the same object. In Chapter 7 we'll look at the equals( ) method, which tests for equality.

Once we establish that the event e comes from the right button, we call our changeColor() method, and we're finished.

You may be wondering why we don't have to change mouseDragged() now that we have a JButton in our application. The rationale is that the coordinates of the event are all that matter for this method. We are not particularly concerned if the event happens to fall within an area of the screen occupied by another component. This means you can drag the text right through the JButton: try it and see! In this case, the arrangement of containers means that the button is on top of our component, so the text is dragged beneath it.

2.3.7 Color Commentary

To support HelloJava3's colorful side, we have added a couple of new variables and two helpful methods. We create and initialize an array of Color objects representing the colors through which we cycle when the button is pressed. We also declare an integer variable that serves as an index into this array, specifying the position of the current color:

int colorIndex; static Color[] someColors = { Color.black, Color.red,     Color.green, Color.blue, Color.magenta };

A number of things are going on here. First let's look at the Color objects we are putting into the array. Instances of the java.awt.Color class represent colors; they are used by all classes in the java.awt package that deal with basic color graphics. Notice that we are referencing variables such as Color.black and Color.red. These look like examples of an object's instance variables, but Color is not an object, it's a class. What is the meaning of this? We'll discuss that next.

2.3.8 Static Members

A class can contain variables and methods that are shared among all instances of the class. These shared members are called static variables and static methods. The most common use of static variables in a class is to hold predefined constants or unchanging objects that all the instances can use.

There are two advantages to this approach. The more obvious advantage is that static members take up space only in the class; the members are not replicated in each instance. But more importantly, static members can be accessed even if no instances of the class exist. In this example, we use the static variable Color.red without having to create an instance of the Color class.

An instance of the Color class represents a visible color. For convenience, the Color class contains some static, predefined objects with friendly names such as green, red, and (the happy color) magenta. The variable green, for example, is a static member in the Color class. The data type of the variable green is Color. Internally, in Java-land it is initialized like this:

public final static Color green = new Color(0, 255, 0);

The green variable and the other static members of Color cannot be modified (after they've been initialized), so they are effectively constants and can be optimized as such by the Java VM. The alternative to using these predefined colors is to create a color manually by specifying its red, green, and blue (RGB) components using a Color class constructor.

2.3.9 Arrays

Next, we turn our attention to the array. We have declared a variable called someColors, which is an array of Color objects. In Java, arrays are first-class objects. This means that an array is, itself, a type of object one that knows how to hold an indexed list of some other type of object. An array is indexed by integers; when you index an array, the resulting value is an object reference that is, a reference to the object that is located in the array's specified slot. Our code uses the colorIndex variable to index someColors. It's also possible to have an array of simple primitive types, such as floats, rather than objects.

When we declare an array, we can initialize it using the familiar C-like curly brace construct. Specifying a comma-separated list of elements inside curly braces is a convenience that instructs the compiler to create an instance of the array with those elements and assign it to our variable. Alternatively, we could have just declared our someColors variable and, later, allocated an array object for it and assigned individual elements to that array's slots. See Chapter 5 for a complete discussion of arrays.

2.3.10 Our Color Methods

Now we have an array of Color objects and a variable with which to index the array. Two private methods do the actual work for us. The private modifier on these methods specifies that they can be called only by other methods in the same instance of the class. They cannot be accessed outside the object that contains them. We declare members to be private to hide the detailed inner workings of a class from the outside world. This is called encapsulation and is another tenet of object-oriented design, as well as good programming practice. Private methods are created as helper functions for use solely in the class implementation.

The first method, currentColor(), is simply a convenience routine that returns the Color object representing the current text color. It returns the Color object in the someColors array at the index specified by our colorIndex variable:

synchronized private Color currentColor( ) {   return someColors[colorIndex]; }

We could just as readily have used the expression someColors[colorIndex] everywhere we use currentColor(); however, creating methods to wrap common tasks is another way of shielding ourselves from the details of our class. In an alternative implementation, we might have shuffled off details of all color-related code into a separate class. We could have created a class that takes an array of colors in its constructor and then provided two methods: one to ask for the current color and one to cycle to the next color (just some food for thought).

The second method, changeColor(), is responsible for incrementing the colorIndex variable to point to the next Color in the array. changeColor() is called from our actionPerformed() method whenever the button is pressed:

synchronized private void changeColor(  ) {     // Change the index to the next color, awkwardly.     if ( ++colorIndex == someColors.length )       colorIndex = 0;     setForeground( currentColor(  ) ); // Use the new color.     repaint(  ); }

Here we increment colorIndex and compare it to the length of the someColors array. All array objects have a variable called length that specifies the number of elements in the array. If we have reached the end of the array, we wrap around to the beginning by resetting the index to zero. We've flagged this with a comment to indicate that we're doing something fishy here. But we'll come back to that in a moment. After changing the currently selected color, we do two things. First, we call the component's setForeground() method, which changes the color used to draw text in our component. Then we call repaint() to cause the component to be redrawn with the new color for the draggable message.

What is the synchronized keyword that appears in front of our currentColor() and changeColor() methods? Synchronization has to do with threads, which we'll examine in the next section. For now, all you need know is that the synchronized keyword indicates these two methods can never be running at the same time. They must always run one after the other.

The reason for this is related to the fishy way we increment out index. Notice that in changeColor(), we increment colorIndex before testing its value. Strictly speaking, this means that for some brief period of time while Java is running through our code, colorIndex can have a value that is past the end of our array. If our currentColor() method happened to run at that same moment, we would see a runtime "array out of bounds" error. Now, it would be easy for us to fix the problem in this case with some simple arithmetic before changing the value, but this simple example is representative of more general synchronization issues we need to address. We'll use it to illustrate the use of the synchronized keyword. In the next section, you'll see that Java makes dealing with these problems relatively easy through language-level synchronization support.

2.4 HelloJava4: Netscape's Revenge

We have explored quite a few features of Java with the first three versions of the HelloJava application. But until now, our application has been rather passive; it has been completely event-driven, waiting patiently for events to come its way and responding to the whims of the user. Now our application is going to take some initiative HelloJava4 will blink! Here is the code for our latest version:

//file: HelloJava4.java import java.awt.*; import java.awt.event.*; import javax.swing.*;    public class HelloJava4 {   public static void main( String[] args ) {     JFrame frame = new JFrame( "HelloJava4" );     frame.getContentPane(  ).add( new HelloComponent4("Hello Java!") );     frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );     frame.setSize( 300, 300 );     frame.setVisible( true );   } }    class HelloComponent4 extends JComponent     implements MouseMotionListener, ActionListener, Runnable  {   String theMessage;   int messageX = 125, messageY = 95; // Coordinates of the message      JButton theButton;      int colorIndex; // Current index into someColors.   static Color[] someColors = {      Color.black, Color.red, Color.green, Color.blue, Color.magenta };      boolean blinkState;      public HelloComponent4( String message ) {     theMessage = message;     theButton = new JButton("Change Color");     setLayout( new FlowLayout(  ) );     add( theButton );     theButton.addActionListener( this );     addMouseMotionListener( this );     Thread t = new Thread( this );     t.start(  );   }      public void paintComponent( Graphics g ) {     g.setColor(blinkState ? getBackground(  ) : currentColor(  ));     g.drawString(theMessage, messageX, messageY);   }      public void mouseDragged(MouseEvent e) {     messageX = e.getX(  );     messageY = e.getY(  );     repaint(  );   }      public void mouseMoved(MouseEvent e) { }      public void actionPerformed( ActionEvent e ) {     if ( e.getSource(  ) == theButton )       changeColor(  );   }      synchronized private void changeColor(  ) {     if (++colorIndex == someColors.length)       colorIndex = 0;     setForeground( currentColor(  ) );     repaint(  );   }      synchronized private Color currentColor(  ) {     return someColors[colorIndex];   }      public void run( ) {     try {       while(true) {         blinkState = !blinkState; // Toggle blinkState.         repaint(  ); // Show the change.         Thread.sleep(300);       }     } catch (InterruptedException ie) { }   } }

Compile and run this version of HelloJava just like the others. You'll see that the text does in fact blink. Our apologies if you find this annoying we're not overly fond of it either.

2.4.1 Threads

All the changes we've made in HelloJava4 have to do with setting up a separate thread of execution to make the text blink. Java is a multithreaded language, which means there can be many paths of execution, effectively running at the same time. A thread is a separate flow of control within a program. Conceptually, threads are similar to processes, except that unlike processes, multiple threads share the same program space, which means that they can share variables and methods (but also have their own local variables). Threads are also quite lightweight in comparison to processes, so it's conceivable for a single application to be running many (perhaps hundreds or thousands) of threads concurrently.

Multithreading provides a way for an application to handle many different tasks at the same time. It's easy to imagine multiple things going on at the same time in an application like a web browser. The user could be listening to an audio clip while scrolling an image; at the same time, the browser can be downloading an image. Multithreading is especially useful in GUI-based applications because it improves the interactive performance of these applications.

Unfortunately for us, programming with multiple threads can be quite a headache. The difficulty lies in making sure routines are implemented so they can be run concurrently, by more than one thread at a time. If a routine changes the value of multiple state variables, for example, then it may be important that those changes happen together, without overlapping changes affecting each other. Later in this section, we'll examine briefly the issue of coordinating multiple threads' access to shared data. In other languages, synchronization of threads can be extremely complex and error-prone. You'll see that Java gives you a few simple tools that help you deal with many of these problems. Java threads can be started, interrupted, and assigned priorities. Threads are preemptive, so a higher priority thread can interrupt a lower priority thread when vying for processor time. See Chapter 8 for a complete discussion of threads.

The Java runtime system creates and manages a number of threads. (Exactly how varies with the implementation.) We've already mentioned the repaint thread, which manages repaint() requests and event processing for GUI components that belong to the java.awt and javax.swing packages. Our example applications have done most of their work in one thread. Methods such as mouseDragged() and actionPerformed() are invoked by the windowing thread and run by its thread, on its time. Similarly, our HelloComponent constructor runs as part of the main application thread (the main() method). This means we are somewhat limited in the amount of processing we do within these methods. If we were, for instance, to go into an endless loop in our constructor, our application would never appear, as it would never finish initializing. If we want an application to perform any extensive processing, such as animation, a lengthy calculation, or communication, we should create separate threads for these tasks.

2.4.2 The Thread Class

As you might have guessed, threads are created and controlled as Thread objects. An instance of the java.lang.Thread class corresponds to a single thread. It contains methods to start, control, and interrupt the thread's execution. Our plan here is to create a Thread object to handle our blinking code. We call the Thread's start() method to begin execution. Once the thread starts, it continues to run until it completes its work, we interrupt it, or we stop the application.

So how do we tell the thread which method to run? Well, the Thread object is rather picky; it always expects to execute a method called run() to perform the action of the thread. The run() method can, however, with a little persuasion, be located in any class we desire.

We specify the location of the run() method in one of two ways. First, the Thread class itself has a method called run(). One way to execute some Java code in a separate thread is to subclass Thread and override its run() method to do our bidding. Invoking the start() method of the subclass object causes its run() method to execute in a separate thread.

It's not usually desirable to create a subclass of Thread to contain our run() method. The Thread class has a constructor that takes an object as its argument. If we create a Thread object using this constructor and call its start() method, the Thread executes the run() method of the argument object, rather than its own. In order to accomplish this, Java needs a guarantee that the object we are passing it does indeed contain a compatible run() method. We already know how to make such a guarantee: we use an interface. Java provides an interface named Runnable that must be implemented by any class that wants to become a Thread.

2.4.3 The Runnable Interface

We've used the second technique in the HelloJava4 example. To create a thread, the HelloComponent4 object passes itself (this) to the Thread constructor. This means that HelloComponent4 must implement the Runnable interface, by implementing the run() method. This method is called automatically when the runtime system needs to start the thread.

We indicate that the class implements the interface in our class declaration:

public class HelloComponent4     extends JComponent     implements MouseMotionListener, ActionListener, Runnable {...}

At compile time, the Java compiler checks to make sure we abide by this statement. We have carried through by adding an appropriate run() method to HelloComponent4. It takes no arguments and returns no value. Our run() method accomplishes blinking by changing the color of our text a few times a second. It's a very short routine, but we're going to delay looking at it until we tie up some loose ends in dealing with the Thread itself.

2.4.4 Starting the Thread

We want the blinking to begin when the application starts. So we'll start the thread in the initialization code in HelloComponent4's constructor. It takes only two lines:

Thread t = new Thread(this); t.start( );

First, the constructor creates a new instance of Thread, passing it the object that contains the run() method to the constructor. Since HelloComponent4 itself contains our run() method, we pass the special variable this to the constructor. this always refers to our object. After creating the new Thread, we call its start() method to begin execution. This, in turn, invokes HelloComponent4's run() method in the new thread.

2.4.5 Running Code in the Thread

Our run() method does its job by setting the value of the variable blinkState. We have added blinkState, a boolean variable which can have the value true or false, to represent whether we are currently blinking on or off:

boolean blinkState;

A setColor() call has been added to our paintComponent() method to handle blinking. When blinkState is true, the call to setColor() draws the text in the background color, making it disappear:

g.setColor(blinkState ? getBackground(  ) : currentColor( ));

Here we are being very terse, using the C-language-style ternary operator to return one of two alternative color values based on the value of blinkState. If blinkState is true, the value is the value returned by the getBackground() method. If it is false, the value is the value returned by currentColor().

Finally, we come to the run() method itself:

public void run( ) {   try {     while( true ) {       blinkState = !blinkState;       repaint( );       Thread.sleep(300);     }   }   catch (InterruptedException ie) {} }

Basically, run() is an infinite while loop, which means the loop will run continuously until the thread is terminated by the application exiting.

The body of the loop does three things on each pass:

  • Flips the value of blinkState to its opposite value using the not operator (!)

  • Calls repaint() to redraw the text

  • Sleeps for 300 milliseconds (about a third of a second)

sleep() is a static method of the Thread class. The method can be invoked from anywhere and has the effect of putting the currently running thread to sleep for the specified number of milliseconds. The effect here is to give us approximately three blinks per second. The try/catch construct, described in the next section, traps any errors in the call to the sleep() method of the Thread class.

2.4.6 Exceptions

The try/catch statement in Java handles special conditions called exceptions. An exception is a message that is sent, normally in response to an error, during the execution of a statement or a method. When an exceptional condition arises, an object is created that contains information about the particular problem or condition. Exceptions act somewhat like events. Java stops execution at the place where the exception occurred, and the exception object is said to be thrown by that section of code. Like an event, an exception must be delivered somewhere and handled. The section of code that receives the exception object is said to catch the exception. An exception causes the execution of the instigating section of code to stop abruptly and transfers control to the code that receives the exception object.

The try/catch construct allows you to catch exceptions for a section of code. If an exception is caused by any statement inside a try clause, Java attempts to deliver the exception to the appropriate catch clause. A catch clause looks like a method declaration with one argument and no return type.

try {    ... } catch ( SomeExceptionType e ) {    ... }

If Java finds a catch clause with an argument type that matches the type of the exception, that catch clause is invoked. A try clause can have multiple catch clauses with different argument types; Java chooses the appropriate one in a way that is analogous to the selection of overloaded methods. You can catch multiple types of exceptions from a block of code. Depending on the type of exception thrown, the appropriate catch clause is executed.

If there is no try/catch clause surrounding the code, or a matching catch clause is not found, the exception is thrown up to the calling method. If the exception is not caught there, it's thrown up another level, and so on until the exception is handled, or the Java VM prints an error and exits. This provides a very flexible error-handling mechanism so that exceptions in deeply nested calls can bubble up to the surface of the call stack for handling. As a programmer, you need to know what exceptions a particular statement can generate. For this reason, methods in Java are required to declare the exceptions they can throw. If a method doesn't handle an exception itself, it must specify that it can throw that exception so that its calling method knows that it may have to handle it. See Chapter 4 for a complete discussion of exceptions and the try/catch clause.

So, why do we need a try/catch clause in the run() method? What kind of exception can Thread's sleep() method throw and why do we care about it when we don't seem to check for exceptions anywhere else? Under some circumstances, Thread's sleep() method can throw an InterruptedException, indicating that it was interrupted by another thread. Since the run() method specified in the Runnable interface doesn't declare it can throw an InterruptedException, we must catch it ourselves, or the compiler will complain. The try/catch statement in our example has an empty catch clause, which means that it handles the exception by ignoring it. In this case, our thread's functionality is so simple it doesn't matter if it's interrupted (and it won't be anyway). All the other methods we have used either handle their own exceptions or throw only general-purpose exceptions called RuntimeExceptions that are assumed to be possible everywhere and don't need to be explicitly declared.

2.4.7 Synchronization

At any given time we can have lots of threads running in an application. Unless we explicitly coordinate them, these threads will be executing methods without any regard for what the other threads are doing. Problems can arise when these methods share the same data. If one method is changing the value of some variables at the same time another method is reading these variables, it's possible that the reading thread might catch things in the middle and get some variables with old values and some with new. Depending on the application, this situation could cause a critical error.

In our HelloJava examples, both our paintComponent() and mouseDragged() methods access the messageX and messageY variables. Without knowing more about the implementation of the Java environment, we have to assume that these methods could conceivably be called by different threads and run concurrently. paintComponent() could be called while mouseDragged() is in the midst of updating messageX and messageY. At that point, the data is in an inconsistent state and if paintComponent() gets lucky, it could get the new x value with the old y value. Fortunately, Swing does not allow this to happen in this case because all event activity is handled by a single thread, and we probably would not even notice if it were to happen in this application anyway. We did, however, see another case, in our changeColor() and currentColor() methods, where there is the potential for a more serious "out of bounds" error.

The synchronized modifier tells Java to acquire a lock for the object that contains the method before executing that method. Only one method in the object can have the lock at any given time, which means that only one synchronized method in that object can be running at a time. This allows a method to alter data and leave it in a consistent state before a concurrently running method is allowed to access it. When the method is done, it releases the lock on the class.

Unlike synchronization in other languages, the synchronized keyword in Java provides locking at the language level. This means there is no way that you can forget to unlock a class. Even if the method throws an exception or the thread is terminated, Java will release the lock. This feature makes programming with threads in Java much easier than in other languages. See Chapter 8 for more details on coordinating threads and shared data.

Whew! Well, it's time to say goodbye to HelloJava. We hope that you have developed a feel for the major features of the Java language and that this will help you as you explore the details of programming with Java. If you are a bit bewildered by some of the material presented here, take heart. We'll be covering all the major topics presented here again in their own chapters throughout the book. This tutorial was meant to be something of a "trial by fire" to get the important concepts and terminology into your brain so that the next time you hear them you'll have a head start.

[1]  Event handling in Java 1.0 was a very different story. Early on, Java did not have a notion of event listeners and all event handling happened by overriding methods in base GUI classes. This was both inefficient and led to poor design with a proliferation of highly specialized components.

[2]  Why isn't it just called a Button? Button is the name that was used in Java's original GUI toolkit, the Abstract Window Toolkit (AWT). AWT had some significant shortcomings, so it was extended and essentially replaced by Swing in Java 2. Since AWT already took the reasonable names such as Button and MenuBar and mixing them in code could be confusing, Swing user interface components have names that are prefixed with "J", such as JButton and JMenuBar.

CONTENTS


Learning Java
Learning Java, Second Edition
ISBN: 0596002858
EAN: 2147483647
Year: 2002
Pages: 30

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