HelloJava2: The Sequel

Let’s make our application a little more interactive, shall we? The following improvement, HelloJava2 , allows us to drag the message around with the mouse.

HelloJava2 is a new application—another subclass of the JComponent class. In that sense, it’s a sibling of HelloJava1. Having just seen inheritance at work, you might wonder why we aren’t creating a subclass of HelloJava1 and exploiting inheritance to build upon our previous example and extend its functionality. Well, in this case, that would not necessarily be an advantage, and for clarity we simply start over.[8]

Here is HelloJava2:

//file: HelloJava2.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class HelloJava2
    extends JComponent implements MouseMotionListener {

  // Coordinates for the message
  int messageX = 125, messageY = 95;
  String theMessage;

  public HelloJava2(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) {}

  public static void main(String[] args) {
    JFrame f = new JFrame("HelloJava2");
    // Make the application exit when the window is closed.
    f.addWindowListener(new WindowAdapter( ) {
      public void windowClosing(WindowEvent we) { System.exit(0); }
    });
    f.setSize(300, 300);
    f.getContentPane( ).add(new HelloJava2("Hello, Java!"));
    f.setVisible(true);
  }
}

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 a new class file, HelloJava2.class, as a result.

To run the new example, use the following command line:

% 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.

The import Statement

So, what have we added? First you may notice that a few lines are now hovering above our class:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class HelloJava2
...

The import statement lists external classes to use in this file and tells the compiler where to look for them. In our first example, we designated the JComponent class as the superclass of HelloJava1. JComponent was not defined by us, and the compiler therefore had to look elsewhere for it. In that case, we referred to JComponent by its fully qualified name, which is javax.swing.JComponent. The JComponent class and all the other classes in the javax.swing package are stored in a standard location, known to the compiler.

In this example, the statement import javax.swing.* enables us to refer to all the classes in the javax.swing package by their simple names. For example, we don’t have to use fully qualified names to refer to the JComponent and JFrame classes. 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 of the AWT package’s classes. However, we are anticipating using several more classes from this package in the upcoming examples.

We also import all the classes from the package java.awt.event ; these classes provide the Event objects that we use to communicate with the user. By listening for events, we find out when the user moved the mouse, clicked a button, and so on. Notice that importing java.awt.* doesn’t automatically import the event package. The asterisk imports only the classes in a particular package, not other packages. Packages don’t contain other packages, even if the hierarchical naming scheme would seem to imply such a thing.

The import statement may seem a bit like the C or C++ preprocessor #include statement, which injects header files into programs at the appropriate places. This is not true; there are no header files in Java. The import statement does not copy any code into a source file. It’s just a convenience. Think of it as “introducing” one or more external classes to the compiler; after they’ve been introduced, you can call them by their simple names, instead of by their fully-qualified names.

Instance Variables

We have added some variables to our example:

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

messageX and messageY are integers that hold the current coordinates of our movable message. They are initialized to default values, which should place the message somewhere near the center of the window. Java integers are always 32-bit signed numbers. There is no fretting about what architecture your code is running on; numeric types in Java are precisely defined. 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 or member variables because they belong to the entire class, and copies of them appear in each separate instance of the class. Instance variables are always visible (usable) in any of 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 (zero), false, or null. 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 of a single 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 will generate a compile-time error. Local variables live only as long as the method is executing and then disappear (which is fine, since nothing outside of the method can see them anyway). Each time the method is invoked, its local variables are recreated and must be assigned values.

We have made some changes to our previously stodgy paintComponent( ) method. All of the arguments in the call to drawString( ) are now variables.

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 then calls the constructor method for the class to do whatever application-level setup is required.

A constructor method is a method with the same name as its class. For example, the constructor for the HelloJava2 class is called HelloJava2( ). Constructors don’t have a return type; by definition, they return an object of that class. But 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 parameters.

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

f.getContentPane( ).add(new HelloJava2("Hello, Java!"));

This line actually does three things. The following lines are equivalent, and a little easier to understand:

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

The first line is the important one, where a new HelloJava2 object is created. The HelloJava2 constructor takes a String as an argument and, as it turns out, uses it to set the message that is displayed in the window. A class could also provide methods that allow us to configure an object manually after it’s created or to change its configuration at a later time. Many classes do both; the constructor simply takes its arguments and passes them to the appropriate methods or variables. The HelloJava2 class, for example, could have a public method, setMessage( ), that allowed us to set the message at any time. Constructors with parameters are therefore a convenience that allows a sort of shorthand to set up a new object.

HelloJava2’s constructor does two things: it sets the text of the theMessage instance variable, and it tells the system “Hey, I’m interested in anything that happens involving the mouse”:

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

So what, you may ask, is the type of the argument to the HelloJava2 constructor, back in the main( ) method? It, too, is a String. With a little magic from the Java compiler, quoted strings in Java source code are turned into String objects. A bit of funny business is going on here, but it’s simply for convenience. (See Chapter 9, for a complete discussion of the String class.)

We can use a special read-only variable, called this , to explicitly refer to our object. A method can use this to refer to the instance of the object that holds it. The following two statements are therefore equivalent ways to assign a value to an instance variable:

theMessage = message;

or:

this.theMessage = message;

We’ll always use the shorter, implicit, form to refer to instance variables. But we’ll need the this variable when we have to 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.

The other method that we call in HelloJava2’s constructor is addMouse-MotionListener( ). This method is part of the event mechanism, which we discuss next.

Events

The last two methods of HelloJava2 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-sensitive 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 graphical user interface (GUI) component in an application. A keystroke, for instance, could correspond to a character being typed into a particular text entry field. Pressing a mouse button could activate a particular button on the screen. Even just moving the mouse within a certain area of the screen could be intended to trigger effects such as highlighting or changing the cursor’s shape.

The way events work was one of the major changes between Java 1.0 and Java 1.1. We’re going to talk about the Java 1.1 (and later) events only; they’re a big improvement, and there’s no sense in learning yesterday’s news. In Java 1.1 and later, 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 types 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 a MouseEvent.

The various GUI components in Java generate events. For example, if you click the mouse inside a component, the component generates a mouse event. (We can view events as a general-purpose way to communicate between Java objects; but for the moment, let’s limit ourselves to the simplest case.) In Java 1.1 and later, any object can ask to receive the events generated by another component. We will call the object that wants to receive events a “listener.” 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 remaining methods in our class are for. The mouseDragged( ) method is called automatically to receive the event generated whenever 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. Our mouseMoved( ) method is boring: it doesn’t do anything. We’re ignoring simple mouse motions.

mouseDragged( ) has a bit more meat to it. It is called repeatedly 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. These are saved in the messageX and messageY instance variables. Now, having changed the coordinates for the message, 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 because we don’t have a graphics context to pass to it.

There’s one other place where we’ve added an event handler: the main( ) method. There, we created an event handler that shuts down the application (by calling System.exit( )) when the user closes our main window. The syntax might look a little weird; we’ve used something tricky called an inner class to get the job done. Inner classes are discussed in Chapter 6. They’re very useful for event handlers.

The real 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 he or she wants, and you won’t be bothered. Java 1.1 and Java 2 don’t go around asking potential recipients whether they might be interested in some event, as happened in Java 1.0. 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.

We’ve danced around one question that may be bothering you by now: how does the system know to call mouseDragged( ) and mouseMoved( )? 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

We can use the repaint( ) method of the JComponent class to request 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.

Invoking the repaint( ) method

Figure 2-4. Invoking the repaint( ) method

This mode of operation isn’t just an inconvenience brought about by not having the right graphics context handy at the moment. The foremost advantage to this mode of operation is that the repainting 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 painting-intensive situations like scrolling. Another advantage is that all of the painting functionality can be kept in our paintComponent( ) method; we aren’t tempted to spread it throughout the application.

Interfaces

Now it’s time to face up to 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 HelloJava2 class: we say that the class implements the MouseMotionListener interface. 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—and indeed, mouseMoved( ) doesn’t do anything. It does say that the methods must take a MouseEvent as an argument and return void (i.e., no return value).

Another way of looking at an interface is as 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. This means that you don’t care about the object’s class; the only requirement is that the object implement the given interface. addMouseMotionListener( ) is such a method: its argument must be an object that implements the MouseMotionListener interface. The argument we pass is this, the HelloJava2 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.

In other languages, you’d handle this problem by passing a function pointer; for example, in C, the argument to addMouseMotionListener( ) might be a pointer to the function you want to have called when an event occurs. This technique is called a callback. For a variety of reasons, the Java language has eliminated function pointers. Instead, we use interfaces to make contracts between classes and the compiler. (Some new features of the language make it easier to do something similar to a callback, but that’s beyond the scope of this discussion.)

The Java distribution comes with many interfaces that define what classes have to do in various situations. 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, like listening for mouse events. Interfaces give you a way of acting on objects based on their capabilities, without knowing or caring about their actual type.

Furthermore, interfaces provide an important escape clause to the Java rule that any new class can extend only a single class (“single inheritance”). They provide most of the advantages of multiple inheritance (a feature of languages like C++) without the confusion. A class in Java can extend only one class but can implement as many interfaces as it wants; our next example will implement two interfaces, and the final example in this chapter will implement three. In many ways, interfaces are almost like classes, but not quite. They can be used as data types, they can even 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.



[8] You are left to consider whether such subclassing would even make sense. Should HelloJava2 really be a kind of HelloJava? Are we looking for refinement or just code reuse?

Get Learning Java now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.