Hello Web! III: The Button Strikes!
Well, now that we have those concepts under control, we can move on to some fun stuff. HelloWeb3
brings us a new graphical interface component: the Button
. We add a Button
component to our applet that changes the color of our text each time the button is pressed. Our new example is shown below.
import java.applet.Applet; import java.awt.*; import java.awt.event.*; public class HelloWeb3 extends Applet implements MouseMotionListener, ActionListener { int messageX = 125, messageY = 95; String theMessage; Button theButton; int colorIndex = 0; static Color[] someColors = { Color.black, Color.red, Color.green, Color.blue, Color.magenta }; public void init() { theMessage = getParameter("message"); theButton = new Button("Change Color"); add(theButton); addMouseMotionListener(this); theButton.addActionListener(this); } public void paint( Graphics gc ) { gc.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 ]; } }
Create HelloWeb3
just as the other applets and put an <applet>
tag referencing it in an HTML document. An <applet>
tag just like the one for HelloWeb2
will do nicely. Run the example, and you should see the display shown in Figure 2.6. Drag the text. Each time you press the button the color should change. Call your friends! They should be duly impressed.
Figure 2.6: Hello Web! III
The New Operator
So what have we added this time? Well, for starters we have a new variable:
Button theButton;
The theButton
variable is of type Button
and is going to hold an instance of the java.awt.Button
class. The Button
class, as you might expect, represents a graphical button, which should look like other buttons in your windowing system.
Two additional lines in init()
actually create the button and cause it to be displayed in our applet:
theButton = new Button("Change Color"); add(theButton);
The first line brings us to something new. The new
keyword is used to create an instance of a class. Recall that the variable we have declared is just an empty reference and doesn't yet point to a real object--in this case, an instance of the Button
class. This is a fundamental and important concept. We have dealt with objects previously in our examples. We have assigned them to variables, and we have called methods in them. So far, however, these objects have always been handed to us ready to go, and we have not had to explicitly create them ourselves. In the case of our paint()
method, we were given a Graphics
object with which to draw. The system created this instance of the Graphics
class for our area of the screen and passed it to us in the parameter variable gc
. Our theMessage
variable is of type String
, and we used it to hold a String
that was returned by the getParameter()
method. In each case, some other method instantiated (created a new instance of) the class for us.
The closest we came to actually instantiating an object was when we passed the name of the HTML <applet>
parameter as an argument to the getParameter()
method. In that case, we created a String
object and passed it as the argument, but we did it in a rather sneaky fashion. As I mentioned previously, String
objects have special status in the Java language. Because strings are used so frequently, the Java compiler creates an instance of the String
class for us whenever it comes across quoted text in our source code. String
objects are, in all other respects, normal objects. (See Basic Utility Classes.)
The new
operator provides the general mechanism for instantiating objects. It's the feature of the Java language that creates a new instance of a specified class. It arranges for Java to allocate storage for the object and then calls the constructor method of the objects' class to initialize it.
Constructors
A constructor is a special method that is called to set up a new instance of a class. When a new object is created, Java allocates storage for it, sets 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 looks like a method with the same name as its class. For example, the constructor for the Button
class is called Button()
. 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 whatever information is passed to them in parameters.
It's important to understand the difference between a constructor and a method like our init()
method. Constructors are special methods of a class that help set up and initialize an instance of a class when it's first created. The init()
method of the Applet
class serves a very similar purpose; however, it's quite different. Constructors are a feature of the Java language. Every class, including Applet
, has constructors. init()
, however, is just a method of the Applet
class like any other. It's an application-level phenomenon that happens to perform initialization.
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, we create a new instance of Button
and assign it to our theButton
variable:
theButton = new Button("Change Color");
This Button
constructor takes a String
as an argument and, as it turns out, uses it to set the label of the button on the screen. A class could also, of course, 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. The Button
class, for example, has a public method, setLabel()
, that allows us to set a Button
's label at any time. Constructors with parameters are therefore a convenience that allows a sort of short hand to set up a new object.
Method Overloading
I said this Button
constructor because there could be more than one. A class can have multiple constructors, each taking different parameters and possibly 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 that are passed to it. We call the Button
constructor and pass it a String
argument, so Java locates the constructor method of the Button
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 developing principle of polymorphism.
A constructor method that takes no arguments is called the default constructor. As you'll see in Basic Utility Classes, default constructors play a special role in the initialization of inherited class members.
Garbage Collection
I've told you how to create a new object with the new
operator, but I haven't said anything about how to get rid of an object when you are done with it. If you are a C developer, you're probably 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 run-time 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.
Components
I have used the terms component and container somewhat loosely to describe graphical elements of Java applications. However, you may recall from Figure 2.3 that these terms are the names of actual classes in the java.awt
package.
Component
is a base class from which all of 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. The familiar paint()
method we have been using in our example is actually inherited from the Component
class. Applet
is, of course, a kind of Component
and inherits all of its public members, just as other (perhaps simpler) types of GUI components do.
The Button
class is also derived from Component
and therefore shares this functionality. This means that the developer of the Button
class had methods like paint()
available with which to implement the behavior of the Button
object, just as we did when creating our applet. What's exciting is that we are perfectly free to further subclass components like Button
and override their behavior to create our own special types of user-interface components.
Both Button
and Applet
are, in this respect, equivalent types of things. However, the Applet
class is further derived from a class called Container
, which gives it the added ability to hold other components and manage them.
Containers
A Button
object is a simple GUI component. It makes sense only in the context of some larger application. 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 scheme. A Container
may also receive events related to its child components. As I mentioned earlier, if a component doesn't respond to a particular type of event by overriding the appropriate event-handling method and handling the event, the event is passed to the parent Container
of the component. This is the default behavior for the standard Java AWT components, which gives us a great deal of flexibility in managing interface components. We could, for example, create a smart button by subclassing the Button
class and overriding certain methods to deal with the action of being pressed. Alternatively, we could simply have the Button
's container note which Button
is pressed and handle the event appropriately. In the interest of keeping our examples contained in a single class, I am using the gestalt view and letting our Button
's container, HelloWeb3
, deal with its events.
Remember that a Container
is a Component
too and, as such, can be placed alongside other Component
objects in other Containers
, in a hierarchical fashion, as shown in Figure 2.7. Our HelloWeb3
applet is a kind of Container
and can therefore hold and manage other Java AWT components and containers like buttons, sliders, text fields, and panels.
Figure 2.7: A hypothetical layout of Java containers and components
In Figure 2.7, 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.
Layout
After creating the Button
object, we'd like to stick it in our applet. To do so, we invoke the add()
method of Applet
, passing the Button
object as a parameter:
add(theButton);
add()
is a method inherited by our Applet
from the Container
class. It appends our Button
to the list of components HelloWeb3
manages. Thereafter, HelloWeb3
is responsible for the Button
: the applet causes the button to be displayed, it determines where in our part of the screen the button should be placed, and it receives events when the button is pushed.
Java uses an object called a LayoutManager
to determine the precise location in HelloWeb3
's screen area the Button
is displayed. 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 Layout Managers. There are several standard layout managers to choose from, and we can, of course, create new ones. In our case, we have not specified a layout manager, so we get the default one, which means our button appears centered at the top of the applet.
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 Button
object. What's going on?
Well, if you check the inheritance diagram in Figure 2.3 again, you'll see that Button
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, we can use an instance of a subclass anywhere we use an instance of its superclass. This is a very important concept, and it's a second aspect of the object-oriented principle of polymorphism. Button
is a kind of Component
, and any method that expects a Component
as an argument will accept a Button
.
More Events and Interfaces
Now that we have a Button
, we need some way to communicate with it: that is, to get the events it generates. We could just listen for mouse clicks, figure out whether they were close enough to the button, and act accordingly. But that would take a lot of work, and would give up the advantages of using a pre-built component. Buttons generate a special kind of event called an ActionEvent
when someone clicks on them. To receive these events, we have added another method to our class:
public void actionPerformed( ActionEvent e ) { if ( e.getSource() == theButton ) { changeColor(); } }
If you understood the previous applet, you shouldn't be surprised to see that HelloWeb3
now declares that it implements the ActionListener
interface, in addition to MouseMotionListener
. ActionListener
requires us to implement an actionPerformed()
method, which is called whenever an ActionEvent
occurs. You also shouldn't be surprised to see that we added a line to init()
, registering the applet as a listener for the button's action events: this is the call to theButton.addActionListener(this)
.
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
, the only button we've put in the applet. This may seem superfluous; after all, what else could possibly generate an action event? In this applet, nothing. But it's a good idea to check, because another applet may have several buttons, and you may need to figure out which one is meant. Or you may add a second button to this applet later, and you don't want the applet to break something. To make this check, we call the getSource()
method of the ActionEvent
object, e
. Then we use the "==" operator to make sure that the event source matches theButton
. Remember that 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 Objects in Java, we'll look at the equals()
method, which tests for equivalence. Once we establish that the event e
comes from the right button, we call our changeColor()
method, and we're done.
You may be wondering why we don't have to change mouseDragged()
now that we have a Button
in our applet. 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 that you can drag the text right through the Button
and even lose it behind the Button
if you aren't careful: try it and see!
Color Commentary
To support HelloWeb3
'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 to this array, specifying the current color:
Color[] someColors = { Color.black, Color.red, Color.green, Color.blue, Color.magenta }; int colorIndex;
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 and are used by all classes in the java.awt
package that deal with color graphics. Notice that we are referencing variables such as Color.black
and Color.red
. These look like normal references to an object's instance variables; however Color
is not an object, it's a class. What is the meaning of this?
Static Members
If you recall our discussion of classes and instances, I hinted then that a class can contain methods and variables 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 all of 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. The second advantage is that static members can be accessed even if no instances of the class exist. A hypothetical Component
class might have a static variable called maximumWidth
. Some other class that has to deal with this component, such as a layout manager, might want to know the maximum width of such a component, even if there aren't any around at the moment. Since maximumWidth
is a static variable, the layout manager can get this information.
An instance of the Color
class represents a color. For convenience, the Color
class contains some static, predefined color objects with friendly names like green
, red
, and (my favorite) magenta
. Color.green
is thus a predefined Color
object that is set to a green color. In this case, these static members of Color
are not changeable, so they are effectively constants and can be optimized as such by the compiler. Constant static members make up for the lack of a #define
construct in Java. However, static variables don't, in general, have to be constant. I'll say more about static class members in Objects in Java. The alternative to using these predefined colors is to create a color manually by specifying its red, green, blue (RGB) components using a Color
class constructor.
Arrays
Next, we turn our attention to the array. We have declared a variable called someColors
, which is an array of Color
objects. Arrays are syntactically supported by the Java language; however, they are true, first-class objects. This means that an array is, itself, a type of object 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 the object in the corresponding slot in the array. Our code uses the colorIndex
variable to index someColors
. It's also possible to have an array of simple primitive types, rather than objects.
When we declare an array, we can initialize it by using the familiar C-like curly brace construct. Specifying a comma-separated list of elements inside of 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 The Java Language for a complete discussion of arrays.
Our Color Methods
So, we now have an array of Color
objects and a variable with which to index them. What do we do with them? Well, we have declared two private methods that 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 are not visible outside of the object. 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 developing practice. Private methods are also often created as helper functions for use solely within 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 action()
method whenever the button is pressed.
synchronized private void changeColor() { if ( ++colorIndex == someColors.length ) colorIndex = 0; setForeground( currentColor() ); repaint(); }
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 reset our index to zero and start over. After changing the currently selected color, we do two things. First, we call the applet's setForeground()
method, which changes the color used to draw text in the applet and the color of the button's label. Then we call repaint()
to cause the applet to be redrawn with the new color for the draggable message.
So, 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 is that in changeColor()
we increment colorIndex
before testing its value. That 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 run-time "array out of bounds" error. There are, of course, ways in which we could fudge around the problem in this case, but this simple example is representative of more general synchronization issues we need to address. In the next section, you'll see that Java makes dealing with these problems easy through language-level synchronization support.