Custom Objects and Threads
Recall that the last applet we were working on paints a rectangle in a canvas, and offers control buttons to manipulate the color, shape, and location of that rectangle.
Click here for MoveBoxWithCanvas.java source code Click here for DrawingCanvas.java source code
When looking at our applet, the one thing that really sticks out is the method of color selection: first, it looks 'non-standard', and second it involves a fair amount of code. Therefore, we want to design a new object specifically made for selecting a color, and then replace the code in our applet using the new ColorSelector object.
I have designed a new ColorSelector object that you can use in any of your classes. Here is a description of our new ColorSelector object. This description and the corresponding class file is everything you need to use the object. You do not need to know how it actually works (but of course the source code is included for your reference).
In our original code we do not need to touch the DrawingCanvas at all. Since we only
want to modify the user interface, we only need to modify the main applet code. And, in
fact, we can considerably simply that code. Here is the old and the new code, where I only
list the methods that have changed from the previous "MoveBoxWithCanvas" and
"DrawingCanvas" classes:
Without using ColorSelector |
Using the ColorSelector Object |
private Button up = new Button("Up"); private Button down = new Button("Down"); private Button left = new Button("Left"); private Button right = new Button("Right"); private Button increase = new Button("[+]"); private Button decrease = new Button("[-]"); private Choice objects = new Choice(); private CheckboxGroup colors = new CheckboxGroup(); private Checkbox red = new Checkbox("red", colors, true), black = new Checkbox("black", colors, false), blue = new Checkbox("blue", colors, false), yellow = new Checkbox("yellow", colors, false), green = new Checkbox("green", colors, false), pink = new Checkbox("magenta", colors, false); private DrawingCanvas drawing = new DrawingCanvas(); |
private Button up = new Button("Up"); private Button down = new Button("Down"); private Button left = new Button("Left"); private Button right = new Button("Right"); private Button increase = new Button("[+]"); private Button decrease = new Button("[-]"); private Choice objects = new Choice(); private ColorSelector colors = new ColorSelector("vertical"); private DrawingCanvas drawing = new DrawingCanvas(); |
public void init() { objects.addItem("Rectangle"); objects.addItem("Circle"); objects.addItem("Oval"); Panel head = new Panel(); head.setLayout(new FlowLayout()); head.add(new Label("Choose an object: ")); head.add(objects) Panel colorCol = new Panel(); colorCol.setLayout(new GridLayout(6,1)); colorCol.add(black); colorCol.add(blue); colorCol.add(green); colorCol.add(pink); colorCol.add(red); colorCol.add(yellow); Panel buttons = new Panel(); buttons.setLayout(new FlowLayout()); buttons.add(up); buttons.add(down); buttons.add(left); buttons.add(right); buttons.add(increase); buttons.add(decrease); setBackground(Color.blue); setLayout(new BorderLayout()); add("South", buttons); add("North", head); add("West", colorCol); add("Center", drawing); } |
public void init() { objects.addItem("Rectangle"); objects.addItem("Circle"); objects.addItem("Oval"); Panel head = new Panel(); head.setLayout(new FlowLayout()); head.add(new Label("Choose an object: ")); head.add(objects); Panel buttons = new Panel(); buttons.setLayout(new FlowLayout()); buttons.add(up); buttons.add(down); buttons.add(left); buttons.add(right); buttons.add(increase); buttons.add(decrease); setBackground(Color.blue); setLayout(new BorderLayout()); add("South", buttons); add("North", head); add("West", colors); add("South", buttons); } |
public boolean action(Event e, Object o) { if (e.target == up) return drawing.handleUp(); else if (e.target == down) return drawing.handleDown(); else if (e.target == left) return drawing.handleLeft(); else if (e.target == right) return drawing.handleRight(); else if (e.target == increase) return drawing.handleIncrease(); else if (e.target == decrease) return drawing.handleDecrease(); else if (e.target == black) return drawing.handleColorChange(Color.black); else if (e.target == black) return drawing.handleColorChange(Color.black); else if (e.target == blue) return drawing.handleColorChange(Color.blue); else if (e.target == green) return drawing.handleColorChange(Color.green); else if (e.target == pink) return drawing.handleColorChange(Color.pink); else if (e.target == red) return drawing.handleColorChange(Color.red); else if (e.target == yellow) return drawing.handleColorChange(Color.yellow); else if (e.target instanceof Choice) return drawing.handleChangeObject( objects.getSelectedItem()); else return super.action(e, o); } |
public boolean action(Event e, Object o) { if (e.target == up) return drawing.handleUp(); else if (e.target == down) return drawing.handleDown(); else if (e.target == left) return drawing.handleLeft(); else if (e.target == right) return drawing.handleRight(); else if (e.target == increase) return drawing.handleIncrease(); else if (e.target == decrease) return drawing.handleDecrease(); else if (e.target == colors) drawing.handleChangeColor(colors.getColor()); else if (e.target instanceof Choice) return drawing.handleChangeObject(objects.getSelectedItem()); else return super.action(e, o); } |
Of course, our code has not gotten shorter - even though at first glance it has. But, we are now using the ColorSelector's code as well, so that in total the number of lines has gone up. However, the code for the actual program doing our specific task is shorter, more concise, and easier to understand. As long as the ColorSelector is doing what it promises, we don't have to care how it works at all, and therefore it does not add to the complexity of our program. Moreover, we can now use the ColorSelector in other programs as well, or use it repeatedly in the same program, for example to choose foreground and background colors. Recall, however, that at our current state of knowledge the file ColorSelector.class must reside in the same directory as the rest of the class file in order to run correctly. Here is our new applet in action:
Click here for the code of the ColorSelector object | |
Click here with the right mouse button to download the ColorSelector.class file |
Most modern computers systems can handle multiple programs at the same time. Actually, that is somewhat misleading: computers with one central processor can only handle a single program at any given time, but it can switch between a variety of programs fast enough so that the programs appear to run simultaneously.
To carry this multitasking concept even further, why not have one program execute several strands of execution at the same time (again, not really at the same time but appearently simultaneously). In fact, many programs that you are using are employing this concept, called 'threads', all the time. For example:
Netscape can download a file and begin displaying the "top" of the file while continuing to download the rest - a thread handles the download independently of the display | |
Microsoft Word 7 and 8 check your spelling as you type, and underlines any mistakes it finds while you make the mistake - a thread does this ongoing spellchecking | |
The Java virtual machine automatically cleans up objects that are no longer in use and reallocates memory - a thread checks for objects pointing to null and disposes those while the rest of the program runs normally | |
Animations run while allowing you to press buttons to control the way the animation runs - a thread checks for user input and modifies the running animation whenever needed |
So, we also want start learning about this concept by creating our own threads of execution in java programs. Java provides a Thread class in the java.lang package, so we could use that. However, if we want to make applets with threads, we have a basic problem: our applet extends Applet, and since Java does not allow multiple inheritance, it can not at the same time extend Thread also. The problem is solved using an Interface. First, let's look at the Runnable interface that allows classes that already extend other classes to use threads as well. Here's what the Java API has to say about the Runnable interface:
So the Runnable interface has only one method, and that is an abstract method. That, of course, means that if your class implements Runnable, it must define the run method (otherwise it would become an abstract class itself). Here is a simple example of a threaded applet that will simply count from 0 up.
First, our classes extends Applet. Second, it implements Runnable and hence
must define the run method. Next, a new field thread of type Thread is
added to the applet (in addition to the TextField and the integer). When the applet loads,
its init method executes, setting up the proper layout, instantiating a new Thread, and
executing the thread's start method. Note that when the thread is instantiated,
it is 'linked' to the current instance of our applet, because the reference 'this'
is passed as a parameter to the thread. It means that when the thread's start method
is called, it will in turn call upon the applet's run method. In other word, the
thread basically controls what is going on and when, while the run method
specifies exactly what the applet should do. Very often the run method is an infinite loop
whose exectution is governed by the thread's start(), stop(), and sleep(#)
methods. In particular, the sleep method will make the thread wait for the specified
number of microseconds. Since the sleep method can throw an exception, it need to be
embedded in a standard try-catch block. During the time a thread sleeps, other threads or
programs will get the processor and the processor can execute them. When the thread needs
to run again, it will request processor time from the CPU, which in turn will execute the
thread as soon as possible.Here's the above applet in action:
But now we have a big problem: to see what I mean, take a look at the last number you see,
then go back to the previous page. Wait a while, then return to this place. You will see
that the number has increased even when you were not looking at the page. That does
illustrate the concepts of independently exeecuting threads nicely, but it means that once
you look at this page, the thread will run indefinitely even if you don't look at this
page any longer. It would, however, quit executing when the JVM shuts down - i.e. when
Netscape quits. That, of course, is a waste of resources, and we need to improve on the
above applet, as in the following code:
Let's review carefully what happens here:
the applet is loaded as soon as the user 'hits' this page and therefore the applet's init method executes. It instantiates, among other things, a new thread which is now not pointing to null. Then the thread's start method is executed - different from the applets own start method - which automatically executes the applet's run method. Since thread does not currently point to null, the loop is indefinite and the thread will start running (counting up the numbers). | |
Everytime the user leaves your page, the 'stop' method of your applet will execute. That in turn stops the thread (killing the run loop) and sets the thread to null, unless it already set to null (the counter will stop counting). | |
When a user revisits your page, the applet's 'start' method will execute. Since the thread has been set to null (by the stop method), a new thread is instantiated and started (which in turn will get into the infinite loop of the run method again and the counter will continue counting). | |
The previously running thread that now points to null will be cleaned up automatically by the Java Virtual Machine's garbage collection mechanism (which is, to be sure, another thread). |
Note that the first time the applet loads, first the init method executes, and immediately after the start method. However, since the thread was instantiated in the init method, it is not null, and therefore the start method does not do anything (it still executes, though).
Here is the applet in action. Check the following: notice the number of the counter,
then go to the previous page. Wait a few seconds, then return to this page. You should see
that the counter was not increased when you were not looking at the page, but when you
return to the page the counter starts increasing from wherever it left off. In other
words, initially both counters are approximately equal (since the threads started at about
the same time). When you flip pages and return, however, the second counter stops when you
are not looking at the page, while the first one continues to run.
Note and Warning: When using threads in an applet, be sure to use the appropriate start and stop methods. If you don't, a thread may continue executing even when you are not looking at the page, and that would waste precious resources.
There may be occasions when you do want the thread to continue running, but not when the run
loop consists of an otherwise infinite loop.
As a case in point, whenever you look at this page, the first counter thread will run, and t
Next, let's modify our previous MiniPaint applet. Instead of having buttons to move the rectangle around, we now want the rectangle to continuously move around, while the buttons can be used to change the direction. The other control elements should work as before.
Here is what we will have to do:
We do not want to modify the actual applet | |
The canvas is responsible for the drawing, and therefore we need to implement the Runnable interface for the drawing canvas, not for the original applet. | |
Modeled after the above simple examples, we need to add appropriate start, stop, and run methods to the canvas | |
we also need to add another thread as a field, and since we now need to store the direction somewhere, we finally need an additional field to contain the current direction of the moving rectangle. |
This time, let's first take a look at the working applet, then let's come up with the source code we need to accomplish this applet.
The code for the class MiniPaint is exactly as before, with no changes at all - well, actually we did rename the class, so we now need to have field of type ThreadedCanvas instead of DrawingCanvas. But no change other than that.. We of course have made use of convenient our ColorSelector object, as outlined at the beginning of this lecture. Here is the code for the modified drawing canvas object, with the new code in bold and methods that have not changed removed.
Note that our start and stop methods imply that the current object moving in the given direction will actually stop as soon as you leave this page, and resume its movement as soon as you revisit this page. That, in fact, would be the appropriate behaviour.
To conclude the discussion about threads, here is a selection from the Java API on Threads. Keep in mind that an applet using threads needs to have a thread object as a field - with all the methods listed below - and in needs to have a start, stop, and run method as well.
We will go into some more details on threads when we discuss bigger projects we are going to undertake next. Don't forget to quit and restart Netscape - the first counter thread is still running, and can not be stopped otherwise !