HelloJava2: The Sequel

Now that we've got some basics down, let's make our app 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 lie 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 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 using the following command:

 C:\> 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 app exits; we'll explain that later when we talk about events. Now let's see what's changed.

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 0, false, or null, depending on their type. Numeric types are set to 0, 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.

Constructors

The HelloComponent2 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 app-level setup is required. A constructor always has the same name as its class. For example, the constructor for the HelloComponent2 class is called HelloComponent2( ). 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.add( new HelloComponent2("Hello, Java!") );


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

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


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 for a complete discussion of the String class.) The second line simply adds our new component 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:

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


Now you can pass the text on the command line when you run the app using the following command:

 C:\> 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 tutorial. HelloComponent2'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 HelloComponent2(String message) {
 theMessage = message;
 addMouseMotionListener( this );
 }


The special, read-only variable called this is used to explicitly refer to our object (the "current" object context) 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 or use our public variables.

Events

The last two methods of HelloComponent2, 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 app. 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 mousethat 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. 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 tutorial. 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.[*]

[*] 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.

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 app 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( ).

The repaint( ) Method

Since we changed the coordinates for the message (when we dragged the mouse), we would like HelloComponent2 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 Screenshot-4. 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

Screenshot-4. Invoking the repaint( ) method
Java ScreenShot

during painting-intensive situations like scrolling. Another advantage is that all the painting functionality must be encapsulated through our paintComponent( ) method; we aren't tempted to spread it throughout the app.

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. It does say that the methods must take a MouseEvent as an argument and return no value (that's what void means). 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. 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 . 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.

Java ScreenShot
Comments