Now that we’ve got some basics down, let’s make our application a little more interactive. The following minor upgrade 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
, and so on). 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
.
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 indicate 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
Or, if you are following in Eclipse, click the Run button. 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. Now let’s see what’s changed.
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.
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
application-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
Chapter 10 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 application using the following command:
C:
\
>
java
HelloJava2
"Hello, Java!"
args[0]
refers to the
first command-line parameter. Its meaning will become clearer when we
discuss arrays later in the book. If you are using an IDE, such as
Eclipse, you will need to configure it to accept your parameters before
running it.
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.
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 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
Action
Event
. 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 Mouse
Event
s.
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. 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.
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.[3]
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()
.
Because 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 Figure 2-8.
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 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 application.
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 must
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.
[3] 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.
Get Learning Java, 4th Edition 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.