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