Javanot
Javanot
∗ ∗ ∗
A computer system consisting of many devices is typically organized by connecting those
devices to one or more busses. A bus is a set of wires that carry various sorts of information
between the devices connected to those wires. The wires carry data, addresses, and control
signals. An address directs the data to a particular device and perhaps to a particular register
or location within that device. Control signals can be used, for example, by one device to alert
another that data is available for it on the data bus. A fairly simple computer system might
be organized like this:
Now, devices such as keyboard, mouse, and network interface can produce input that needs
to be processed by the CPU. How does the CPU know that the data is there? One simple idea,
which turns out to be not very satisfactory, is for the CPU to keep checking for incoming data
over and over. Whenever it finds data, it processes it. This method is called polling , since
the CPU polls the input devices continually to see whether they have any input data to report.
Unfortunately, although polling is very simple, it is also very inefficient. The CPU can waste
an awful lot of time just waiting for input.
To avoid this inefficiency, interrupts are often used instead of polling. An interrupt is
a signal sent by another device to the CPU. The CPU responds to an interrupt signal by
putting aside whatever it is doing in order to respond to the interrupt. Once it has handled
the interrupt, it returns to what it was doing before the interrupt occurred. For example, when
you press a key on your computer keyboard, a keyboard interrupt is sent to the CPU. The
CPU responds to this signal by interrupting what it is doing, reading the key that you pressed,
processing it, and then returning to the task it was performing before you pressed the key.
Again, you should understand that this is a purely mechanical process: A device signals an
interrupt simply by turning on a wire. The CPU is built so that when that wire is turned on,
the CPU saves enough information about what it is currently doing so that it can return to
the same state later. This information consists of the contents of important internal registers
such as the program counter. Then the CPU jumps to some predetermined memory location
and begins executing the instructions stored there. Those instructions make up an interrupt
handler that does the processing necessary to respond to the interrupt. (This interrupt handler
is part of the device driver software for the device that signalled the interrupt.) At the end of
1.2. ASYNCHRONOUS EVENTS 5
the interrupt handler is an instruction that tells the CPU to jump back to what it was doing;
it does that by restoring its previously saved state.
Interrupts allow the CPU to deal with asynchronous events. In the regular fetch-and-
execute cycle, things happen in a predetermined order; everything that happens is “synchro-
nized” with everything else. Interrupts make it possible for the CPU to deal efficiently with
events that happen “asynchronously,” that is, at unpredictable times.
As another example of how interrupts are used, consider what happens when the CPU needs
to access data that is stored on the hard disk. The CPU can access data directly only if it is
in main memory. Data on the disk has to be copied into memory before it can be accessed.
Unfortunately, on the scale of speed at which the CPU operates, the disk drive is extremely
slow. When the CPU needs data from the disk, it sends a signal to the disk drive telling it
to locate the data and get it ready. (This signal is sent synchronously, under the control of a
regular program.) Then, instead of just waiting the long and unpredictalble amount of time
that the disk drive will take to do this, the CPU goes on with some other task. When the disk
drive has the data ready, it sends an interrupt signal to the CPU. The interrupt handler can
then read the requested data.
∗ ∗ ∗
Now, you might have noticed that all this only makes sense if the CPU actually has several
tasks to perform. If it has nothing better to do, it might as well spend its time polling for input
or waiting for disk drive operations to complete. All modern computers use multitasking to
perform several tasks at once. Some computers can be used by several people at once. Since the
CPU is so fast, it can quickly switch its attention from one user to another, devoting a fraction
of a second to each user in turn. This application of multitasking is called timesharing . But a
modern personal computer with just a single user also uses multitasking. For example, the user
might be typing a paper while a clock is continuously displaying the time and a file is being
downloaded over the network.
Each of the individual tasks that the CPU is working on is called a thread . (Or a process;
there are technical differences between threads and processes, but they are not important here.)
At any given time, only one thread can actually be executed by a CPU. The CPU will continue
running the same thread until one of several things happens:
• The thread might voluntarily yield control, to give other threads a chance to run.
• The thread might have to wait for some asynchronous event to occur. For example, the
thread might request some data from the disk drive, or it might wait for the user to press
a key. While it is waiting, the thread is said to be blocked , and other threads have a
chance to run. When the event occurs, an interrupt will “wake up” the thread so that it
can continue running.
• The thread might use up its allotted slice of time and be suspended to allow other threads
to run. Not all computers can “forcibly” suspend a thread in this way; those that can
are said to use preemptive multitasking . To do preemptive multitasking, a computer
needs a special timer device that generates an interrupt at regular intervals, such as 100
times per second. When a timer interrupt occurs, the CPU has a chance to switch from
one thread to another, whether the thread that is currently running likes it or not.
Ordinary users, and indeed ordinary programmers, have no need to deal with interrupts and
interrupt handlers. They can concentrate on the different tasks or threads that they want the
computer to perform; the details of how the computer manages to get all those tasks done are
not important to them. In fact, most users, and many programmers, can ignore threads and
6 CHAPTER 1. THE MENTAL LANDSCAPE
J a v a I n t e r p r e t e r
f
o r M a c O S
J a v a
J a v a J a v a I n t e r p r e t e r
o m p i l e r B y t e c o d e
f
P r o g r a m C o r W i n d o w s
P r o g r a m
J a v a I n t e r p r e t e r
f
o r L i n u x
Why, you might wonder, use the intermediate Java bytecode at all? Why not just distribute
the original Java program and let each person compile it into the machine language of whatever
computer they want to run it on? There are many reasons. First of all, a compiler has to
understand Java, a complex high-level language. The compiler is itself a complex program. A
Java bytecode interpreter, on the other hand, is a fairly small, simple program. This makes it
easy to write a bytecode interpreter for a new type of computer; once that is done, that computer
can run any compiled Java program. It would be much harder to write a Java compiler for the
same computer.
Furthermore, many Java programs are meant to be downloaded over a network. This leads
to obvious security concerns: you don’t want to download and run a program that will damage
your computer or your files. The bytecode interpreter acts as a buffer between you and the
program you download. You are really running the interpreter, which runs the downloaded
program indirectly. The interpreter can protect you from potentially dangerous actions on the
part of that program.
I should note that there is no necessary connection between Java and Java bytecode. A pro-
gram written in Java could certainly be compiled into the machine language of a real computer.
And programs written in other languages could be compiled into Java bytecode. However, it is
the combination of Java and Java bytecode that is platform-independent, secure, and network-
compatible while allowing you to program in a modern high-level object-oriented language.
8 CHAPTER 1. THE MENTAL LANDSCAPE
∗ ∗ ∗
I should also note that the really hard part of platform-independence is providing a “Graph-
ical User Interface”—with windows, buttons, etc.—that will work on all the platforms that
support Java. You’ll see more about this problem in Section 1.6.
interest should be computed by multiplying the principal by 0.04. A program needs some
way of expressing this type of decision. In Java, it could be expressed using the following “if
statement”:
if (principal > 10000)
interest = principal * 0.05;
else
interest = principal * 0.04;
(Don’t worry about the details for now. Just remember that the computer can test a condition
and decide what to do next on the basis of that test.)
Loops are used when the same task has to be performed more than once. For example,
if you want to print out a mailing label for each name on a mailing list, you might say, “Get
the first name and address and print the label; get the second name and address and print
the label; get the third name and address and print the label—” But this quickly becomes
ridiculous—and might not work at all if you don’t know in advance how many names there are.
What you would like to say is something like “While there are more names to process, get the
next name and address, and print the label.” A loop can be used in a program to express such
repetition.
∗ ∗ ∗
Large programs are so complex that it would be almost impossible to write them if there
were not some way to break them up into manageable “chunks.” Subroutines provide one way to
do this. A subroutine consists of the instructions for performing some task, grouped together
as a unit and given a name. That name can then be used as a substitute for the whole set of
instructions. For example, suppose that one of the tasks that your program needs to perform
is to draw a house on the screen. You can take the necessary instructions, make them into
a subroutine, and give that subroutine some appropriate name—say, “drawHouse()”. Then
anyplace in your program where you need to draw a house, you can do so with the single
command:
drawHouse();
This will have the same effect as repeating all the house-drawing instructions in each place.
The advantage here is not just that you save typing. Organizing your program into sub-
routines also helps you organize your thinking and your program design effort. While writing
the house-drawing subroutine, you can concentrate on the problem of drawing a house without
worrying for the moment about the rest of the program. And once the subroutine is written,
you can forget about the details of drawing houses—that problem is solved, since you have a
subroutine to do it for you. A subroutine becomes just like a built-in part of the language which
you can use without thinking about the details of what goes on “inside” the subroutine.
∗ ∗ ∗
Variables, types, loops, branches, and subroutines are the basis of what might be called
“traditional programming.” However, as programs become larger, additional structure is needed
to help deal with their complexity. One of the most effective tools that has been found is object-
oriented programming, which is discussed in the next section.
the construction of correct, working, well-written programs. The software engineer tends to
use accepted and proven methods for analyzing the problem to be solved and for designing a
program to solve that problem.
During the 1970s and into the 80s, the primary software engineering methodology was
structured programming . The structured programming approach to program design was
based on the following advice: To solve a large problem, break the problem into several pieces
and work on each piece separately; to solve each piece, treat it as a new problem which can itself
be broken down into smaller problems; eventually, you will work your way down to problems
that can be solved directly, without further decomposition. This approach is called top-down
programming .
There is nothing wrong with top-down programming. It is a valuable and often-used ap-
proach to problem-solving. However, it is incomplete. For one thing, it deals almost entirely
with producing the instructions necessary to solve a problem. But as time went on, people
realized that the design of the data structures for a program was as least as important as the
design of subroutines and control structures. Top-down programming doesn’t give adequate
consideration to the data that the program manipulates.
Another problem with strict top-down programming is that it makes it difficult to reuse
work done for other projects. By starting with a particular problem and subdividing it into
convenient pieces, top-down programming tends to produce a design that is unique to that
problem. It is unlikely that you will be able to take a large chunk of programming from another
program and fit it into your project, at least not without extensive modification. Producing
high-quality programs is difficult and expensive, so programmers and the people who employ
them are always eager to reuse past work.
∗ ∗ ∗
So, in practice, top-down design is often combined with bottom-up design. In bottom-up
design, the approach is to start “at the bottom,” with problems that you already know how to
solve (and for which you might already have a reusable software component at hand). From
there, you can work upwards towards a solution to the overall problem.
The reusable components should be as “modular” as possible. A module is a component of a
larger system that interacts with the rest of the system in a simple, well-defined, straightforward
manner. The idea is that a module can be “plugged into” a system. The details of what goes on
inside the module are not important to the system as a whole, as long as the module fulfills its
assigned role correctly. This is called information hiding , and it is one of the most important
principles of software engineering.
One common format for software modules is to contain some data, along with some sub-
routines for manipulating that data. For example, a mailing-list module might contain a list of
names and addresses along with a subroutine for adding a new name, a subroutine for printing
mailing labels, and so forth. In such modules, the data itself is often hidden inside the module;
a program that uses the module can then manipulate the data only indirectly, by calling the
subroutines provided by the module. This protects the data, since it can only be manipulated
in known, well-defined ways. And it makes it easier for programs to use the module, since they
don’t have to worry about the details of how the data is represented. Information about the
representation of the data is hidden.
Modules that could support this kind of information-hiding became common in program-
ming languages in the early 1980s. Since then, a more advanced form of the same idea has
more or less taken over software engineering. This latest approach is called object-oriented
programming , often abbreviated as OOP.
1.5. OBJECT-ORIENTED PROGRAMMING 11
The central concept of object-oriented programming is the object, which is a kind of module
containing data and subroutines. The point-of-view in OOP is that an object is a kind of self-
sufficient entity that has an internal state (the data it contains) and that can respond to
messages (calls to its subroutines). A mailing list object, for example, has a state consisting
of a list of names and addresses. If you send it a message telling it to add a name, it will
respond by modifying its state to reflect the change. If you send it a message telling it to print
itself, it will respond by printing out its list of names and addresses.
The OOP approach to software engineering is to start by identifying the objects involved in
a problem and the messages that those objects should respond to. The program that results is
a collection of objects, each with its own data and its own set of responsibilities. The objects
interact by sending messages to each other. There is not much “top-down” in such a program,
and people used to more traditional programs can have a hard time getting used to OOP.
However, people who use OOP would claim that object-oriented programs tend to be better
models of the way the world itself works, and that they are therefore easier to write, easier to
understand, and more likely to be correct.
∗ ∗ ∗
You should think of objects as “knowing” how to respond to certain messages. Different
objects might respond to the same message in different ways. For example, a “print” message
would produce very different results, depending on the object it is sent to. This property of
objects—that different objects can respond to the same message in different ways—is called
polymorphism .
It is common for objects to bear a kind of “family resemblance” to one another. Objects
that contain the same type of data and that respond to the same messages in the same way
belong to the same class. (In actual programming, the class is primary; that is, a class is
created and then one or more objects are created using that class as a template.) But objects
can be similar without being in exactly the same class.
For example, consider a drawing program that lets the user draw lines, rectangles, ovals,
polygons, and curves on the screen. In the program, each visible object on the screen could be
represented by a software object in the program. There would be five classes of objects in the
program, one for each type of visible object that can be drawn. All the lines would belong to
one class, all the rectangles to another class, and so on. These classes are obviously related;
all of them represent “drawable objects.” They would, for example, all presumably be able to
respond to a “draw yourself” message. Another level of grouping, based on the data needed
to represent each type of object, is less obvious, but would be very useful in a program: We
can group polygons and curves together as “multipoint objects,” while lines, rectangles, and
ovals are “two-point objects.” (A line is determined by its endpoints, a rectangle by two of its
corners, and an oval by two corners of the rectangle that contains it.) We could diagram these
relationships as follows:
12 CHAPTER 1. THE MENTAL LANDSCAPE
D r a w a b l e O b j e c t
M u l t i p o i n t O b j e c t T w o P o i n t O b j e c t
P o l y g o n C u r v e L e c t a n g l e O v a l
i n e R
and Linux. Java programs, which are supposed to run on many different platforms without
modification to the program, can use all the standard GUI components. They might vary a
little in appearance from platform to platform, but their functionality should be identical on
any computer on which the program runs.
Shown below is an image of a very simple Java program—actually an “applet”, since it is
meant to appear on a Web page—that shows a few standard GUI interface components. There
are four components that the user can interact with: a button, a checkbox, a text field, and a
pop-up menu. These components are labeled. There are a few other components in the applet.
The labels themselves are components (even though you can’t interact with them). The right
half of the applet is a text area component, which can display multiple lines of text, and a
scrollbar component appears alongside the text area when the number of lines of text becomes
larger than will fit in the text area. And in fact, in Java terminology, the whole applet is itself
considered to be a “component.”
Now, Java actually has two complete sets of GUI components. One of these, the AWT or
Abstract Windowing Toolkit, was available in the original version of Java. The other, which
is known as Swing , is included in Java version 1.2 or later, and is used in preference to the
AWT in most modern Java programs. The applet that is shown above uses components that
are part of Swing. If your Web browser uses an old version of Java, you might get an error
when the browser tries to load the applet. Remember that most of the applets in this textbook
require Java 5.0 (or higher).
When a user interacts with the GUI components in this applet, an “event” is generated.
For example, clicking a push button generates an event, and pressing return while typing in a
text field generates an event. Each time an event is generated, a message is sent to the applet
telling it that the event has occurred, and the applet responds according to its program. In
fact, the program consists mainly of “event handlers” that tell the applet how to respond to
various types of events. In this example, the applet has been programmed to respond to each
event by displaying a message in the text area.
The use of the term “message” here is deliberate. Messages, as you saw in the previous sec-
tion, are sent to objects. In fact, Java GUI components are implemented as objects. Java
includes many predefined classes that represent various types of GUI components. Some of
these classes are subclasses of others. Here is a diagram showing some of Swing’s GUI classes
and their relationships: