Uploaded by malay0204

Introduction to Computer Science

advertisement
1 COMPUTING FUNDAMENTALS
CHAPTER OBJECTIVES
·
·
·
·
·
·
·
·
·
·
Describe the early history of computers and computing.
Identify the four generations of computer hardware and the
technology behind them.
Identify the four categories of computers.
Explain the function of the five basic components of computer
hardware.
Recognize that information is stored in binary form in computer
memory.
Differentiate between system and application software.
Cite the seven phases of software development.
Identify three generations of programming languages.
Explain the process by which a high-level language program is
executed.
Describe the program preparation cycle.
This book is an introduction to Computer Science. Computer Science is
the study of computer hardware, algorithms and data structures and
how they fit together to provide information systems. Each of these
topics can be studied at various levels. For example, physicists study the
properties of matter that allow hardware components to be designed,
electrical engineers study how the components can be combined to
produce circuits and computer engineers study how circuits can be
combined to produce computers. Most Computer Scientists do not need a
detailed understanding of the properties of matter, circuit design or
computer design, but rather a basic understanding of how the hardware
operates with respect to the design of algorithms.
The algorithm—a clearly defined sequence of steps to achieve some
goal—is a key programming concept covered throughout this book.
During your career as a Computer Science student, you will be
introduced to the three main areas of Computer Science at a variety of
levels. In this book, we will briefly consider computer hardware from a
functional viewpoint, and then introduce algorithms and programming.
This will only be an introduction, there is much more to learn! In fact,
you will go on learning for the rest of your career as a Computer
Scientist. Computer Science is probably the most quickly changing of all
subjects. Computers, programming languages and even computing
concepts of twenty, ten or even five years ago are rapidly replaced by
new, improved versions.
This chapter will serve as an introduction to Computer Science with a
brief history of the discipline, an introduction to the functional
components of a computer and an introduction to the program
development process. In subsequent chapters you will be introduced to
computer programming in the Java programming language as a
foundation upon which to build a Computer Science career.
When discussing programming, we need a language in which to express
the algorithms. The most convenient means is to use an actual
programming language. Each language has its own drawbacks. It may be
that the language will be out of date in industry in a few years’ time, or
the language may not support all concepts that should be discussed. We
have to live with these drawbacks. Java is the language we have chosen
for this book. It is a relatively new language that is object-oriented. It
supports most of the concepts currently viewed as leading to good
programming style without having many of the inconsistencies of
languages such as C++ or the complexities of Eiffel or Smalltalk. Even if
you go on to program in another language, the Java concepts are
transferable, even if the specific notation is not. In this text we are really
discussing the concepts and using Java as a medium to discuss them.
A computer is a special kind of machine. Unlike machines of the past like
a circular saw or an automobile which could do only one task—cut wood
or deliver people and goods from point A to B—computers are able to
perform a wide variety of different tasks. Computers are
programmable; they can be instructed to do a variety of different
things. The program applies the computer to a particular task. Instead of
working on physical materials, computers work on data—facts, figures
and ideas. Computers synthesize these data into information—reports,
summaries and animations. Computers are therefore information
processing machines, and the computer programs are information
processing systems.
1.1
A BRIEF HISTORY OF COMPUTING
Computers, as we know them, are a modern development, evolving from
the 1940s to the present day. However, humankind has had to perform
calculations since the dawn of civilization.
FROM COUNTING TO COMPUTING
Counting was first needed to determine the size of wild herds or the
number of domesticated animals. Then a notation for numbers was
developed to record this information. Finally, arithmetic was developed
for people to be able to divide resources among several individuals. Here
was the dawn of algorithms. Arithmetic methods such as long division
are clearly algorithms.
As civilization evolved and humankind had the luxury of academic
pursuit, some philosophers (as they were then called) studied arithmetic
processes. Euclid is credited with the first written algorithm—his
description of how to find the greatest common divisor of two integers.
An Arab philosopher named Mohammed ibn Musa Al-Kowarizmi (ca.
850) wrote at length about arithmetic processes and lent his name to the
subject, algorithm.
Calculation by hand was, of course, tedious and error prone. One early
device to aid in calculation was the abacus, which has long been used in
China (ca. 1300). A wooden frame with rods strung with beads which
could be moved up and down, the abacus could be used to perform
complex calculations. In essence, it was the first hand-held calculator.
However, the user performed the actual arithmetic algorithm.
In 1617, the English mathematician John Napier developed a tool (called
Napier’s bones) based on logarithmic tables, which allowed the user to
multiply and divide easily. This evolved into the slide rule (Edmund
Gunther, 1621), which was the mainstay of scientists and engineers until
the more recent development of the hand-held calculator. Blaise Pascal
(after whom the programming language Pascal is named) developed a
fully mechanical adding machine in 1642. The user didn’t have to
perform the algorithm, the machine did it all. The mechanization of
computation had begun.
Still, with one exception, all of the computation devices developed over
the next two or three hundred years were just simple machines, not
computers.
The one exception was the design of the Analytical Engine by Charles
Babbage in the 1840s. Babbage was a mathematician and inventor who
was very interested in automating calculations. He had partially
developed a machine called the Difference Engine (1822–42) which
would be able to automatically calculate difference tables (important for
preparing trajectory tables for artillery pieces) under contract to the
British Government. He had much grander plans, however, a machine
that could do any calculation required—the Analytical Engine. This
machine was the mechanical forerunner of modern computers. Just like
computers of today, there was a means of entering data (input) and
receiving results (output) via dials, a place to store intermediate results
(memory), an arithmetic mill (the part that did the computations, what
we call the processor) and a mechanism for programming the machine.
The program instructions were punched as holes into wooden cards (an
idea borrowed from the automated weaving loom previously developed
by Jacquard, 1804–6).
Unfortunately, Babbage was a perfectionist and a bit of an eccentric.
Between the inability of the manufacturing process of the day to mill
parts with the required tolerances, Babbage’s tendency to go on to new
ideas rather than complete what he started, and his inability to get along
with the government officials for whom he was developing the Analytical
Engine, the Analytical Engine was never completely built. However, for
the 200th anniversary of his birth, a replica of the analytical engine was
built and is currently in the Science Museum in London, England.
Ada Augusta King, the Countess of Lovelace and daughter of the poet
Lord Byron, was an amateur mathematician and avid handicapper of
horses. She was introduced to Babbage by her mother and became quite
interested in the practical use of the Analytical Engine. She wrote
programs for the Analytical Engine and is regarded as the first
programmer. The programming language Ada is named in her honor.
THE MODERN ERA
For a machine to be considered a computer, it must be programmable!
The stored program concept, as defined by the mathematician John
von Neumann (1945) is now considered essential to the notion of a
computer. That is, a computer must have a memory in which instructions
are stored and which can be modified by a program itself. Babbage’s
Analytical Engine fulfilled this criterion.
The modern age of electronic computers really begins in the 1940s (with
a push from the war effort), although credit for the first electronic
computer is not clear. Throughout the 1940s several electronic
computing devices were developed but none was fully electronic and
programmable.
One development, of which we have little information since much was
lost after the end of the war, was the work in Germany by Konrad Zuse
on a series of computing devices culminating in the Z3 (about 1941).
Reportedly, this was electronic and programmable. Zuse also developed
a notation for programs called Plankalkül (1945), which is regarded as
the first programming language.
GENERATIONS OF COMPUTERS
The basic components of an electronic computer are electronic switches.
Computers can be classified into generations based on the technology
used for these switches. The older electro-mechanical computers used
relays, but the first electronic computers (first generation, 1944–58)
used vacuum tubes. A vacuum tube is an evacuated tube of glass that
can be used as an electronic switch. Today we don’t see vacuum tubes
very often except as the picture tube of old televisions and computer
monitors.
The second generation (1959–63) of computers began with the
development of the transistor. A transistor is a solid state device that
functions as an electronic switch. Because transistors are small and can
last indefinitely, this meant that second generation computers were
much smaller and more reliable than first generation computers.
The development of the integrated circuit brought about the third
generation (1964–70) of computers. Essentially, an integrated circuit
is a solid-state device on which an entire circuit—transistors and the
connections between them—can be created (etched). This meant that a
single integrated circuit chip, not much bigger than early transistors,
could replace entire circuit boards containing many transistors, again
reducing the size of computers.
From here, the evolution of computing technology has been ever
increasing miniaturization of the electronic circuitry. The fourth
generation (1971–) is typically considered to be VLSI (very large scale
integration). Currently it is possible to place many millions of transistors
and the accompanying circuitry on a single integrated circuit chip.
By the mid-’70s, it was possible to put the complete circuitry for the
processor of a simple computer on a single chip (called a
microprocessor) and the microcomputer was born. In 1977, a small
garage-based company called Apple Computer marketed the first
commercial personal computer (PC)—the Apple II. In 1981, IBM
released its version of a PC, expecting to sell a few thousand, worldwide.
They didn’t want to have the hassle of maintaining an operating system,
so they sold the code to Bill Gates (a small-time software developer), and
Microsoft was born. In 1984, Apple released the “computer for the rest of
us”, the Macintosh, designed to be easy enough to use that it could be
used by people without special training. Based on the research done at
Xerox’s Palo Alto Research Center, the Macintosh was the first
commercial computer to use a mouse and a graphical user interface
(GUI). The modern era of computers had arrived.
1.2
COMPUTER SYSTEMS
A computing system consists of user(s), software, procedures, hardware,
and data that work together to produce an outcome. The user is the
individual that will be using the system to produce a result such as a
written report or calculation. Typically this is not someone trained in
Computer Science; however s/he most likely is trained in computer use.
The software are the computer programs (algorithms expressed in a
computer language) that allow the computer to be applied to a particular
task. The procedures are the steps that the user must follow to use the
software. This is usually described in the documentation (either a
printed book or on-line documentation that is read on the computer).
The hardware is the physical computer itself. Finally, the data are the
facts, figures, ideas etc. that the program will process to produce the
desired information.
In this book our focus is on software, that is, with programming.
However, we need to have a general understanding of the hardware of a
computer to be able to write software.
COMPUTER HARDWARE
There is a variety of different kinds of computers used for different
purposes. Typically computers are divided into categories based on their
power (i.e. how fast they can do computations), physical size and cost.
Four categories are usually described as:
Tablet—small, light, hand-held, with touch screen and/or stylus,
lower power, $$$
Laptop—small, portable, typically with full-sized keyboard and
screen, mid-power, $$$$
Desktop—larger, with larger separate screen and keyboard,
powerful, $$
Server—larger individual or cluster of machines, high power, $$$$$
The division into the four categories is somewhat subjective and the
categories overlap. Tablets and laptops increasingly challenge desktops
for speed and power, albeit at a higher purchase price. Intelligent devices
such as smartphones are essentially computers and are sometimes the
equal of a tablet introducing the term “phablet” to describe both. Most
computing is now done on desktop and laptop machines with tablets and
phones used to obtain web services. Servers which have many
processors and large numbers of network connections provide webbased services such as electronic banking, e-commerce and data storage
and compute services (the cloud).
Regardless of the size, power or category, however, computers work
essentially in the same way and are made up of the same general
components: central processing unit, main memory, input devices,
output devices and auxiliary storage (see figure 1.1).
Figure 1.1 Hardware Components
The heart (or brains) of the computer is the central processing unit
(CPU). The CPU contains the circuitry that allows the computer to do the
calculations and follow the instructions of the program. The CPU is
divided into two main parts: the control unit and the arithmetic/logic
unit. The control unit (CU) controls the components of the computer
and follows the instructions of the program. The arithmetic/logic unit
(ALU) performs the arithmetic (e.g. addition) and logical (e.g.
comparison of numbers) functions of the computer. A microprocessor
has the entire CPU on a single chip.
The main memory (or RAM—random access memory) is the place
where the computer remembers things. The data being processed, the
results or produced and the program instructions themselves must be
present in memory while they are being used. When power to the
computer is lost, the contents of memory cannot be relied upon. We
therefore say that main memory is volatile. This means that main
memory can only be used for short-term storage.
Input devices are the components that the computer uses to access data
that is present outside the computer system. Input devices convert the
data coming from the real world into a form that the computer can
process. Examples of input devices are keyboards, scanners, touch
screens, and sensors.
Output devices are the components that present results from the
computer to the outside environment. They convert the computer
representation to the real-world representation. Examples of output
devices include monitors, printers, and audio output.
Since it is necessary to store programs and data for long periods of time
and main memory is volatile, we need some form of long-term (nonvolatile) memory. These are the auxiliary storage devices. They include
disk, CD, DVD and flash drives.
Although not traditionally considered one of the basic hardware
components, communications devices are common on allmost all
computer systems today. Computer systems must be able to
communicate with other computers to exchange information.
Communications devices unite computers into networks (including the
Internet). This is the way that applications such as web-browsing and
electronic mail are provided. A common communications device on a
microcomputer is a cable or digital modem, which allow cable television
or telephone lines to be used for computer communication. Wireless
network communication is also supported by the cellular phone system
and wifi.
*1.3
DATA REPRESENTATION
We have seen that computer hardware is made up of basic components
that are essentially electronic switches. A switch is called a bi-stable
device because it has two states: open (no current flowing) or closed
(current flowing). Since memory is comprised of these switches, data in
memory must be represented in terms of two states. In Mathematics, the
number system that has only two digits is called the binary (or base
two) number system. The two digits are 0 and 1. This corresponds to
the situation in computer memory, so computers have adopted the
binary number system as their basic representation.
The binary number system is similar to our common decimal (base ten)
number system, in that it is a positional number system. In a positional
number system, a number is written as a sequence of digits (0 through 9
for base ten), with digits in different positions having different values.
For example the decimal number:
represents the number composed of 1 hundreds, 0 tens and 7 ones or
one hundred and seven. The digits (starting at the decimal point and
moving left) represent ones (i.e. 100), tens (101), hundreds (102),
thousands (103) etc. Note that these are the powers of the base, 10. A
binary number works in the same way, except the digits are restricted to
0 and 1 and the base is 2. Thus the binary number:
represents 1 sixty-four (i.e. 26), 1 thirty-two (25), 0 sixteens (24), 1
eight (23), 0 fours (22), 1 two (21) and 1 one (20) or also one hundred
and seven.
To distinguish the binary digits (0 and 1) from the decimal digits (0
through 9), we give them the name bit (binary digit). Thus each switch
in computer memory represents one bit. To represent information, bits
are grouped together. A single bit can represent two possible distinct
values (0 and 1), two bits together represent four possibilities (00, 01,
10, 11). In general, a group of n bits can represent 2n possibilities as
summarized in Table 1.1.
Table 1.1 Powers of 2
A group of eight bits is called a byte, and is the basic unit of storage on
computers. Memory itself is usually measured in megabytes (million
[1]
bytes , MB), or gigabytes (one thousand megabytes). We can think of
memory as a set of boxes or cells, each of which can hold some data. To
distinguish one box from another, the boxes are labeled with (binary)
numbers called addresses (much as houses on a street). When the
program needs to remember a value for future use, it stores (places) the
value in a cell at a particular address. Figure 1.2 shows a model of
memory. The addresses label each cell. The number 27 (here written in
decimal since binary numbers get very long) has been stored at address
0010. Later the program may recall the value by reading the value from
the cell with the given address. Only one value can reside in a cell at any
one time. Reading a value doesn’t change what is in the cell, whereas
writing (storing) replaces the old value with a new one, rendering the old
value lost.
Figure 1.2 Memory Model
Ultimately, every kind of data that a computer processes must be
represented as a sequence of bits. To make it convenient to process
information, the same number of bits is used for the values of any one
kind. For example, in Java integral values (i.e., numbers without
fractions) are represented using 32 bits (see chapter 4). Numbers are
represented naturally in base two. Text characters are assigned binary
numbers according to a coding scheme and typically are represented one
byte (8 bits) per character (although Java uses 2 bytes). Other kinds of
information must be coded somehow as sequences of binary digits in a
process called digitization. For example, music can be coded as a
sequence of binary numbers each representing the height of the sound
wave measured at particular sampling intervals. This is the way music is
stored on audio CDs.
1.4
COMPUTER SOFTWARE
Software is often divided into two categories: system and application.
System software are software that manage the computer system and
consists primarily of the operating system as in Windows 7. Application
software are programs like Word 2007 that allow the computer to be
applied to a specific task such as word processing.
SYSTEM SOFTWARE
The operating system (OS) is a set of programs that manage the
resources of the computer. When the computer is first turned on, it is the
operating system that gets things started and presents the user interface
that allows the user to choose what s/he wishes to do. The control unit
starts fetching instructions from a special kind of memory called readonly memory (ROM). This memory is non-volatile and comes from the
manufacturer loaded with a program called the bootstrap loader. This
is a simple program that starts loading the operating system from the
hard disk into RAM and then instructs the control unit to start fetching
instructions of the operating system.
The operating system then checks out the system to make sure all
components are functioning correctly and presents the user interface.
This interface is the so-called desktop, which mimics an office desktop
and consists of pictures called icons that symbolize the hard drive, file
folders, and programs themselves. When the user indicates that s/he
wishes to do word processing, the operating system loads the designated
program into memory and then instructs the control unit to fetch
instructions from it.
The operating system typically assists the application programs in doing
common tasks such as reading from disk or drawing on the screen. It also
keeps track where files are located on the disk and handles creation and
deletion of files. When the user asks a word processing program such as
Word to open a file, Word, in turn, asks the operating system to locate
the file and load it into memory. When the user is editing the file, Word is
simply modifying the copy in memory. This is why, if you don’t save the
file and your computer crashes or there is a power failure, you lose what
you have done. Finally, when the user asks Word to save the file, Word
requests this operation of the operating system. When the user quits
Word, it instructs the control unit to continue fetching instructions from
the operating system, which can then go on to a different task. When the
user shuts down the computer, the operating system makes sure
everything that must be remembered is written to disk and then shuts
down.
APPLICATION SOFTWARE
Application programs work with the operating system to apply the
computer to specific tasks. The kinds of application programs available
are only limited by programmers’ imaginations and, of course, market
conditions. We have already mentioned one of the most common
application programs—word processing programs such as Microsoft
Word. These are designed primarily for creating text documents. Other
applications include spreadsheets (Microsoft Excel), for doing
numerical calculations such as tracking sales and database systems
(such as Microsoft Access or Oracle), for keeping track of interrelated
data such as student registration and mark information at a university.
Although complex in their own right, application programs require the
user to have little knowledge of Computer Science. Rather the user must
have significant domain knowledge—knowledge of the area in which
the program is applied.
SOFTWARE DEVELOPMENT ENVIRONMENTS
There is one kind of program that doesn’t fit well in the above categories.
These are software development environments—the programs that
are used by programmers to write other programs. From one point of
view, these are application programs because they apply the computer to
the task of writing computer software. On the other hand, the users are
Computer Scientists and the programming task is not the end in itself,
but rather a means to apply the computer to other tasks. Often software
development environments are grouped under the category of systems
software. We will talk more about software development environments
later in this chapter when we talk about program preparation.
1.5
SOFTWARE DEVELOPMENT
Development of software (sometimes called Software Engineering)
involves the analysis of a problem and the design and development of a
computer program to apply the computer to that problem. In this section
we overview the process so we can begin developing simple programs.
As discussed earlier, a computer program is an algorithm expressed in a
special notation called a programming language and an algorithm is a
sequence of steps to achieve a specific task. To be effective an algorithm
must cover all possibilities that might occur. It must be expressed
unambiguously so it is clear what must be done. The process must also
terminate, that is it cannot go on forever. When we develop programs,
we must keep these requirements in mind.
SOFTWARE ENGINEERING
Development of large-scale software is a very complex task typically
carried out by a team of software development professionals. Although
there are a number of different methodologies for software
development, they share common phases: analysis, design, coding,
testing, debugging, production and maintenance.
Before a software system can be developed, what is required must be
clearly understood. This is the task of the analysis phase: to develop a
requirements specification that clearly indicates what is (and sometimes
what is not) required of the system. Although senior team members
typically perform analysis, even in our early stages of learning Computer
Science it will be important to be clear about what is to be done. Even if
we develop a fabulous system, if it is not what was required it was a
wasted effort.
Design is the determination of an approach to solving the problem.
Again, this is typically done by senior team members and involves
dividing the problem up into a number of pieces that will be developed
by individual team members. Even when we are developing small
programs it will be important to decide on an approach and to break the
task up into smaller, easily manageable tasks to allow us to come to a
solution in reasonable time.
Coding is the actual expression of an algorithm in a programming
language. Here the programmers (now including the more junior team
members) tackle the individual pieces of the problem as set out in the
design and develop a solution. We will spend most of our time discussing
this phase; it is necessary if we are going to carry out any of the others,
so we learn it first.
When a system has been developed, we want it to perform as specified in
the analysis. How do we know it will? This is the responsibility of testing
(one of the most overlooked phases of development, just consider some
of the software you have used). Each part of the system, starting with the
individual pieces developed by the programmers, must be tested to see
that it functions according to the design. The pieces are then combined to
build up the system, which must ultimately be tested to see that it
conforms to the requirements specification. Whenever we develop a
program—even if it is a simple program as an assignment in our first
programming course—we must test the program to ensure it does what
is required.
Unfortunately since we are all human, programs don’t usually perform as
they are required on the first try. This is where debugging comes in.
When it is determined that the program doesn’t do what was expected,
we must correct the problem. The problem can arise from a number of
sources including: not really understanding what is to be done, not fully
understanding the details of some feature of a programming language or
an invalid assumption or oversight in our development of the algorithm.
Careful design of the tests that we use in testing can help us pinpoint the
error and ultimately correct it.
Finally, the system does what it is intended to do (or at least we are
convinced it does). Now the system is released to the people who are
expected to use it (the users). This phase is called production.
But it doesn’t end here! Even the most carefully designed and tested
software will contain undetected errors (bugs). User’s requirements
change. A system has to be made available on new hardware and
operating systems. The phase in which the system is re-analyzed, redesigned, re-coded, etc., resulting in a new version of the system is called
maintenance. Typically, this phase is much longer that the phases
leading up to it so it is very important to perform the earlier phases with
this in mind.
Although we will not study the software development process in detail in
this book, the requirements of these phases will guide our steps in
program development. Before we begin writing any program, we will try
to have a clear understanding of what is required and a plan of how to
approach the problem (analysis and design). We will look at techniques
for determining exactly what it is our program is doing (wrong) as we
look at methods in Chapter 3 and control structures in Chapter 6. This is
the start of debugging. We will consider the types of inputs to use in
testing our programs when we introduce input and output in Chapter 9.
Throughout, we will consider ways of making our programs easier to
understand and thus maintain through the use of naming and
documentation conventions. Through a disciplined approach, we will
learn that complex software can be developed in reasonable time and
with a minimum of undetected bugs—the primary goals of all software
developers.
PROGRAMMING LANGUAGES
We generally use natural language such as English to express algorithms
to other people. But English statements are often ambiguous and rely
upon the listener’s common sense and world knowledge. Since
computers have no common sense, it is necessary to be unambiguous.
For that reason natural languages are not used for programming but
rather specially designed computer programming languages are used
instead.
GENERATIONS OF LANGUAGES.
Like computers themselves,
computer programming languages have evolved through a number of
generations. At the beginning, programmers wrote their programs in
machine language and each operation was written as a separate
instruction as a sequence of binary digits. These early languages are
known as the first generation languages.
But writing long series of 0s and 1s was, at best, tedious. It was decided
that the computer itself could help things if a program could be written
that would automatically convert an algorithm written in a symbolic
notation into machine language. Each operation (opcode) was given a
name and the operands (addresses) were expressed as a combination of
names and simple arithmetic operations. These second-generation
languages were called assembly languages. A portion of a program
written in assembly language is shown in Figure 1.3. Each assembly
language instruction still corresponds to one machine operation; the
difference from machine language is the use of symbols for the opcodes
and addresses.
Figure 1.3 Assembly Language
Since the computer does not understand assembly language, running the
assembly language program requires two phases: (1) translation of the
assembly program into machine language (called assembly) and then
(2) running of the resulting machine language program (called
execution).
The entire process is described in Figure 1.4. The cylinders represent
information stored on disk. The rectangles indicate a machine language
program being executed by the CPU. In the assembly phase, a program
called an assembler reads the assembly language program and produces
and stores an equivalent machine language program. In the execution
phase, the resulting machine language program is loaded into memory
and executed reading its data and producing its results. Of course, once
the program has been assembled (phase 1), it can be executed (phase 2)
any number of times. In fact, the assembler itself may have been
originally written in an assembly language and translated into machine
language by another assembler.
Figure 1.4 Executing an Assembly Language Program
Although they were a significant improvement over machine language,
assembly languages were still tedious for writing programs. Thousands
of instructions had to be written to do the simplest things. What was
needed was a more natural language. The new languages that were
designed allowed development of programs for specific application
domains such as scientific and business processing. These languages,
such as Java, Python and C++, are called problem-oriented languages
or simply high-level languages.
As programs get bigger, it is more efficient to build them up using pieces
of previously written (and previously compiled) code saved in libraries.
The program that puts the pieces together is called a linker. Again, since
the computer doesn’t understand the high-level language, a translating
program called a compiler is needed. The compiler translates (compiles)
a single high-level language instruction into many machine language
instructions.
The process of executing a high-level language program is shown in
Figure 1.5. In phase 1, the compiler compiles the source program
written in a high-level language into machine language code called
object code. In phase 2, the linker combines the object code and code
stored in libraries into executable code in machine language. Finally, in
phase 3, the resulting machine language code is executed. As for
assembly, the compile and link phases can be done once, in advance, and
then the execution phase can be repeated whenever the program is to be
run. This is exactly what happens when you execute an application
program like Word. The previously compiled and linked code is simply
loaded into memory by the operating system and executed. In fact, the
only code that is distributed is the machine language code.
Figure 1.5 Executing a High-level Language Program
As we will see in Chapter 2, execution of a Java program is a bit different
than this typical model for high-level languages. This is due to Java’s
requirement for platform independence. However, the phases of
program processing are essentially the same for Java as other languages.
FROM FORTRAN TO JAVA. Hundreds of high-level languages have
been developed since the 1950s for a variety of different application
domains. The first high-level language to have widespread use was
FORTRAN (short for formula translation system). Released in 1954 by
IBM, FORTRAN was designed for scientific (mathematical) programming
and allowed mathematical formulas to be written in a notation similar to
that used in algebra. COBOL (common business-oriented language),
developed in 1959, was designed specifically for business applications.
The 1960 definition of the language ALGOL (algorithmic language) was
the first to include a formal language specification. Pascal, developed by
N. Wirth in 1968, was designed to support teaching good programming
techniques in Computer Science. C was designed in 1972 as a systems
programming language and has become one of the most successful
programming languages. Ada was developed in 1980 for the U.S.
Department of Defense and named after Ada Augusta King, the first
programmer. Java, our language of choice, was developed in 1990 at Sun
Microsystems and has rapidly become one of the most commonly used
programming languages.
PROGRAM PREPARATION
Once an algorithm has been developed in a high-level programming
language, a number of steps must be completed to produce the desired
executable code. This is called the edit-compile-link-execute cycle,
consisting of four steps.
The first step is edit. Here the programmer uses a special program called
a program editor (similar to a word processor, but designed for
programming languages instead of natural languages) to type in, correct,
and save a source (high-level language) program.
In the compile phase a compiler is used to translate the program into
object code. Often the program hasn’t been correctly expressed and
contains errors in grammar known as syntax errors. If the compiler
detects a syntax error the programmer uses the editor to correct it and
then recompiles.
When the program is free of syntax errors, the linker is used to link the
generated object code with library code.
Once the program is successfully linked, the program is executed to test
that it does what is desired. The program may try to do things that are
unreasonable (e.g. divide a number by zero), or might execute but
produce incorrect results. These situations are called execution errors,
logic errors or bugs and must be corrected, resulting in the source
program being reedited, recompiled, relinked and finally executed again.
This cycle of edit-compile-link-execute continues until the programmer
is satisfied that the resulting code works as desired. Since most realworld programs typically are composed of many separately developed
pieces of code, the cycle begins again with another piece, and so on until
the entire software system is completed.
Today, most programmers use software development environments or
interactive development environments (IDEs) to perform the editcompile-link-execute cycle. The IDE allows the system to be developed as
a number of separately created pieces called files. When the programmer
has modified one or more pieces, the IDE determines which pieces must
be compiled and linked so that the system can be tested. The
programmer may not be aware of the complete cycle as it is occurring.
Programming is a time-consuming task that must be approached in a
careful and structured manner to be successful. The rest of this book
deals with this process.
SUMMARY
In this chapter we have seen that computers as we know them have a
brief history (from the 1940s). However algorithms and computing
devices date back to the time of the Greeks and to the 1600s,
respectively. Modern computers can be classified into four generations
based on the technology used for their primary electronic components.
Computer systems are comprised of a number of parts including
hardware and software. Although computer hardware have been
classified by size and power into categories from microcomputers to
supercomputers, the five functional hardware components are still the
same. All information in a computer is represented, in some manner,
using the binary number system. The instructions that control the
computer, represented in a binary code, are called the machine language
of the computer. Computer software is classified into system software
and application software.
Our primary emphasis in this text is on software development. Software
engineering typically involves a seven phase process, only one of which
is programming (coding). Modern computer systems are written in highlevel programming languages that must be translated into machine
language so that computers may understand the instructions. A
programmer follows a four step cycle (edit-compile-link-execute) to
proceed from concept to an executable program in machine language.
REVIEW QUESTIONS
1.
T
F Second generation computers are based on integrated
circuits.
2.
T
F A mainframe computer would likely be used for an airline
reservation system.
3.
T
F
4.
T
F Digitization is the process of encoding information into
binary.
5.
T
F
6.
T
F Domain knowledge is knowledge in the area of application
of the application software.
7.
T
F
Assembler is a first-generation language.
8.
T
F
FORTRAN is a second generation language.
9.
Which of the following is not associated with Charles Babbage?
a)
b)
c)
d)
Main memory is for long-term storage.
The bootstrap loader is stored in the CD-ROM drive.
Analytical Engine
Plankalkül
Ada Augusta King
Difference Engine
10. Which of the following is not a basic hardware component?
a)
b)
c)
d)
CU
IDE
RAM
ALU
11. The Arithmetic/Logic Unit (ALU) is responsible for
a)
b)
c)
d)
controlling the other units
doing arithmetic
decoding instructions
both a and c
12. Which of the following is not normally considered as application
software?
a)
b)
c)
d)
word processor
compiler
spreadsheet
e-mail
13. The first programming language was:
a)
b)
c)
d)
FORTRAN
BASIC
Plankalkül
Ada
14. The program that translates a high-level programming language
program into machine language is called:
a)
b)
c)
d)
an assembler
a translator
a compiler
a linker
15. The program development cycle consists of the following phases:
a)
edit, compile, link, execute
b)
c)
d)
open, edit, run, save
design, code, compile, debug
try, bomb, cry, recover
EXERCISES
1.
From your instructor or the Computing Center at your institution,
obtain documentation on the use of the computer systems in the
laboratories you will be using in this course. Learn how to obtain
access to the Internet, send and receive e-mail and how and where to
save your work (i.e. programming assignments).
2.
Using the library, the Internet and reference books, write a brief
biography of some of the following important individuals in the
history of computing:
a)
b)
c)
d)
e)
f)
g)
h)
3.
Charles Babbage
Ada Augusta King
Allan Turing
John von Neumann
John Backus
Grace Hopper
Allan Kay
James Gosling
From the box cover, reference manual or online documentation,
determine the version and release number and the hardware
requirements for one of the pieces of software (e.g. word processor,
Java compiler, Internet browser) available in the laboratory or on
your home computer.
2 JAVA PROGRAMS
CHAPTER OBJECTIVES
Write a main class of a program.
Use Turtle Graphics in a computer program.
Apply repetition in a program.
Apply composition or nesting to produce programs of increased
sophistication.
·
Identify the fundamental parts of a class definition.
·
Understand the notation for describing Java syntax.
·
Understand how Java programs are executed while still
achieving platform independence.
·
·
·
·
This book is about the construction of computer programs. As we have
seen, computers actually only understand programs expressed in their
natural language—machine language (a system of 0s and 1s). This
notation is difficult for human programmers to use in writing programs,
so high-level or problem-oriented languages were developed. Java is one
such language, and we shall use Java to express our programs.
A program expresses an algorithm—a procedure for doing something—
as a series of steps (statements) in the programming language.
Programs make use of resources (such as windows and files) provided in
a library of previously written program code. Programs often perform
actions repeatedly. To facilitate this, programming languages include a
construct (statement) for repetition called a loop.
A programming language is not a natural language, like English, that
evolved but rather one defined for a specific purpose—writing computer
programs. However, like any language, Java has grammatical rules that
must be followed. So that all involved in Java programming, from
compiler writers to programmers, have a clear understanding of the
rules, they are expressed in a formal notation.
2.1
JAVA
Java was developed at the beginning of the ‘90s by James Gosling et al. at
Sun Microsystems. Initially the language (then called Oak) was designed
for use in the development of consumer electronics, especially set-top
boxes for interactive television. Such systems are called embedded
systems in which the software is just one part of a larger system. As
market conditions change these systems often undergo a change of
processor. Since each different processor has its own machine language,
an early design criterion for Java was platform independence. That is
the code generated by the Java compiler should run on any processor.
This feature is now called “write-once-run-anywhere” and allows us to
write our Java code on a Macintosh or PC (or other machine) and then
run it on whatever machine we desire.
Java happened to come along at about the same time as a new use of the
internet: the World Wide Web. A web-browser such as Safari might run
on any machine and download a web page from a server (some other,
possibly different kind of machine) and display it. A platformindependent language called HTML describes the web page. Originally
web pages were static and simply showed text and graphics like a page in
a printed book. However, it was soon realized that dynamic content—
pages with which the viewer could interact—would be much more
interesting. What was needed was a programming language whose code
could run on any machine. Java was an obvious answer. A special kind of
Java program (called an applet) runs within a browser and provides the
executable content. This lead to a great deal interest and a lot of hype in
Java as the programming language for the web.
Our interest in Java is neither as a web programming language nor as a
language for embedded consumer electronics, but as a general
application programming language. Java was designed to be a modern
language. As such it embodies the object-oriented paradigm of
programming. It was also designed to be simple and safe. Like C++, it
borrows much of its structure from the programming language C, but it
has also improved on many of the features that make C++ difficult to use.
This makes it a good language for learning computer programming as
well as a good language for application development.
In object-oriented programming, a program is designed to be a model
of the real-world system it is replacing. The program contains objects
that represent real world entities (such as customers, students, reports,
and financial transactions) that interact with each other. Many useful
objects are provided in libraries to reduce the code that we have to write.
In our initial programs we will simply write the code describing one
object and make use of other objects from the libraries. Later we will
develop larger programs that use many objects, some from libraries and
some that we write ourselves.
DRAWING A SQUARE
Figure 2.1 shows a listing of a simple program that uses a drawing
environment called Turtle Graphics to draw a square. To the left of this
program listing is a series of numbers. These are not part of the program
itself, but are simply line numbers for reference in the description that
follows.
Figure 2.1: Example—Draw a Square
The code is part of a package called Example_2_1. It uses resources
from two libraries: Media and Math (lines 3, 4). The program is a class
(object) called Square since it draws a square (line 20). It uses a
TurtleDisplayer (window on the screen that can be drawn on) it
calls display (line 14) and a Turtle (object that can draw) it calls
yertle (line 15) to do the drawing. Both of these come from the Media
library. After creating the actual display and turtle and placing the turtle
on the display (lines 22-24), if instructs the turtle (yertle) to move
around and draw lines making a square (lines 26-35). When done, it
closes the window (line 37) and the program ends (terminates). The
result of executing the Square program is the window shown in Figure
2.2.
Figure 2.2: Drawing a Square
Since programs are meant to be read by people as well as a compiler, the
language allows comments to be included within the program text (lines
6–9 and 18). Comments begin with the characters /** (e.g., line 6) and
end with the pair */ (e.g., line 9). A second form of comment is found on
lines 14, 15, and 39. This kind of comment begins with the pair of
characters // and ends at the end of the line. The compiler ignores all
comments when translating the program into machine code.
Additionally, for the convenience of the human reader, white space—
empty lines, e.g. lines 2, 5, 10, 12 and 13, etc. and spaces and tabs for
indentation and alignment—may be inserted as desired.
2.2
TURTLE GRAPHICS
[2]
Turtle graphics was first introduced with the language Logo . The
metaphor is there is a turtle that is sitting on a piece of paper holding a
pen. The turtle can be instructed to move either forwards or backwards,
to turn left or right, or to place the pen on the paper or lift it from the
paper. If the turtle moves with the pen on the paper, a line is drawn. This
provides a basic drawing facility.
The library package called Media provides facilities for working with
various media including line (turtle) graphics, images (pictures) and
sounds. It is not one of the standard Java packages but rather was
defined to provide a framework for introduction to programming in this
book. The complete specification of the TurtleDisplayer and
Turtle classes can be found at URL:
http://www.cosc.brocku.ca/sites/all/files/documentation/Brock_packages/index.ht
To use the turtle graphics facility, the Media package must first be
imported (line 3 in Figure 2.1). A TurtleDisplayer (canvas, paper)
object and a Turtle object are declared (lines 14, 15) and then created
(lines 22, 23) and the turtle is placed on the paper (line 24).
The turtle starts out at the middle of the page facing to the right with the
pen up. The turtle is asked to place the pen down on the paper (line 26)
and move forward (line 27) 40 drawing units (the number in
parentheses; the page is 300 drawing units square), causing a line to be
drawn. The turtle is directed to turn to the right (line 28) some number
[3]
of radians . A right angled turn (1/4 around a circle) is π/2 and is
expressed in Java as PI/2.
After drawing the other three sides of the square (lines 29–34), the turtle
is directed to lift the pen from the paper (line 35). Finally, the user of the
program is permitted to close the display on the screen (line 37).
The methods a displayer (d) responds to are summarized in Table 2-1
and methods a turtle (t) responds to in Table 2.2.
Table 2.1 TurtleDisplayer Methods
Table 2.2 Turtle Methods
2.3
REPETITION (LOOPS)
Consider the example in Figure 2.1. Notice that lines 27–34 are
repetitious—the same pair of statements is repeated four times drawing
the four sides of a square. This isn’t too bad in drawing a square, but
what if we were drawing a hexagon (six sides) or a figure with even more
sides! Repeating the two lines for each side would quickly become
tedious. Luckily Java has a construct called a loop that allows us to
repeatedly execute a sequence of statements.
DRAWING A HEXAGON
Let’s write a program to draw a hexagon. A hexagon looks Figure 2.3.
Figure 2.3: Geometry of a Hexagon
There are six sides of equal length each rotated from each other by some
angle. The angles between the sides have to make a complete rotation
(2π radians), so the angle is just 2π divided by the number of sides (2π/6
or π/3). We draw the figure by repeatedly (six times) drawing a side and
rotating the turtle:
We could write the code like this:
where the lines are repeated six times (like we did four times in drawing
the square). However, Java provides a construct—called a for loop—that
performs a sequence of statements repeatedly. The following code
achieves the same result:
The for loop causes the statements between the { } (called the body of
the loop) to be repeated some number of times. The notation between
the ( ) tells how many times. Essentially, to repeat some actions a
specific number of times:
we write a for loop that looks like this:
Using this construct, we can write the program as shown in Figure 2.4. In
comparison with the example in Figure 2.1, we see that other than a
change of names (Hexagon rather than Square), the lines 27–34 have
been replaced by the for loop (lines 30-33). The program thus draws six
lines rather than four.
In the for loop, the name i (the index variable) can be any name of our
choosing. Traditionally the single letters i through n are used. The
notation within the ( ) indicates that the index variable (i) counts
repetitions of the body starting at 1 (i=1) and counting up (i++) to 6
(i<=6). In other words, the body is executed 6 times.
Figure 2.4 Example—Draw a Hexagon
2.4
COMPOSITION (NESTING)
Many complex patterns are composed of repetitions of a sub-pattern. For
example, the honeycomb pattern in Figure 2.4 is composed of hexagons
in a circular arrangement. To produce the more complex pattern, we can
embed the algorithm for the basic pattern (the hexagon) within code that
repeats the pattern in some arrangement (i.e. circularly). This process of
creating a new algorithm by embedding one algorithm within another
algorithm is called composition and is a powerful tool in programming.
DRAWING A HONEYCOMB
Consider writing a program to draw the honeycomb in Figure 2.5. The
drawing consists of six hexagons drawn in a circular arrangement. Note
that, in terms of drawing this pattern, we don’t actually have to draw the
interior hexagon, only the 6 exterior ones.
Figure 2.5 Honeycomb as a Repetition of Hexagons
We can express this drawing as follows:
We know how to draw a hexagon, so we can substitute that algorithm
into the above:
To make this into a computer program, we need to fill in some of the
gaps. Consider Figure 2.5. Assuming we want to draw the honeycomb
around the starting position of the turtle (the center of the page), we
need to move the turtle out from the center and then turn the turtle to
face down the first side of the first hexagon. If the length of the side of the
hexagon is 40 units, the radius (distance from the center to a vertex) is
also 40. Thus we need to move the turtle 40 units (with pen up) to get to
the corner of hexagon 1. Since the code we used to draw a hexagon
(Figure 2.3) draws the first side in the current turtle direction continuing
clockwise, we need to orient the turtle up and to the left for the first
hexagon. Since the exterior angle of a hexagon is π/3, the interior angle is
2π/3. So we need to rotate 2π/3 to the left. Moving to the starting point
for the first hexagon is thus:
Once the first hexagon has been drawn, the turtle is back to where it
started—at the first vertex, facing up and to the left. To move it to the
position for the next hexagon, we need to move along the first edge and
then rotate π/3 to the left to point along the first edge of the second
hexagon:
Filling in these details, we get the following algorithm:
Figure 2.6 shows the complete program. The program is similar to the
previous two programs. The turtle and turtle displayer are created and
the turtle put on the display. The code that does the work follows. This is
just the algorithm above written in Java (lines 23–25).
There are a few things to note. The for loop that draws a hexagon (lines
28–31) is nested (contained within) the loop for repeating the figure six
times (lines 26–35). This is evident since the inner (nested) loop is
between the { (on line 26) and the } (on line 35) which mark the body of
the outer loop. Also note that the index variable on the outer loop is
named j while the index variable on the inner loop is named i. The
names of the index variable of a nested loop must be distinct from the
name of the index variable on the outer loop. This is a downside of using
nesting to achieve composition. We will see later that there is another
way to achieve composition, without nesting, which avoids this.
Finally, lines 37–38 should be explained. After the completion of the
outer loop, the honeycomb has been completed and the pen is up.
Rotating the turtle and moving backward doesn’t have any visible effect.
Why do it? When the honeycomb is complete, the turtle is back to the
first vertex of hexagon 1, pointing along the first edge. Lines 37–38
return the turtle to its starting point—essentially undoing lines 23–24.
Consider if drawing the honeycomb was only a part of a drawing a more
complex scene. In this case, after we draw the honeycomb, we would
want to draw something else, probably not where we left off with the
honeycomb. Knowing exactly where the turtle is (i.e. right where it was
before starting the honeycomb) makes it much easier to figure out how
to move the turtle for the next component of the scene.
Figure 2.6 Example—Draw a Honeycomb
As a last point about nesting, it is instructive to look at how many lines
are drawn. The code within the inner loop draws 1 line. The inner loop
repeats this 6 times, giving 6 lines. The outer loop repeats this 6 times
giving 6×6=36 lines in total. In general if a loop that repeats something n
times is nested within a loop that repeats m times, the total number of
times the body of the inner loop is executed is n×m.
REPEATED PATTERNS
Many complex patterns can be decomposed into repetition of drawing a
simpler pattern. For example, tiling a surface—covering an entire
rectangular area with some figure (tile)—involves complex repetition of
a pattern. Covering the whole surface involves drawing rows of the
pattern down the area. Drawing a row of the pattern involves repeatedly
drawing the pattern across the area. This gives us a tiling algorithm:
Notice that this involves two compositions: composing a row as a
sequence of patterns and composing the area as a sequence of rows. In
general, there could be any number of levels of composition. However, as
the number of levels grows, achieving composition via nesting (as done
in this chapter) becomes too complex and a more powerful composition
technique (methods) becomes desirable.
2.5
CLASSES AND OBJECTS
Classes are the fundamental building blocks in object-oriented
programming. Each represents some kind of entity in the real-world
system that the program is modeling. In Java, a program is a collection of
classes (including those written by the author and those from libraries).
The honeycomb program in Figure 2.6 consists of three classes: the class
Honeycomb, as written, and the classes TurtleDisplayer and
Turtle, as imported from the Media library.
In Java the code we write and have the compiler compile is a class
declaration. All code we write will be contained in some class. A class
declaration serves to define a set of possible objects. Think of the class
name as a generic noun like dog or house. These nouns describe the set
of possible objects (dogs, houses). Actual objects such as my dog Rover
are created from this declaration through the use of a creation
expression (e.g. line 20):
which creates a new object as an instance of the class. The program
creates three objects: a TurtleDisplayer on line 19, a Turtle on
line 20 and a Honeycomb on line44. It is these objects that interact to
perform the tasks required of the program. In this simple program to
draw a honeycomb, there is only one of each kind of object. However, in
larger systems, there may be many kinds of objects and many of each
kind.
When an object is created, we give it a name so we can keep track of it,
just like I gave my dog the name Rover when I got him from the breeder.
In programming languages, such a name is called a variable identifier
(or variable for short). We can choose any name we desire as long as it
isn’t one of the Java reserved words (see URL:
http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.9 for
a list).
The reserved words are the only words that the Java compiler
understands implicitly. Any other word (e.g. variable names) we use in a
program must be defined for the compiler. We declare (define) a new
variable name using a field declaration (e.g. line 13):
which tells the compiler that the name yertle will be used to refer to
an object of the class Turtle.
The way we remember something by giving it a name is called an
assignment statement (line 20):
In line 20, the Variable is yertle (which was declared in line 13) and
the expression is the creation expression (see above) which creates a
new Turtle object. The statement assigns (remembers) this Turtle
object using the (variable) name yertle. When we use the identifier
yertle later in the program (e.g. line 23), it is this Turtle object,
created in line 20, that is being referenced.
Once an object of a class has been created, it may be asked to perform
actions. This happens in a method call statement (e.g. line 23):
Object is the name (variable) of the object that is being asked to do
something (execute a method). In this case it is the Turtle object
yertle. MethodName is the name of the operation (method) that the
object is being asked to perform. In this case it is forward which
instructs the turtle to move in the direction it is currently facing. Often a
method requires additional information; in this case, how far to move.
Such information is provided as Arguments above. The method
forward requires one additional piece of information—the number of
drawing units to move—so this is supplied as one argument: the number
40. Methods may require any number of arguments, including zero (e.g.
penDown() in line 27), one (e.g. forward(40) in line 23), or more,
separated by commas.
STATE AND BEHAVIOR
Every object has a state (collection of all the things the object knows).
What an object does when it responds to a method call (its behavior)
depends on its current state. When an object does something, its state
may change. For example, a turtle state consists of a number of things:
where it is on the page, which direction it is facing, whether the pen is up
or down, etc. When a turtle responds to the method call forward(40),
what it does depends on its state. It moves forward from its current
position, in the direction it is facing 40 drawing units. If the pen is down,
it leaves behind a 40 unit line, if not, it moves without leaving behind a
line. Once it has moved, its current location has changed. Thus behavior
depends on state and may change state.
Figure 2.7 Example—Two Turtles
Each object has a unique identity and thus an unique state. Change to the
state of one object does not affect the state of another. Consider the
program in Figure 2.7. Two turtles (yertle and mertle) are created
and both are placed on the same display. So we can tell between them,
yertle’s pen color is set to red and mertle’s to green. yertle is
moved up 60 drawing units (moveTo moves the turtle to a specific point
on the page. The center of the page is (0,0) and positive values are up
and to the right). Now the two turtles draw the four lines of a square. The
result of the program execution is shown in Figure 2.8.
Figure 2.8 Two Turtles Drawing Two Squares
yertle and mertle each have different, and independent, states. By
line 35, yertle is at (0,60), facing right with a red pen that is down
(this is yertle’s state) while mertle is at (0,0), facing right with a
green pen that is down. Within the loop from lines 36–40, both yertle
and mertle respond to identical method calls, but do different things.
That is they both draw squares but yertle’s is higher on the page and is
in red while mertle’s is lower and in green.
OBJECTS, VARIABLES AND MEMORY
As we know, everything that takes part in a computer program is stored
in memory. Since an object’s state affects the execution of a program, the
state must be stored in memory. When an object is created (creation
expression), some memory is set aside (allocated) to store the state of
the object. This memory represents everything that makes up the state of
the object including, in the case of a Turtle object for example, its
location, direction, pen state (up/down, color), what display it is drawing
on, etc.. The things that define the state are the same for all Turtle
objects; it’s the values of these that make one object different from
another.
Figure 2.9 shows the representation in memory of the two turtles in the
program in Figure 2.7 after the execution of line 34. Variables in a
program correspond to cells in memory in which information can be
remembered (stored). An assignment statement stores the value of the
expression on the right hand side of the = (called the rhs) into the
storage indicated by the variable on the left-hand side (lhs). In the
assignment statement in line 25, the creation expression allocates
storage for a Turtle object (i.e. for the state of a turtle object). The
address of this storage—called an object reference, or reference for
short, in Java—is what is stored in memory for the variable yertle
(shown as an arrow in the figure). Line 26 does the same for another
Turtle object which is referenced by mertle.
Figure 2.9 State of Two Turtles
Lines 27 and 28 change the state of the turtles by informing them of the
display they are drawing on. This is the display created in line 24 (and
referenced by the variable display not shown in the figure). Lines 3034 also modify the state of the two turtles. Now, once we reach lines 36
and 37, the two different turtles move forward based on their unique
state information and thus draw different colored lines at different
places on the same display. Of course, these operations change the state
of the turtles (as do lines 38 and 39) and the next time through the loop,
the turtle’s states are different and so the next lines drawn are in a
different direction starting at different points.
2.6
JAVA PROGRAM FRAMEWORK
Figure 2.10 shows the framework of a Java class as a main program. The
code for a class is stored in a file with the same name as the class and a
.java suffix. As mentioned in Section 2.1, the program text can be typed
on as many lines as desired (including leaving blank lines) and spaces
and tabs and/or comments can be inserted between any two words or
symbols to aid the human reader. Words written in plain font are
typed as is. Words in italics are names (identifiers) that are chosen
by the programmer. A name in Java is a sequence of letters and digits
beginning with a letter and may contain underscores (_) but not spaces.
The name cannot be a reserved word. By convention, when a name
consists of more than one word we capitalize the first letter of the second
and subsequent words.
Figure 2.10 Framework of a Java Class
A package is a collection of related classes and all Java classes belong to
some package. The package statement specifies to which package the
class belongs. For our purposes, each example will be a distinct package.
By convention, package names begin with an uppercase letter and
correspond to directories (folders) in the operating system. The class
(.java) file is stored in this directory.
The import statements indicate that the class uses resources (classes,
values) defined in other (library) packages. The first form indicates that
the class may use any class defined in the library (e.g. Turtle from the
Media library). The second form indicates that the class may use values
defined in the specified class (e.g. PI—the value of π—defined in by
Math class of the java.lang library).
The class declaration begins with the class header. ClassName is
chosen to reflect what the class represents (e.g. Square for a class that
draws a square). By convention, class names begin with an uppercase
letter.
The class body consists of the statements within the { } following the
class name to the end of the file. The class body specifies what makes up
the state and behaviors of an object of the class.
The first statements in the class body are the instance variable
declarations. Instance variables are the variables that represent the
state of an object and represent memory in which a value may be stored.
Type is an indication of what kind of information the variable stores.
Variable is the variable name and, by convention begins with a
lowercase letter.
Next is a constructor declaration. A constructor is a specification of the
behavior of an object immediately upon creation. That is, it indicates
what the object does after it comes into existence. The constructor
header includes the ClassName of the class (this is what makes this a
constructor). The constructor body is the statements enclosed in the {
} following the header. These statements can be assignment statements,
for loops or any other Java statement. They specify the sequence of
actions that make up the behavior of the object.
The final lines following the constructor must, for the moment, be taken
on faith. The last line declares a variable (v chosen by the programmer)
of the class type and then creates an object of that class. Once the object
is created, it performs its creation behavior as specified by the
constructor body. This is how the program gets started and accomplishes
its desired result. Once the statements in the constructor body have been
completed, the program terminates.
2.7
EXECUTION OF JAVA PROGRAMS
As mentioned in section 2.1, one of the goals of the design of Java was
platform independence—that the code generated by a Java compiler
would run on any platform. This is necessary if a Java applet is to be
transmitted as part of a web page and then executed, even though the
browser might be running on any machine (such as a PC, Mac or an iPad).
In Chapter 1, we saw that each processor family has a different machine
language. A PC doesn’t understand Mac or iPad machine language and
vice versa. Since, as described in Section 1.4, a compiler generates
machine language, how is platform independence possible?
To achieve the goal of platform independence, the Java designers
specified that a Java compiler generates a special machine code-like
binary language called Java bytecode instead of generating actual
(native) machine code. Since the processor does not understand
bytecode, a special program, called a Java interpreter, is written for
each platform. This interpreter, like a compiler or linker, is a program in
native machine language that executes the bytecode on the actual target
platform.
Figure 2.11 shows this process in a diagram similar to Figure 1.5. The
Java compiler translates the Java source program, as Java code, into Java
bytecode. The linker combines this bytecode with bytecode for library
classes producing “executable” bytecode. In the execution phase, the Java
interpreter is loaded into memory and executed. It inputs the bytecode
for the program and the data used by the program and executes the
bytecode instructions, producing the output.
Figure 2.11 Executing a Java Program
The result is that, even if the Java program is compiled and linked on a
Linux machine, it can be executed on any other machine, such as an iPad
—platform independence achieved. However, this does require that a
Java interpreter is available for the target machine. Typically, browsers
such as Safari or Internet Explorer have a Java interpreter built-in so Java
applets may be executed. To run a Java application program outside a
browser, it is necessary to acquire, install and run an appropriate Java
interpreter.
*2.8
JAVA SYNTAX
A programming language is a medium for communication similar to a
natural language such as English. Of course, in English the
communication is usually between two people. In computer
programming the communication is between a person, the programmer,
and a computer program, the compiler.
To allow clear, unambiguous communication, certain rules must be
followed. In a natural language these rules are called grammatical rules
that we all learned formally or informally as we learned the language.
These rules specify how we may use word, punctuation, and other basic
elements of the language to compose sentences. They specify, for
example, that a sentence must have a subject and a verb and may have an
object. They also specify that a period must be placed at the end of an
imperative sentence. Implicit from the construction of the sentence and
the actual words used is the meaning of the sentence.
Similarly, a programming language has a set of grammatical rules (its
syntax) and a set of rules about meaning (its semantics). The syntax
specifies how the basic elements of the language are used to compose
programs. It specifies the placement of identifiers (names) like yertle,
keywords like class, and punctuation like ; and ) in the program. The
semantics specifies the effect of the program when it is executed.
[4]
Java is defined in the Java Language Specification . This document
defines both the syntax (grammar) and semantics of the language.
Throughout the specification, the grammar is described using a BNF-like
notation. In this notation, the grammar is described by a set of rules. At
the beginning of the rule there is a word followed by a colon (such as
Sentence: in Figure 2.12). This is the name of the rule. Following this
line are one or more lines representing alternatives. Each alternative
consists of a sequence of words and symbols which are to be written in
order. Words written in italics are names of other rules. Words and
symbols written in plain font may be punctuation such as ;, keywords
that have a specific meaning and are defined by the language such as
class, and identifiers, words coined by the programmer such as
yertle.
SIMPLIFIED ENGLISH GRAMMAR
As an example, the rules in Figure 2.12 specify a simple English grammar.
Figure 2.12 Simplified English Grammar
The grammar specifies that a Sentence consists of a Subject followed
by a Verb followed by an Object followed by a period. A Subject can
be a NounPhrase, as can an Object. A NounPhrase can be either an
Article followed by a Noun or just a Noun. A Verb is one of the
words: likes or has. An Article is one of the words: a or the.
Finally, a Noun is one of the words: John, Mary, book, or Java. An
English sentence can be composed (derived) by writing sequences of
symbols, starting with the name of the first rule, Sentence. The
derivation proceeds by substituting an alternative for a rule name, until
there are no rule names left. Figure 2.13 demonstrates the derivation of
the sentence: “John has a book .” according to this grammar.
Figure 2.13 Example—Derivation of an “English” Sentence
This grammar can be used to derive a number of sentences including
those in Figure 2.14. Not all of these are meaningful sentences. The
semantic rules of the language would specify which are meaningful and
what those meanings would be.
Figure 2.14 Example—“English” Sentences
To make the rules a little easier to write (and read), a few notational
conveniences are used. A rule of the form:
may be written as:
where the subscript opt following the name Article means that the
inclusion of Article is optional. A rule of the form:
may be written as:
where the special phrase one of written on the first line of a rule
means that the symbols on the following line are really alternatives.
Finally, a very long alternative can be written on more than one line with
the subsequent lines indented substantially.
This kind of rule implies that one or more occurrences of SomeUnit
may be written. If just the first alternative is used, one instance of
SomeUnit occurs. If the second alternative is used first followed by the
first, two instances occur, etc. Typically the existence of a plural symbol
implies one or more occurrences of the symbol.
CLASS DECLARATION SYNTAX
A simplified version of the syntax of a class declaration is found in Figure
2.15.
Figure 2.15 Class Declaration Syntax
By this grammar, the class declaration for Square (starting at line 11 in
Figure 2.1) begins with an optional Modifier. Modifiers describe
properties of classes, such as where they may be used. This is called
scope and is described in a later chapter. In this case public means that
the class may be used by other classes.
The modifier public in line 11 is followed by the keyword class. Next
is Identifier —the class name Square. Finally, a ClassBody
appears. A class body is an optional sequence of (one or more since it is
plural) ClassBodyDeclaration (lines 12–44) enclosed in braces ({
and } in lines 11 and 45). A ClassBodyDeclaration is a
FieldDeclaration (lines 14 & 15), a ConstructorDeclaration
(lines 20–39) or a MethodDeclaration (line 42). We will not discuss
the syntax of a method declaration at this time.
A field is another name for an instance variable. In Figure 2.1 there are
two fields declared in lines 14 & 15 as FieldDeclaration. The
Modifier is the keyword private. This means that the fields cannot
be used by other classes. In line 14 the Type is the class name
TurtleDisplayer and the Identifier is the name display. So
display references a TurtleDisplayer. Similarly in line 15,
yertle is declared as a Turtle reference.
The constructor for the Square class is found lines 20–39 as a
ConstructorDeclaration. The Modifier is the keyword public.
As for classes, modifiers can be used to indicate the properties of a
constructor. The modifier public indicates that other classes may
create Square objects. Next is the Identifier Square naming the
constructor. A constructor always has the same name as the class itself.
The FormalParameterList is omitted and so there is an empty pair
of parentheses following the identifier. Finally there is an optional
sequence of BlockStatements enclosed in braces (lines 21–37).
These are the body of the constructor.
STATEMENT SYNTAX
The body of a constructor is a sequence of BlockStatements. A
statement is the specification of some action to be performed. There are
many kinds of statements in Java. In Figure 2.1, two kinds of statements
are used: assignment statements (line 22 & 23) and method invocation
statements (lines 24–37). Figure 2.16 shows the syntax of these two
statements.
Figure 2.16 Statement Syntax
Line 22 is an Assignment statement. The LeftHandSide is the
instance variable display and the AssignmentExpression is a
creation expression for a new TurtleDisplayer object. An
assignment statement is the way information is stored in memory. In this
case, the address of the new TurtleDisplayer object is stored in the
memory location labeled by the variable display. Similarly line 23
stores the address of a new Turtle object in yertle.
A MethodInvocation is the way that an object asks another object to
perform some operation. Primary is the instance variable referring to
the object that is being asked to perform the operation (display in
lines 24 & 37 and yertle in lines 26–35). Following the period is the
name (Identifier) of the method that the object is being asked to
perform (e.g., placeTurtle, penDown, forward). There is an optional
ArgumentList enclosed in parentheses. For methods that require no
additional information, like penDown and penUp, this list is omitted and
just the parentheses are written. For methods that require additional
information such as the turtle to be placed on the display for
placeTurtle or a distance to move for forward and backward, this
value is supplied as the ArgumentList inside the parentheses.
SUMMARY
Programming in a high-level language is more productive than using
machine language. We will use the language Java, an modern objectoriented language for our examples. The set of rules for writing a
program in a language are specified by its grammar or syntax.
A Java program consists of a number of classes, some written by the
programmer, some used from libraries. Classes define a set of possible
objects, each an instance of a class with its own state. Objects created via
the operator new. When an object is created, its constructor is executed.
Fields, specifically instance variables, serve as memory for the object
(recording its state) and methods and constructors specify actions
(behavior) the object may perform. A behavior depends on and may
change the state. Assignment statements allow the object commit things
to memory and method invocation statements allow the object to make
use of services provided by other objects.
Turtle graphics is provided by the Turtle class in the Media library. It is a
facility for doing line drawings in a window on the screen (a
TurtleDisplayer). A Turtle object can be requested to move or
rotate, and movement with the pen down draws a line.
A for loop can be used to repeat a sequence of statements some number
of times. For example it can be used to draw a hexagon by six times
repeating drawing a side and turning the corner. Loops can be nested
(composition) to produce complex drawings.
To achieve platform independence, a Java compiler generates bytecode
instead of machine language. For a Java bytecode program to be
executed, a program, called a Java interpreter, must be run.
REVIEW QUESTIONS
1.
T
F In an embedded system additional hardware is integrated
into the computer’s processor.
2.
T
F
3.
T
F The semantics of a programming language are the set of
rules that describe the meaning of a correctly composed
program.
4.
T
F Every Turtle object (from the Media library) starts out at the
middle of the page, facing to the right, with its pen up.
5.
T
F A program must include a class declaration for every class it
uses.
6.
T
F
A class is a type of object.
7.
T
F
The following is an example of a field declaration:
8.
The syntax of a language specifies:
a)
b)
Java provides platform independence.
the set of symbols used in the language
the grammatical rules (how the basic elements may be
combined)
c) the meaning of a correct sequence of basic elements
d)
9.
all of the above
Which of the following is a valid sentence according to the grammar?
a)
b)
c)
d)
a the
John Mary
a book
Mary book John
10. Which of the following is not a kind of symbol in a programming
language grammar?
a)
b)
c)
d)
punctuation
identifier
keyword
all are symbols
11. The following line:
is an example of:
a)
b)
c)
d)
a field declaration
an assignment statement
a method invocation
a class declaration
12. In the following line of code
a)
b)
c)
yertle is an object and forward is a class
forward is an object and 10 is a parameter
yertle is an object and forward is a method
d)
yertle is a class and 10 is an argument
13. The following sequence of statements draws what figure?
a)
b)
c)
d)
a triangle
a square
a hexagon
none of the above
14. The following:
is an example of:
a)
b)
c)
d)
composition
nesting
repetition
all of the above
15. How many lines would the turtle draw (forward) in the following
code?
a)
24
b)
c)
d)
19
6
3
EXERCISES
1.
Modify Example_2_2 (Hexagon) to draw a pentagon (a regular
five-sided closed figure) with sides 40 units long. The exterior angle of
a pentagon is 2π/5.
2.
Modify Example_2_2 (Hexagon) to draw a pentagram (as shown
below, a regular five-point star) with sides 80 units long. The exterior
angle (i.e. from one side to the next) is 4π/5.
3.
Write a program to draw a cube, in perspective, as shown below. The
sides of the cube should be 40 units long. Use any reasonable means to
draw the figure (it cannot simply be drawn using a single loop, but
must composed of a number of parts.) The turtle can be moved from
one place to another without drawing a line if the method penUp is
used before the forward. (Don’t forget to put the pen down again.)
4.
Modify Example_2_3 (Honeycomb) to draw the following figure
(a poppy) which consists of four equilateral triangles (side 40 units,
exterior angle 2π/3) each rotated π/2 from the other.
5.
6.
Write a program to draw a picket fence (shown below) as series of
13 pickets (boards), each a rectangle 10 units wide and 80 units high.
The pickets should be spaced 5 units apart.
Write a program to draw a picture frame:
The frame is essentially a square 90 units on a side, except that each
side is replaced by a sequence of 6 connected pieces consisting of 7
lines drawn as shown:
To make the frame bold, use the Turtle method penWidth(2)
before drawing the lines. This sets the width of the drawing pen to 2
units.
3 METHODS
CHAPTER OBJECTIVES
·
·
·
·
·
·
·
·
Explain procedural abstraction as represented by a method.
Design methods to represent cohesive sub-tasks in a program.
Explain the difference between local and non-local methods.
Identify the main method and main class of a program.
Apply parameters in the design of methods.
Design function methods.
Use method stubs in testing and debugging methods.
Explain the concepts of scope and visibility.
As problems get more complex and programs get bigger, we reach the
point where we cannot fully comprehend the details of the entire
solution at one time. Abstraction is an important technique to deal with
complexity. In programming, one abstraction mechanism is a procedure
or method. A method describes a common way of accomplishing a task
(e.g. drawing a square). The method can be defined once—where the
details can be considered—and used when needed without
consideration of the details.
Methods gain power—ability to handle more possibilities—when they
are parametric (have parameters). For example, a parameter for a
method that draws a square could be the length of the side of the square.
This way the same method can draw squares of different size.
In this chapter, we will see how to write method declarations that specify
the sequence of statements that make up the operation and give them a
name. We will use the method invocation statement to then perform our
locally defined method, sometimes called invoking the method or
executing the method. We will see the sequence of execution when a
method is called. We will learn how to extend the power of methods by
using parameters and will see how to write function methods—methods
that compute a value. Finally we will see how Java controls the names
space (the names declared within a program) through scope rules.
3.1
METHODS AND ABSTRACTION
In the honeycomb example in Figure 2.4 we nested the drawing of a
hexagon within a loop to repeat it six times. We built a more complex
program out of smaller pieces using composition by nesting. This way of
extending programs is effective as long as the program doesn’t get too
big. As programs get larger, the side effects of nesting become more
complex and eventually unmanageable. For example, what happens if we
accidentally use the same index variable in two loops? If we used nesting
10 or 20 times; how would we keep track of the loop indices? Also, we
often find that we need to do the same thing at a number of different
places within the program. For instance, in drawing a scene, we may
need to draw a square at a number of different places.
We need a mechanism to allow us to compose an operation like drawing
a square, give it a name, and then refer to that operation by name at a
variety of places in the program. In Java, this mechanism is called a
method (also known as a procedure in other languages). A method is a
named sequence of instructions that can be referenced in other places in
the program through the use of a method invocation statement. In fact,
we have already used methods and the method invocation statement in
Chapter 2 when we used methods such as forward provided by the
Turtle class.
Methods were the first and simplest of the mechanisms in Computer
Science to deal with the complexity of large systems. We can deal with
complexity by focusing on a particular issue (e.g. drawing a square)
ignoring why we would want to do so. At another point, we ignore the
details of how to perform the operation, but simply use it. This is a form
of abstraction—ignoring details and differences and focusing on the
similarities—called procedural abstraction.
Consider drawing a complex scene with a triangle at one place, a square
at another place and a pentagon at a third. While we are designing the
scene and figuring out how to move from one place to another, we can
ignore the details about how to actually draw the figures. At a later time,
we can concentrate on the details of drawing each of the figures
individually. We are thus using abstraction to deal with the complexity.
The algorithm might look like:
Ultimately process to draw each of the figures would be expressed as
method definitions and the actual drawing as method invocations.
DRAWING TWO SQUARES
Let us consider writing a method declaration and using it in a program.
To keep it simple, let’s write a program to draw two squares at different
places on the display. The code for drawing a square is quite straight
forward:
A method declaration has the form shown in Figure 3.1.
Figure 3.1 Method Declaration
To declare our method, we choose an appropriate methodName
(normally a verb or verb phrase starting with a lowercase letter with
subsequent words starting with an upper case letter) such as
drawSquare. We then fill a sequence of statements that accomplishes
the task (drawing a square, the code above) as the body of the method
declaration.
Figure 3.2 Example—Draw Two Squares
Figure 3.2 shows a complete program to draw two squares using a
method. The method declaration following the form of Figure 3.1 occurs
at lines 33–42 with, the method name being drawSquare and the body
being the statements as described. As indicated in the comments, the
method draws a square with the first side in the current turtle direction
and proceeding clockwise (i.e. the current position is the top-left corner).
The method leaves the turtle back at the original position and direction,
with the pen up.
You may notice that a method declaration is very similar to a constructor
declaration (see Figure 2.10). This similarity is not accidental. A
constructor is the method that is to be used at the start of an object’s life,
and consists of a sequence of statements to be performed at that time. A
method is a bit more general since it can be used at any time not just
when the object is created. It consists of a sequence of statements to be
performed when required—in other words, when invoked. The method
declaration consists of two parts: a method header and a method body.
The method header specifies what the method defines (that is, the
abstraction) and the method body supplies the details (that is, the
statements to be performed).
When we need to draw a square, we simply position the turtle at the
appropriate place and direction and use (invoke) the drawSquare
method. We do not need to worry about how the square is drawn. This
happens within the constructor (lines 17–28). We move to the location
for the first square (line 22) and then draw the square using a method
invocation (line 23). For the second square, we do likewise (lines 24 &
25).
If you compare the method invocations of drawSquare (lines 23 & 25)
with the method invocations of moveTo, penDown, forward, right
and penUp (lines 24, 24, 35, 37, 38 and 40), you will notice that the
invocation of drawSquare does not include the object that is to perform
the operation (see Section 2.5). We previously indicated that all methods
are executed by some object, what is happening here with
drawSquare? The constructor itself is being executed by an object—the
TwoSquare object created in line 44. When a method invocation does
not include an object, the object is implicitly the one executing the
method call statement itself—“this” object. This kind of method
invocation is called a local method call—a call to a method of this object
itself. Since drawSquare is part of the TwoSquare class, TwoSquare
objects know how to do drawSquare just as Turtle objects know how
to forward. In fact, a local method invocation is just shorthand for a
method invocation with the object itself executing the method as:
The Java reserved word this refers to the object that is executing the
statement and is implicit whenever the Object is omitted in a method
call.
METHOD EXECUTION
When a method is invoked within a piece of code, the execution of that
piece of code is suspended and execution of the method begins with the
first statement of the method. When the last statement of the body has
been executed, the method terminates and execution of the code
containing the invocation resumes with the statement following the
method invocation. The statements of the method are executed by the
object on the method call (which is this object in a local method call).
Figure 3.3 Method Execution
Figure 3.3 shows the execution of part of the constructor of the
TwoSquare class. There are two objects involved: this object, the
TwoSquare object created in line 44 whose constructor is being
executed and yertle, a Turtle object created in line 20. The arrows
show the flow (sequence) of execution. The label beside an arrow
indicates which object is executing the code.
When the TwoSquare object is created in line 44, the constructor of the
class TwoSquare is executed by the this object. When it reaches the
call to moveTo, the execution of the constructor is suspended and the
method moveTo in the Turtle class is executed by the object yertle.
When the moveTo method reaches the end (having moved yertle to
the new position), the constructor resumes execution (by the this
object) with the next statement. In executing the drawSquare method
call, execution of the constructor is again suspended and the
drawSquare method of the TwoSquare class is executed by the this
object. When the drawSquare method reaches the end (having had
yertle draw a square), the constructor again resumes execution (by
the this object) with the next statement. And so on.
Method declarations do not stand on their own—they are always part of
a class. In fact, a class declaration is actually just a collection of
declarations that include constructor declarations, instance variable
declarations, and method declarations. It is through method declarations
that we specify what an object can do.
CENTERING THE HEXAGON
If we are drawing a complex scene consisting of many figures—squares,
hexagons, circles…—it is complicated to keep track of where drawing
begins and ends for each figure. It is easier if we view the drawing of an
object around its central point and leave the complexity of figuring
where the lines go to the method drawing the figure.
How would we go about drawing a hexagon centered on a particular
point—the position of the turtle? If we consider the hexagon inscribed in
a circle (as in Figure 3.4), the size of the hexagon can be specified by the
radius of the circle and the location of the hexagon as the center of the
circle. To draw the hexagon we must: move the turtle out from the center
to the circumference (a distance equal to the radius r), rotate it to face
down the first side to be drawn (angle of π/2+π/6) and then draw the
six sides (each of length 2 r sin π/6) at an angle of π/3 from each
other. When complete, we are back at the starting point of the drawing
and can return the turtle to the center of the circle by reversing the
original operations. Note that the first vertex drawn is the one to which
the turtle is originally pointing and drawing proceeds clockwise.
Figure 3.4 Geometry of a Hexagon
The program is found in Figure 3.5. The method drawHexagon follows
the process described above. The method fixes the radius as 80 (line 34),
computes the angle between the sides (line 35) and then computes the
length of a side (line 36). The expressions in these assignment
statements make use of standard arithmetic operators (+ for addition, for subtraction, * for multiplication and / for division) and standard
mathematical functions such as sin. Like PI, sin is provided by the
java.lang.Math imported in line 3.
Note that these variables are declared at the start of the method (lines
30–32). These declarations look just like instance variable declarations
(lines 12–13) except that they don’t include a modifier (private) and
the type is double—the variables store a numeric value. These are
called local variable declarations and the variables radius, angle and
side are thus local variables. Local variables are temporary memory
used in a method to remember things. They exist only while the method
is executing, and the values are lost when the method terminates.
Figure 3.5 Example—Draw a Hexagon Centered on the Turtle
We use local variables to store (1) the radius of the figure, (2) the angle
between the sides, and (3) the length of the side, which is computed from
the radius. By computing the angle and length of the side once and
storing them in variables, we avoid the repeated recomputations that
would have occurred within the loop if we did the computations there.
Although in this case the effect would be small (that is, only 10 extra
computations), if we were to draw a lot of hexagons or we were drawing
a figure with many more sides, this effect could become significant. As a
general rule, if we need the result of a computation a number of times as
within a loop, it is better to compute it once, store the result in a local
variable and simply reference the variable as needed. This is a common
use for local variables.
THE main METHOD
Consider the last line of code in the class beginning public static
void … in figure 3.4. This is actually a method declaration of a method
called main whose body consists of solely the creation of a new
Hexagon2 object whose reference is assigned to the local variable h.
One class in each program, called the main class, must have a method
called main, with these modifiers and parameters (the stuff in
parentheses, see Section 4.2). The program actually begins with the
execution of this method. In our case, the main method simply creates a
Hexagon2 object whose constructor does what we want. We will use
this trivial main method in the main class of each of our programs as a
way of getting things started.
3.2
METHODS WITH PARAMETERS
Methods like those we wrote in Section 3.1 have their uses; however,
they are not very versatile. For example, suppose we wished to draw a
picture consisting of say ten hexagons of different size. We would have to
write ten methods, each to draw a hexagon with a different radius. If we
were to compare two such methods, we would see that, other than
differing names, they would differ in only in the value assigned to the
local variable radius. It would be better if we could generalize the
method code so that it would work for hexagons of different size much as
the Turtle method forward can be used to draw lines of different
lengths.
PARAMETER LIST
If a single method is to be able to draw different-sized hexagons, it
somehow needs to know how big the radii are to be. For the Turtle
method forward, different length lines are drawn by providing values
that indicate the different line lengths. This is called passing a parameter.
What we need is for our hexagon method to use parameters.
The form of a method declaration with parameters is shown in Figure
3.6. There is an optional parameter list between the parentheses
consisting of pairs: type and paramName—like in a variable
declaration. This is where we specify that a method expects to be passed
parameters.
Figure 3.6 Method Declaration with Parameters
As discussed above, the hypothetical hexagon drawing methods differed
only in the value assigned to radius. Thus a generalized drawHexagon
method would need to be passed the value for the radius as a parameter:
To indicate that the method accepts a parameter, we would use the
following method header:
This header indicates that the method drawHexagon accepts as a
parameter a double value that it calls radius. The new, parametric
version of drawHexagon is shown in Figure 3.7 as extracted from a
complete program (Example_3_3).
Figure 3.7 Parametric drawHexagon Method
Within the method body, the formal parameter radius is used just as a
local variable. In the previous version (Figure 3.5), it was a local variable.
The difference here is that, unlike a local variable, a parameter has a
value when the method body begins—the value passed as the parameter.
We can view this as if an assignment statement occurs as the method is
called, assigning the passed parameter value to the parameter. This is
emphasized by the fact that there is no longer an assignment to the
variable radius. In all other respects, a parameter is just the same as a
local variable. It exists only while the method is executing and the value
is lost when the method terminates, being set to the passed parameter
value the next time the method is called.
Technically, the expression that is used in the method call—the
parameter value passed to the method—is called an actual parameter
or and argument. The variable declared in the method header—the
parameter receiving the value—is called a formal parameter or just
parameter.
Java requires that a method with no parameters be invoked with no
arguments and a method with one parameter be invoked with one and so
on. In addition, since the passing of a parameter is similar to an
assignment, the argument must be assignment compatible with the
parameter (see Section 4.4). When there are two or more parameters,
the types of the corresponding arguments and parameters must be
assignment compatible left-to-right.
Note the extra comment (line 40) in front of the method declaration.
When a method accepts a parameter, the comment specifies the
requirement by a line starting with @param, then the parameter name
followed by a description of the use of the parameter. This informs the
Java documentation program (JavaDoc) of the parameter usage when it
generates on-line documentation such as that for the Brock libraries we
discussed in Section 2.2.
DRAWING A BEACH UMBRELLA
Figure 3.8 A Beach Umbrella
Figure 3.9 uses the drawHexagon method (Figure 3.7) to draw a beach
umbrella consisting of eight nested hexagons of sizes from 10 to 150
pixels as shown in Figure 3.8. The constructor has a local variable
radius to store the radius of the hexagon that is to be drawn next. After
creating and preparing the turtle, it initializes radius to the size of the
first hexagon (10 pixels) and then goes through a loop eight times to
draw the eight hexagons. Each time, after drawing a hexagon of the
specified radius, it increases radius by 20 in preparation for drawing
the next hexagon. Since drawHexagon returns the turtle to the original
position, all the hexagons are drawn centered on the same point.
The assignment statement in line 30 is worth considering. Remember, in
Java = means assignment not equality (the statement would be
inconsistent in Mathematics). The execution of an assignment statement
is such that the expression (rhs) is evaluated by obtaining the value of
radius (10 the first time around), adding 20 (giving 30) and then
storing the value (30) into the memory for the lhs (radius). The effect
is to increase (increment) radius by 20.
Figure 3.9 Example—Drawing a Beach Umbrella
The program in Figure 3.9 demonstrates the use of two additional
Turtle operations: t.setPenWidth(width) which changes the pen
nib width to some number of pixels and t.setPenColor(color)
which changes the pen color to the specified color. Normally, the pen
width is one pixel but it can be set to any number of pixels. The usual pen
color is black but it can also be set to any Color value. The package
java.awt (imported in line 5) defines a class called Color which
provides a number of basic colors referred to as BLACK, RED and so on.
Each of these methods affects subsequent drawing operations until the
width or color is changed again. Note that we change the pen width and
color before calling the drawHexagon method. Since drawHexagon
uses the variable yertle declared as an instance variable, these
changes affect the drawing in the drawHexagon method. The
drawHexagon method simply uses whatever pen width and color are in
effect for yertle when it is called.
MEMORY MODEL
In section 2.5 we described the state of two Turtle objects through the
diagram of the status of memory—a memory model. A memory model is
a convenient way to visualize what is going on at a particular point in the
execution of a program. It is an abstraction—ignoring some details and
emphasizing what is of interest—that aids us in understanding a
complex program. An object of a class is represented as a rounded
rectangle, labeled with the class name. Within the rectangle are boxes
labeled by variable names representing storage for the instance
variables. Each method that is active (i.e. begun execution but not yet
complete) is represented by a rounded rectangle labeled by the method
name. Within those rectangles are boxes for each parameter and local
variable, labeled by the variable name. Within the boxes for variables, the
current value of the variable is represented.
Figure 3.10 Memory Model for Umbrella
Figure 3.10 shows a memory model for the Umbrella (Figure 3.9)
object at the completion of line 47 in the drawHexagon method (Figure
3.7) having been called from line 29 in the Umbrella constructor the
second time through the loop (lines 28-31).
The Umbrella object was created in the main method at which time the
constructor began execution. The two instance variables display and
yertle were set in lines 22 & 23 to the Turtle object to do the
drawing and the TurtleDisplayer upon which the drawing is done.
The constructor has a local variable radius which represents the radius
of the next hexagon to be drawn. This started out as 10 (line 27), and
was increased to 30 the first time through the loop (line 30). The second
time through the loop, the method drawHexagon was called (for the
second time) with argument 30 (being the value of radius). The value
of the parameter radius in drawHexagon was assigned this value
during the method call and the body of drawHexagon began execution.
At line 47 the local variable angle in drawHexagon was assigned π/3.
And that is the point at which the memory model in Figure 3.10 is
displayed. Note that the value for side displayed as?, indicating
unknown. When the method begins execution, some cell in memory is
chosen for each parameter and local variable. Since cells in memory
always contain a value, before we first assign a value to a variable, the
value is unknown. When line 48 is executed, a value for side will be
stored.
Note that there are two different memory cells labeled radius one for
the local variable declared in the constructor and the other for the
parameter for drawHexagon. Each has its own value that changes
independently. When code in drawHexagon refers to radius, it is
referring to the parameter. When code within the constructor refers to
radius, it is referring to the local variable in within the constructor. It
isn’t necessary for the argument on the method call to be the same name
as the parameter on the method. In fact, the argument doesn’t even have
to be a variable name (consider line 26 where the argument to
setPenWidth is a constant and line 50 where the argument to right
is an expression). It was simply a matter of convenience that the same
name was used in the constructor and the parameter on drawHexagon.
Any other names would work as well.
When code in either the constructor or the drawHexagon method
refers to yertle, it is referring to the same instance variable yertle.
In the memory model, we basically look from the inside out for variables.
That is, within code for drawHexagon, we look for a variable within the
box for drawHexagon first (finding, for example, radius) and then, if
the variable isn’t there, we look in the encompassing box (finding, for
example, yertle in the box for Umbrella because it isn’t present in
drawHexagon). If radius was not declared (as a parameter) in
drawHexagon, we would fail to find it (because we wouldn’t look in the
constructor) and this would be listed as an error by the complier. We will
discuss the details of which variable is being referenced where in Section
3.6 in the discussion of scope.
3.3
FUNCTION METHODS
In addition to providing procedural abstraction, methods can be used to
compute a value. These methods are like functions in Mathematics and
are called function methods or functions in Java. Function methods are
used when we wish to abstract a computation that would otherwise
result in a complicated expression, or when the computation cannot be
expressed as a simple expression. We will see another common use of
function methods in Chapter 11 when we write accessor methods for
classes.
We have already seen function methods in Section 3.2 when we used
sin from the java.lang.Math library. sin is a method that takes one
parameter (the angle) and computes a value (sin x). This value was then
used in the computation of the length of the side of the hexagon.
Note the difference in the use of a function method and other methods
we have used (procedure method or method) such as drawHexagon.
To perform a procedure method we write the method call as a statement
—a MethodInvocationStatement. We are executing the method for
its effect (i.e. to draw a hexagon). To perform a function method, we
write the method call as (part of) an expression—a
MethodInvocation. We are executing the method for the value it
computes (i.e. the sin of some angle).
WRITING FUNCTION METHODS
To indicate that we are writing a function method—a method produces a
result—we use a different form of a method declaration as shown in
Figure 3.11.
Figure 3.11 Function Method Declaration
The difference between this and a procedure method declaration (Figure
3.6) is that a type is written in the header instead of the keyword void.
The type is the kind of value that the function computes (for example,
double for a number).
Say we are writing a program that draws various regular closed figures
(pentagons, hexagons, octagons, etc.) We would need to compute the
length of a side of each such figure (e.g. line 48 in Figure 3.7 for a
hexagon). Rather than including the code for this computation in each
drawing method, we could abstract it out as a function method and
invoke it where needed, for example replacing line 48 in Figure 3.7 with:
where we compute the length of the side of a hexagon (6 sides) with
given radius.
Figure 3.12 Example—A Function Method to Compute Side Length
Figure 3.12 shows the function declaration which would be included
within the same class as the drawHexagon and other figure drawing
methods. The modifier is private making it a local method. The result
type is double indicating that the result of the computation the method
is abstracting is a double value. There are two parameters: nSides
being an int (integer or counting value) and radius being a double.
The body of the method consists of a single statement—a return
statement. A function method body may, of course, have more than one
statement.
Note the additional JavaDoc comment line (line 67) with the tag
@return. A return tag specifies the return type of the method (double)
and indicates what the value is.
Execution of a function method happens in just the same way as a
procedure method. The parameters are passed and then the first
statement of the method body is executed, continuing with the
statements in turn until the last statement. However, since a function is
executed to produce a value, somehow the function body must indicate
the value produced (returned). This is the purpose of a return statement.
Figure 3.13 Return Statement
Figure 3.13 gives the form of a return statement. The statement begins
with the keyword return followed by an expression and ends with a
semicolon (;). The return statement is just another statement. It can be
placed anywhere within a function method and there can be more than
one return statement within a method. However it is usually the last
statement in the body. The effect of the return statement is to compute
the value of the expression, set this as the return value and then
terminate the method. Any statements following the return are not
executed. The method returns the value computed in the return
statement at the place where the method was invoked. This means that
the function in Figure 3.13, when invoked, solely computes the value of
the expression and returns it as its result.
PATTERN OF METHOD CALL/RETURN
Now that we have seen both procedure and function methods with and
without parameters, it is appropriate to clarify the steps in method
call/return. The steps in method execution are shown in Figure 3.14.
Figure 3.14 Steps in Method Execution
Of course, if there are no parameters, steps 1. and 3. are omitted.
Similarly, if the method is a procedure method, step 5. is omitted. In a
function method, a return statement must be executed and this sets the
return value for step 5 and terminates the execution of the method body.
For a procedure method, the execution of the method body terminates
after the execution of the last statement.
3.4
TESTING AND DEBUGGING WITH METHODS
As our programs get larger, it is not always easy to see what has gone
wrong when they do not work. As we discussed in Section 1.4, we must
effectively test our software and then debug it to remove all errors. In
this section, we will consider some techniques to help with the testing
and debugging of programs containing methods.
First, consider that the entire program or class doesn’t have to be written
and tested all at once! Often it is much better to incrementally develop a
class by writing the instance variable declarations and constructor first.
We then incrementally add methods (local and public) to the class. When
we need to use an abstraction (method), but have not yet written it, we
can write a method stub instead. A method stub is a substitute for a
method for testing purposes. It has the required method header but,
instead of the actual method body, it simply contains a statement to
display the fact that it was called and the values of its parameters. For
example, say we are incrementally developing the Umbrella class
described in Section 3.2. While writing the draw method, we need the
abstraction to draw a hexagon, but have not yet written the
drawHexagon method. We could write a method stub as seen in Figure
3.15.
Figure 3.15 Method Stub
The class System is a standard class, like Math, that provides access to
certain system properties, including the system display console called
out. The system console object has a method println that displays its
parameter as a text string on the console, followed by a line feed, so that
the next display begins on a new line. The parameter to println can be
any number of values, separated by +. The operator +, used in this
context, joins (concatenates) the values into a single line of text. The
values can be variables, expressions, or sequences of text enclosed in
quotes (").
Execution of the Umbrella class with the method stub for
drawHexagon would, in addition to showing a TurtleDisplayer
upon which nothing is drawn, write text to the console as seen in Figure
3.16. The output shows that the drawHexagon method is being called
with the correct parameters (radii 10, 30, … 150). We can now, with
confidence, proceed to write the drawHexagon method knowing the
rest of the program is correct.
Figure 3.16 Console Output
Such incremental development and testing allows us to build a program
knowing that certain parts are working correctly. When a bug is detected
we can concentrate our efforts on the newly added, untested code to find
the source of the problem. We can insert calls to
System.out.println within methods to see what values are being
computed and thus discover bugs. Often, calls to
System.out.println are left in the methods until testing is
complete, even after replacing the method stub with an actual body, to
allow further testing. The calls are removed when the class is considered
complete and working.
3.5
METHODS, SCOPE, AND VISIBILITY
In our programs, we have sometimes used local variables and sometimes
used instance variables. Sometimes variables in different methods have
the same name and sometimes different names. Sometimes the formal
parameter has the same name as the actual parameter and sometimes
not. The rules that sort out the (unique) meaning of a variable, method or
class name are called scope rules. The rules defining where a variable
(or for that matter a method) declared in some declaration can be used
(referenced) within the program are called the visibility rules
(essentially the converse of scope).
JAVA SCOPE RULES
In Java the scope rules are quite simple. To determine which declaration
of a name is being referenced within a piece of code, we follow the
following steps:
1.
Look for a declaration of the name in the for loop, method, or
constructor in which the code resides. If one exists, this is the
defining declaration. This rule applies to both formal parameter
declarations and local declarations.
2. If no such defining declaration exists, apply step 1 again, looking
in the immediately enclosing code unit which could be a for loop
(with nested loops), method, constructor or the class itself.
Continue until there is no enclosing unit. Usually in Java, there is
only the for loop, method/constructor level and the class level to
consider.
3. If no such declaration exists, check the public declarations of
public classes from imported packages. This is how the names such
as Turtle, forward, and PI that are imported from the Media
or java.lang.Math packages are resolved.
4. If no such declaration exists, the name is undeclared and the
reference is in error.
Figure 3.17 Scope Rules
Figure 3.17 shows the scope of the names in the Umbrella program of
Figure 3.9. The extents of the scope of the various declarations are
indicated by the lines. There are seven different scope extents. First, all
public classes, methods and variables of the Media, java.lang.Math
and java.awt.Color libraries have scope over the entire program.
The Umbrella class, its instance variables and methods have scope
including all of the Umbrella class. The local variable radius in the
constructor has scope including all of the constructor. The loop index i
in the constructor has scope of the for loop. The formal parameter and
local variables of drawHexagon have scope including all of the
drawHexagon method. The loop index i in drawHexagon has scope of
that for loop. Finally, the formal parameter and local variable of the
main method have scope of the main method.
Consider the method call on line 42 of the method drawHexagon. There
are references to three names: yertle, forward, and side. Using the
scope rules above, side is resolved to the declaration as a local variable
in drawHexagon (line 34). yertle is resolved to the instance variable
declaration in the Umbrella class (line 10). Finally forward is
resolved to the public method provided by the Turtle class in the
Media package imported in line 3.
Within the for loop in the constructor (lines 22–25), a reference to i is
resolved to the loop index declaration in line 22. Within the loop in the
drawHexagon method (lines 41–44), a reference to i is resolved to the
loop index declaration in line 41. These are different declarations and
hence different variables and thus different storage locations.
The memory model diagram of Figure 3.10 helps clarify the scope issues.
In the memory model diagram, local variables and formal parameters are
placed within the box for the method or constructor in which they are
declared. Constructors, methods and instance variables of an object are
placed within the box for that object. Therefore we can simply trace from
the appropriate box—method in which reference exists—outwards
through the enclosing boxes until we find the first occurrence of the
name.
JAVA VISIBILITY RULES
When coding, we must often make a decision as to where to place a
declaration within the program. Here we are looking at the converse of
scope: visibility. In general, it is desirable to give a name (variable or
method) the most restricted visibility possible that still provides what
we need. That is, it is preferable for a variable to be local or private and
methods to be private. We do this to make large programs easier to
manage. The visibility rules are derived from the scope rules and, for
Java, are:
1.
A loop index declared in a for loop is visible only within the loop
body and any nested for loops.
2. A local variable or formal parameter is visible only within the
method in which it is declared.
3. An instance variable or method declared private within a class
is visible within any constructor or method of that class, unless it is
hidden by a local variable declared with the same name.
4. An instance variable, method or constructor declared public
within a class is visible as in rule 3 but is also visible within any
method or constructor of any class to which the declaring class is
visible.
In deciding where to place the declarations, the declaration of i (the loop
index for the loop within the drawHexagon method) was made local to
the for loop since it was only of concern within that loop and did not
need to be referenced anywhere else. The variable side was declared
local to drawHexagon since it need only be referenced in that method.
The variable yertle was declared as an instance variable (but
private) so that the constructor and the drawHexagon would refer to
the same Turtle object However no code outside the Umberlla class
needs to know about yertle. The method drawHexagon was declared
private since it was only to be used within the Umbrella2 class by
the constructor.
DECLARING NAMES, RULES OF THUMB
There are a number of additional issues regarding scope and visibility
that will be discussed in Section 11.4. For now, we will apply the
following “rules of thumb”, in order of importance, to decide where to
place a declaration.
1.
2.
3.
4.
5.
6.
A for loop index should be declared in the for statement header.
A variable should be declared as a local variable if its value
concerns only the single method or constructor.
A variable should be declared as a formal parameter if the
behavior of the method depends on the value of variable.
A variable should be declared as a private instance variable if it is
part of the state of the object and/or serves to coordinate the
activity of two or more methods or constructors.
A method should be declared as private unless it is to be used by
code in other classes.
A constructor is usually declared as public since objects of the
class will be created by code in other classes.
SUMMARY
A method is a named sequence of code that can be invoked by
referencing its name. A method may take parameters to modify its
actions and may return a result. Methods provide for procedural
abstraction that is, the ability concentrate on the action to be performed
without needing to be concerned with the details of how that action is
accomplished. Abstraction is the primary mechanism to deal with
complexity in systems.
A method is always executed by some object. There are two forms of
method calls. The first explicitly references the object. This is used to ask
other objects to perform an action, such as asking the turtle to draw a
line. The second does not include an object reference. This is used when
the object performs the action itself, that is, for invoking local methods.
Each method has its own local, temporary storage for information it
processes containing values for formal parameters and local variables.
Formal parameters behave as initialized local variables, with the initial
value coming from the actual parameter in the method call. Methods may
also reference instance variables of the class (object).
The scope rules of the language match, to each use of a name (that is,
variable, method or class name), the declaration to which the name
refers. Visibility is the converse of scope which indicates, for each
declaration, where in the code the entity declared is visible. In general,
entities should be declared as locally as possible.
REVIEW QUESTIONS
1.
T
F Abstraction is dealing with complexity by ignoring
irrelevant details.
2.
T
F
3.
T
F A function method is called in a method invocation
statement.
4.
T
F
5.
T
F A private instance variable is visible in all methods of the
class.
6.
T
F The first thing executed in a program is always a
constructor.
7.
T
F
8.
T
F Function methods must always be written after the
constructor and other methods.
9.
Pseudo-code is:
a)
b)
c)
d)
Implicit conversion can occur during parameter passing.
The following is an example of a method header.
Method declarations can only be made inside a class.
a second generation language
an informal notation for an algorithm
machine language of the Z3
none of the above
10. Variables may be declared:
a)
in a method
b)
c)
d)
in a class
in a constructor
all of the above
11. In the following code
a)
b)
c)
d)
x is an instance variable and p is an actual parameter
5 is a formal parameter and r is a local variable
y is an actual parameter and q is a formal parameter
5 is an actual parameter and p is a local variable
12. In the following code:
a)
b)
c)
d)
x and f are both formal parameters
x is the actual parameter and f is the formal parameter
x is the formal parameter and f is the actual parameter
there is no formal parameter
13. Consider the following method declaration:
If the method is invoked as follows:
then:
a)
b)
c)
d)
there is an error because of assignment incompatibility
there is an error due to the wrong number of parameters
x=10 and y=0
a and b
14. Consider the following method declaration:
If the method is invoked as follows:
then:
a)
b)
c)
d)
there is an error because of assignment incompatibility
there is an error due to the wrong parameters types
a=5.0 and b=1
a and b
15. A private method declared in a class is visible:
a)
b)
c)
d)
in the constructor of the class
in the methods of the class
in methods of other classes where the class is visible
a and b
EXERCISES
1.
Modify Example_3_4 (Figure 3.11) to draw 10 concentric
pentagons using a method with header:
The exterior angle for a pentagon is 2π/5 and the length of a side is 2
r sin π/5.
2.
Write a method with header:
which draws a pentagram of specified radius centered on the turtle.
The geometry of a pentagram is:
Write a program that uses the method to draw a pentagram of radius
60 centered on the page.
3.
As we have seen, the basic geometry and drawing process for
regular closed figures (e.g. pentagon, hexagon etc., also known as
regular polygons) is essentially the same. This indicates that a general
method could be written to draw any polygon. Write a method with
header:
which draws a regular polygon with specified number of sides and
radius, centered on the turtle. The geometry of a regular polygon with
n sides is:
Using this method, write a program to draw a triangle centered in
the upper left quadrant of the page, a square centered in the upper
right quadrant, a pentagon centered in the lower right quadrant and a
hexagon centered in the lower left quadrant. Each of the figures
should have a radius of 40 units.
4.
Write a program to use the drawPolygon method of exercise 3 to
draw the following picture:
The birdhouse consists of a triangle, square and pentagon. The sun
should be drawn using a method with header:
The sun itself is a 20-sided polygon (using drawPolygon) of
specified radius. (Note that as the number of sides of a polygon
increase, the figure looks more and more like a circle. this is the way
circles are actually drawn in computer graphics.) The sun is
surrounded by a specified number of rays, which are straight lines of
length radius.
The lines of the picture can be made bolder using the setPenWidth
method and you can even add color using the setPenColor method.
5.
A polyspiral is a spiral-shaped figure consisting of straight lines,
each at a particular angle from the other and each line longer than the
last by some amount (increment). Write a method with header:
which draws a polyspiral, starting at the current turtle position and
direction. The spiral consists of num lines with the first line of length
len. The angle between sides is angle radians and the increment in
line length is inc. For example, the method call:
drawPolyspiral(2,PI/3,2,50) would draw the following
figure:
Write a program that will draw the figure above using
drawPolyspiral. Modify the program to use the call:
drawPolyspiral(1,0.9*PI,2,90). Try some other sets of
parameters.
6.
An epitrochoid is a figure that results from one circle rotating about
another circle with a pen attached to the outer circle. These figures
are the kinds of figures drawn by the children's toy Spirograph™
where one toothed disk (the outer circle) has a hole for a pen and
rotates around the other toothed disk (the inner circle). The figures
are dependent on the radius of the inner circle (a), the radius of the
outer circle (b) and the distance of the pen from the center of the
outer circle (k). In Spirograph™ k is always smaller than b due to
physical limitations, but in general it doesn’t have to be so. Write a
method with header:
which draws an epitrochoid. The method will use the turtle absolute
drawing method moveTo instead of the relative drawing method
forward. The points (x,y) for the drawing are based on a variable
t as follows:
The turtle must be moved to the first point (i.e. t=0) with the pen up
and then num (the last parameter) lines can be drawn (with the pen
down) with t incremented by 1/num each time (i.e. t runs in the
interval 0..1).
For example, the method call:
drawEpitrochoid(50,5,10,100) would draw the following
figure:
Write a program which uses the drawEpitrochoid method to
draw the figure above. Modify the program to make the call:
drawEpitrochoid(20,10,40,100). Try some other values of
your own choice.
4 TYPES AND EXPRESSIONS
CHAPTER OBJECTIVES
Choose the appropriate numeric type to use in a program.
Explain accumulated round-off error
Explain operator precedence and its effect on writing
expressions.
·
Recognize mixed-mode expressions and determine the
conversions that will occur.
·
Declare variables and use them to store results of computations.
·
Determine if an expression is assignment compatible with a
variable.
·
·
·
As we saw in Chapter 1, computers are very good at performing
computations with numbers. In fact, that is about all that the ALU can do!
Everything else that a computer does—from word processing to
animation—ultimately requires that the words, pieces of a picture, or
other information be represented in numeric form as binary numbers or
bit strings. We will consider the representation of a variety of
information, however the first we will consider is the native information
that computers process: numbers.
Numbers can be used for a variety of things, such as counting or
recording measurements. In programming languages there are different
types of numbers (numeric types) for different purposes. The processing
of numeric information involves computation using arithmetic
operations. These computations are represented in programming
languages as expressions using a notation similar to algebra.
4.1
NUMBERS
The computer represents all numeric information in binary form. Binary,
however, is very tedious and error prone for humans to work with. The
compiler comes to our aid. In most programming languages, we
represent numbers in our usual base-10 (decimal) notation and the
compiler handles the conversion into binary.
Computers typically have two different kinds of numeric
representations: fixed-point and floating-point. Fixed-point numbers
are exact values and roughly correspond to Integers in Mathematics.
Floating-point numbers are approximations and correspond roughly to
rational numbers. In Mathematics, these sets of numbers are infinite, but
computer memory is finite. For that reason there is a bound on the size
of both fixed-point and floating-point numbers as well as a limit on the
precision of floating-point numbers.
NUMERIC TYPES
In Java there are four different versions of fixed-point and two different
versions of floating-point numbers. The six numeric types are: byte,
short, int, long, float, and double. These are predefined type
identifiers in the Java syntax. The types, their storage requirements, and
the range of values for each are summarized in Table 4.1.
Table 4.1 Numeric Types
The four fixed-point types (byte, short, int, and long) represent
exact integral (numbers without fractional parts) values in the ranges
given. The most commonly used is int, giving the best combination of
storage space, range, and speed. byte and short are used only in
specialized cases in which very large numbers of integral values of small
range are needed. We will not discuss them further. long is used when it
is known the range provided by int is not sufficient. We will use long
sparingly, for example if we needed to represent the time within the year
in milliseconds. There are 31,536,000,000 milliseconds in a year!
The ranges of values for the floating-point types require some
explanation. The notation used is like that of scientific notation—where a
measurement is written as a fraction multiplied by 10 to some power. In
Java, the notation such as E+38 or e+38 (called e-notation) at the end of
a floating point number means “times 10 to the 38th power”. Thus the
range for float written in e-notation in Table 4.1 is the same as the
following in scientific notation:
-3.40282347×1038 through 3.40282347×1038
Remember that floating-point values are approximations. float has
about 8 digits of precision while double has about 18. Note also that the
possible range of values is much greater for double. Floating-point
values are used whenever we must represent numbers with a fractional
part or whenever very large or very small numbers are possible, as in
scientific computing. We will commonly use the double type in our
programs.
Working with approximations requires some thought. Consider the value
⅓. As a decimal fraction it is: 0.333…—an infinitely repeating fraction.
To do practical arithmetic, we need to choose some number of digits of
precision to work with, say eight. In this case ⅓ would be represented as
0.33333333, an approximation. If we sum this value 3 times we get
0.99999999 not 1! We have accumulated round-off error. This
situation occurs whenever we work with approximations. Each
approximation includes some error and in any computation with these
approximations, the round-off error accumulates making the results
more and more imprecise.
Figure 4.1 shows a Java program demonstrating accumulated round-off
error. Using double variables and values (about 18 digits of precision),
it sums 1/10 ten times and displays the result to the console. The
resulting console display is:
Figure 4.1 Accumulated Round-off Error
What happened? As a decimal fraction, 1/10 is 0.1 and summing it 10
times would be 1.0. However, remember that computers represent all
information in binary and, as a binary fraction, 1/10 is an infinitely
repeating fraction. Summing this binary approximation 10 times yields a
value close to, but not exactly, 1.0. The important point here is not that
binary fractions are inferior to decimal fractions, but rather that it is
always necessary to keep in mind that floating-point numbers are
approximations and that floating-point computations always have the
potential for accumulated round-off error and the results of a
computation should never be considered exact. If exact arithmetic is
required, it is necessary to use other mechanisms (e.g. Java libraries that
provide larger fixed-point numbers or rational arithmetic).
NUMERIC LITERALS
When we need to write an explicit value such as 10 in a Java program, we
use a numeric literal. Each numeric literal has an unique type.
Fixed-point literals are written in the natural base-10 representation as a
sequence of decimal digits optionally preceded by a sign. If the value is
within the range of the int type, the literal is considered to be of type
int. If it is outside this range, it is considered to be of type long. To
write a literal that is within the range for int but to be considered long,
we follow the digits of the literal with the letter l or L. There are no
literals of type byte or short.
Floating-point literals are written as a sequence of decimal digits,
optionally preceded by a sign and followed by either a decimal point and
a number of additional decimal digits or an exponent (in e-notation) or
both. Note that if the sign in the exponent is positive, it can be omitted. If
an f or F follows the literal, it is considered to be of type float;
otherwise, it is considered to be of type double.
Examples of numeric literals and their types are given in Table 4.2.
Table 4.2 Numeric Literals
4.2
EXPRESSIONS
Expressions are used in programming languages to describe numeric
computations. The notation used is similar to that used in algebra.
·
·
·
identifiers are used as variables (similar to single letters
like x in algebra).
literals represent constant values.
operators represent operations.
BASIC JAVA OPERATORS
In Java there are quite a few operators but for the time being we will
consider only the basic ones. The list of basic numeric operators is given
in Table 4.3. Note the use of * for multiplication. In algebra, there are a
number of notations for multiplication including: juxtaposition (for
example, ab means a times b), the ∙ (dot) as in A∙B, and the × as in A×B.
Since early input devices did not support the raised dot or the raised
cross, the symbol * was adopted—being close to a raised cross. Similarly,
there are a number of different notations for division in algebra (/, ÷,
and placing the numerator and denominator on consecutive lines
separated by a horizontal line). In Java, the slash (/) is used.
Table 4.3 Basic Java Operators
Expressions, then, consist of a sequence of operands (literals and
variables) separated by operators and using parentheses for grouping.
For example the algebraic expression:
can be written in Java as:
One further note should be made concerning the operators / and %. In
Java, if you divide one fixed-point value by another, the result is always a
fixed-point value with any remainder ignored (this is called integer
division). For example, 6/2 yields 3 as expected while 5/2 yields 2. It is
possible to determine the remainder on division, using the remainder
operator (%). 6%2 yields 0—6/2 is 3 with remainder 0—whereas 5%2
yields 1—5/2 is 2 with remainder 1). If this form of division is not
desired, it is possible to get a floating-point result via conversion, as we
will see later in the chapter.
ORDER OF OPERATIONS
A question arises in writing expressions: in what order are the
computation done? For example, does the Java expression:
mean that b is to be subtracted from a and then this difference
multiplied by c, as in:
or does it mean that b is to be multiplied by c and then the product
subtracted from a, as in:
Clearly, for most values of a, b, and c, there is quite a difference. As in
algebra, there are rules of operator precedence that make the meaning
clear. Each operator has a precedence level. Higher-level operators bind
to the operands more tightly than lower-level ones. Operators of the
same level bind left to right. This gives an implicit grouping of operators
and operands that can be overridden through the use of parentheses.
The operator precedence levels for the basic numeric operators are
found in Table 4.4.
Table 4.4 Operator Precedence
Table 4.4 tells us that the expression:
would be interpreted, in Java, as the same as:
since * has higher precedence than - and thus multiplication is done
first. If the other meaning of the expression were desired, the grouping
would have to be explicitly indicated through the use of parentheses as:
Table 4.5 shows a number of Java expressions along with the completely
parenthesized form (i.e. the implicit grouping made explicit) and the
equivalent algebraic notation.
Table 4.5 Sample Expressions
MODES OF ARITHMETIC AND CONVERSION
The ALU of the computer can only perform operations on values of the
same type. For example, it can add together two int values or divide
two double values. The result of a computation is also of a particular
type, usually the same as the two operands. This means that every
operand (literal or variable), the result of every operation, and ultimately
every expression has exactly one type. An expression involving all int
operands is an int expression and produces an int value as a result,
and likewise an expression involving all double operands is a double
expression and produces a double value as a result. The type involved
in the expression is called the mode of the expression. For example int
and double, respectively, are the modes in these examples. What
happens if the types of the operands are not all the same?
An expression where all the operands are not of the same type is called a
mixed-mode expression. In such an expression, a process called
conversion is used to change the types of the values involved in an
operation to the same type so that the operation can proceed. The
conversions occur in the order defined by the order of operations. Each
conversion is what is called a widening conversion where loosely, a
value is only converted into a “larger” type (i.e., one that can still
represent the complete value) so that no information is lost. The
conversions are summarized in Table 4.6. More than one conversion may
have to take place to put the two operands into the same type.
Table 4.6 Widening Conversions
byte and short values are always converted to int in any expression,
even if all operands are of the same type. Fixed-point types are converted
to floating-point when the other operand is floating-point. Similarly, the
shorter types (int and float) are converted to the longer types (long
and double) when the other operand is a longer type.
Sometimes the order in which the expression is written makes a
difference in conversion, even though it is irrelevant mathematically. For
example, 4/5*1.5 yields 0.0 (that is, 4/5→0, 0*1.5→0.0*1.5→0.0),
while 1.5*4/5 yields 1.2 (1.5*4→1.5*4.0→6.0,
6.0/5→6.0/5.0→1.2). Remember, the conversions occur as necessary
following the order of operations, which, in this case, is left to right. The
last example in Table 4.5 could not have been written as
4/3*PI*r*r*r since 4/3 will be done using integer division yielding 1
and this would not produce the desired result.
It is possible to force a conversion using a cast. A cast is an explicit
direction to the compiler to cause a conversion. A cast is written by
writing the desired type in parentheses in front of an operand. A cast has
a higher precedence than the operators. This means that the operand is
cast to another type before it associates with an operator. Thus
(double)4/5*1.5 yields 1.2 ((double)4→4.0,
4.0/5→4.0/5.0→0.8, 0.8*1.5→1.2). Since the cast binds first, it
doesn’t apply to the entire expression, therefore (double)1+4/5
yields 1.0 (because (double)1→1.0, 4/5→0,
1.0+0→1.0+0.0→1.0). A cast can also be used to force a narrowing
conversion from a “larger” type to a smaller type. This might possibly
lose some information such as the fractional part in a floating to fixedpoint conversion. Thus, (int)(4.0/5.0)*1.5 yields 0 (because
4.0/5.0→0.8, (int)0.8→0, 0*1.5→0.0*1.5→0.0).
4.3
VARIABLES
As we have seen, computers have a specific hardware component called
memory in which information can be stored. At the hardware level, cells
in memory are referenced via addresses, and the ALU can retrieve the
contents of a cell or store a result into a cell via its address. At the
programming language level, variable identifiers—variables for short
—are used instead of addresses to refer to information stored in
memory. We can think of a variable as a name associated with some cells
in memory that the compiler translates into an address.
Whenever some information must be remembered, a variable identifier
is chosen. Since the programmer makes up this name it must be declared
(defined) using a declaration. This is sort of like writing a dictionary.
Whenever we make up a new word, we must give a definition of that
word. We will, in fact, sometimes refer to the series of variable
declarations in a piece of code as a variable dictionary.
We have seen four places a variable declaration can be placed leading to
four different kinds of variables: instance variables declared within a
class declaration (Section 2.6), for loop indices declared in the for loop
(Section 2.3), local variables declared in a method (Section 3.1) and
parameters declared in a method header (Section 3.2).
Java restricts the choice of identifiers to a sequence of letters and digits,
beginning with a letter. The identifier may be composed of a number of
words, however there must be no spaces in the identifier. The standard
convention is to begin the identifier with a lowercase letter and each
subsequent word in the identifier with an uppercase letter. Java reserved
words such as class, for and int, must not be used as identifiers. (A
complete list of reserved words is found at URL:
http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.9).
A variable can be used to remember a particular kind of information as
defined by the type in the declaration. References to objects can be
remembered by a variable declared using a class name as the type, as
we have in many examples with the declaration of yertle. The numeric
results of computations can be remembered by a variable declared using
a numeric type names such as double as the type. The amount of
memory required to store the information is determined by the type
and is 4 bytes for an object reference and from 1 to 8 bytes, as shown in
Table 4.1 for numeric types.
4.4
ASSIGNMENT STATEMENT
As was discussed in Section 2.5, the statement used to commit something
to memory is called an assignment statement. The lhs (left hand side) is
a variable. It can be any of the four kinds of variables above, however it is
bad form to assign a value to a loop index.. The rhs (right hand side) is
an expression, either a numeric expression as described in Section 4.2,
an object creation expression (Section 2.5) or a function method call
(Section 3.11).
The effect of an assignment statement is to replace the information
currently stored in the memory indicated by the lhs with the information
computed by the expression on the rhs. It is important to remember that
this is a replacement and that any information previously stored in that
variable is lost. This is clear if we remember that a variable is essentially
a name for a cell in memory, that a cell can only hold one piece of
information at a time, and that assignment is the storage of a value in a
cell. The corresponding operation of retrieval does not destroy
information because we are only looking at it, not removing it from the
cell. The use of an identifier as an operand in an expression indicates
information retrieval only.
ASSIGNMENT COMPATIBILITY
As we saw earlier, every expression computes a value of a particular
type. Likewise, every variable can store only a value of a particular type.
An assignment statement is said to be valid if the type of the expression
(rhs) is assignment-compatible with the type of the variable (lhs). If the
assignment statement is not valid, the compiler will indicate this by
issuing an error message. The type of the rhs is assignment-compatible
with the lhs if:
·
·
·
it is the same type.
it is a subtype of the lhs. (A subtype is a “special kind of”
another type such as a poodle is a “special kind (breed) of dog”.)
We will see this later.
if it can be converted to the type of the lhs using a widening
conversion.
Thus the Turtle object reference produced by creation expression: new
Turtle() is assignment-compatible with the Turtle identifier
yertle because they are the same type—Turtle. Similarly the int
expression 80 is assignment-compatible with double variable radius
on line 34 of Example_3_2 shown in Figure 3.5 since in can be
converted by widening conversions from int to double.
Table 4.7 shows a number of valid (assignment compatible) and invalid
(not compatible) assignment statements with a given the set of variable
declarations.
Table 4.7 Assignment Compatibility
Note that widening conversions can occur across an assignment (as in d
= 8;) but narrowing conversions cannot (as in i = i/d), Narrowing
conversions must be done explicitly in the expression before assignment
(as in i = (int)(i/d);). Note also that the conversion across the
assignment happens after the expression has been evaluated, so that d =
8/i; causes 1.0 to be assigned to d. The expression evaluates to an
int using integer division and then the int is converted to double.
The example d = d/i; shows two different uses of a variable. When a
variable is used on the lhs of an assignment, it indicates a location into
which a value is to be stored. When a variable occurs on the rhs, it
represents the value currently stored in the location (that is, a retrieval).
Since the rhs is always evaluated first, this expression replaces the old
value of d (8.0) with a new value (1.6), and the old value is lost.
PARAMETER COMPATIBILITY
In Section 3.2 parameter passing was likened to an assignment
statement. Here, the argument is equivalent to the rhs and the parameter
to the lhs. The argument is an expression and is evaluated. The type of
the result is then compared to the type of the parameter. If the argument
is assignment compatible with the parameter, the parameter passing
occurs. If not, the compiler indicates an error. As with assignment, an
automatic widening conversion can occur on parameter passing. This is
how it was valid to write yertle.forward(40); when the method
forward takes a double parameter and 40 is an int expression. The
int expression is widened to a double before the value is passed.
SUMMARY
Computers are designed primarily to process numeric information. In
Java, this kind of information is represented by the numeric types.
Although there are six numeric types in Java, we will primarily use just
two: int and double. int is used when the information being
represented is a count or a precise value without fraction. double is
used when the information is a measurement (i.e. imprecise) or has a
fractional part.
The numeric computations in Java are written as expressions.
Expressions involve values—represented by literals and variables—and
operations—represented by operators. To precisely define the meaning
of the expression (that is, the order in which the operations are
performed), Java specifies operator precedence, with higher-precedence
operators binding before lower-precedence ones and operators of equal
precedence binding left-to-right.
All expressions, including literals and variables, have a type. A widening
conversion may occur automatically in an expression to ensure that both
operands of an operator are of the same type. A cast may be used to force
a conversion, especially a narrowing conversion which will not happen
automatically.
Information may be remembered by objects using variables, either longterm using instance variables or short-term using parameters, local
variables and for loop indices. The assignment statement evaluates the
expression on the right-hand side and stores the resulting value in the
cell indicated by the variable on the left-hand side. A widening
conversion may automatically occur on assignment to ensure the value
stored is of the type of the variable. When the variable is an object
variable, the value stored is a reference to the object that is referenced
on the right-hand side. When the variable is a numeric type, the actual
value is stored.
REVIEW QUESTIONS
1.
T
F A fixed-point number is an exact value with a decimal
fraction.
2.
T
F
3.
T
F In an expression Java will automatically perform a
narrowing conversion.
4.
T
F A mixed-mode expression involves operands of different
type.
5.
T
F
A cast can cause a widening conversion.
6.
T
F
The following declaration declares an instance variable:
7.
T
F
Retrieval of a value from a variable is destructive.
8.
Which of the following is a constituent of an expression?
a)
b)
c)
d)
9.
Division has higher precedence than subtraction.
operator
variable
literal
all of the above
The expression:
would be written in Java as:
a)
b)
c)
d)
2x-yy/2xx+y
2*(x-y*y)/(2*x*x+y)
2*(x-y*y)/2*x*x+y
2*x-y*y/2*x*x+y
10. The Java expression:
evaluates to:
a)
b)
c)
d)
2.125
4
4.5
4.75
11. Which of the following is a widening conversion:
a)
b)
c)
d)
int to short
int to long
int to double
b and c
12. A variable declared within a constructor is called a(n):
a)
b)
c)
d)
instance variable
local variable
object reference
b and c
13. If a is declared as double, which of the following expressions is
assignment-compatible with a?
a)
b)
c)
d)
7
1 / 3.7
new Turtle()
a and b
EXERCISES
1.
Rewrite Example_4_1 to sum ⅓ three hundred times, ¼ four
hundred times and ⅛ eight hundred times. Comment on the results.
2.
Write a program that will display to the console a table of
temperatures in °F (degrees Fahrenheit) and the equivalent
temperature in °C (degrees Celsius) from -40 °F to 210 °F in
increments of 10 °F. Use the formula:
5 PICTURES
CHAPTER OBJECTIVES
·
·
·
·
·
·
Describe the physical properties of light and human vision that
lead to the computer representation for color.
Explain the representation (digitization) of images.
Use the Picture APIs to write programs to manipulate images.
Apply a while loop in the processing of an image.
Apply an if statement to perform conditional processing.
Understand the notion of color distance.
Computers are used to process many different kinds of data from text to
video. One important area of computing is Computer Graphics—the
study of the creation and manipulation of images, both still and moving.
Computer Graphics is the basis for many important modern uses of the
computer from computer games to visualization to virtual reality.
In this chapter we will consider still images—pictures—as a medium for
manipulation. Free and commercial software—such as Photoshop™—is
commonly used to touch up pictures taken using a digital camera and can
provide many interesting effects. To allow a computer program to
manipulate an image, the image must be represented digitally (i.e.
ultimately as numbers). After examining the properties of light and
human vision, we will consider the most common representation of
pictures as pixels (picture elements) made up of an RGB-triple.
A picture can be modified by manipulating the colors of the pixels in the
image. Since a picture is made up of a collection of pixels, processing a
picture involves sequencing through the pixels using a while loop. Since
some kinds of processing involve making a decision about whether or
not to manipulate a particular pixel, conditional processing using an if
statement is considered.
5.1
REPRESENTING PICTURES
Light is part of the electromagnetic spectrum (Figure 5.1) and has
properties of both waves and particles. The spectrum includes gamma
and x-rays, microwaves, and radio and TV signals. Humans perceive part
of this spectrum—from 370-730 nanometers—as visible light. The
visible light spectrum ranges from violet through indigo, blue, green,
yellow, orange and red (VIBGYOR). Below violet is ultraviolet (UV) and
above red is infrared (IR—radiant heat).
Figure 5.1 The Electromagnetic Spectrum
[5]
The human eye has three types of sensors (cones) which react to
different wavelengths of light. Each cone produces an electric current
when exposed to light. The amount of current depends on the
wavelength of light to which it is exposed and the cone’s activation
potential. Figure 5.2 shows the activation potentials of the three cones
labeled S (short), M (medium) and L (long). For example, blue light
(around 455 nm) would produce a high voltage in the S cone and quite
low voltages in the M and L cones while red light (around 610 mn) would
produce a high voltage on the L cone, a smaller voltage on the M cone
and almost no voltage on the S cone. The brain then interprets these
voltages as color. The eye also contains other sensors called rods. These
have an activation potential that peaks around 500nm (between S and
M). Rods are much more sensitive to light and produce current even at
low light levels. The rods are responsible for low light or night vision.
Since there is only one kind of rod, low-light vision is monochromatic.
Natural light is actually a spectrum of light waves at many wavelengths.
When we pass “white” light through a prism, it splits into its individual
wavelengths producing a rainbow effect. White light is a combination of
all color wavelengths. Black on the other hand is absence of light—no
light of any wavelength. White light will trigger all three cones to their
maximum voltage while black will produce no voltage on any cone
(darkness).
As can be seen in Figure 5.2, the S cone reacts highly to blue light, the M
cone to green and the L cone to red. The brain thus detects color as a
triple of voltage values (R,G,B) from the L, M and S cones. Using the
physical process of the brain as a model, a natural representation for
color is a triple of Red, Green and Blue (RGB) values which we call the
red, green and blue color channels. For computer representation we use
one byte for each channel, scaled to the range 0–255. Thus white would
be (255,255,255) and black would be (0,0,0).
[6]
Figure 5.2 Activation Potential of Cones in Human Eye
Since all three cones produce some output—even if very small—for
every wavelength, a single wavelength light source (say orange around
590 nm) will produce an RGB triple with high R, medium G and small B.
Exposing the same cones to a three wavelength source of red, green and
blue light—each of appropriate intensity—would produce the same RGB
triple. This means that we can produce a color light source that emits
only three wavelengths (RGB) but still simulates every color. If we have
one byte for each channel and thus 256 different intensities for each
channel, we can produce 2563 = 16,777,216 different colors. The human
[7]
eye can distinguish about 10,000,000 different colors .
If the three light sources are close enough together, to the eye they will
appear as one light source rather than three. Color display devices such
as television and computer screens use this principle. The screen is
divided into many small spots—called picture elements or pixels—each
containing three light sources for RGB. Since the three light sources are
close enough together this appears as a continuous color image, due to
the low acuity of the human eye. Figure 5.3 shows a close-up of a color
LCD screen, showing the pixels (groups of three RBG subpixels).
[8]
Figure 5.3 Pixels of an LCD Screen
On the display screen, the pixels are arranged in a two-dimensional
arrangement such as 1024 pixels wide by 768 pixels high. This is called
the resolution of the screen. With a particular size screen (e.g. 10”) the
higher the resolution, the sharper the image because the pixels are
smaller and closer together.
A digital camera works much like the human eye. Inside the camera is an
array of sensors each of which produces voltages for the three color
channels. These voltages are scaled and recorded as an RGB triple. Like
the display screen, the sensors are arranged in a two-dimensional
structure producing a stored image with some resolution such as
3648x2726 = 9,980,928 pixels for a 10 megapixel camera. Of course, with
3 bytes per pixel, this means one such picture requires 29,942,784 bytes
or about 28Mb of storage in “raw” format. Typically, pictures are
compressed to use less storage using formats such as JPG.
For computer processing, therefore, pictures are digitized (represented
in binary form) as a collection of pixels each consisting of three color
channel values for R, G and B. These pixels are arranged in a two
dimensional structure for display with a width and height measured in
pixels (e.g. 640x480). The color channel values are one byte values in the
range 0-255.
5.2
PROCESSING PICTURES
The Media library provides classes to allow the manipulation of pictures
through access to and modification of the color channels of the individual
pixels of the picture. Figure 5.4 shows a simple program that reads a
picture stored on disk and displays it in a window on the screen as
shown in Figure 5.5.
Figure 5.4 Example—Display a Picture
The class PictureDisplayer presents a window on screen in which a
picture can be displayed. In many ways it is similar to a
TurtleDisplayer. Once the displayer has been created (line 20),
pictures can be placed on the display (line 22) making them visible in the
window on the screen. When the close method is executed (line 23),
the user is given the opportunity to view the picture and then press the
Close button to make the window disappear.
The class Picture represents a single picture to be processed. The class
provides methods to determine attributes of the picture (such as width
and height) and access to the individual pixels. The creation expression
(line 21) creates a picture object by loading its pixels from a data file
stored on disk. The Picture constructor presents an open dialog to
allow the user to select a picture file—a file with the .jpg extension.
[9]
Figure 5.5 Picture Display
GRAYSCALE
At night, when light intensity is low, what we see is produced solely by
the rods in the eye. Images have no color, just shades of gray. The
differing shades of gray are based on the luminance (amount of light) —
black being no light through shades of dark gray to light gray to white
being bright light (relatively speaking). In photography, a black-andwhite picture isn’t really just black and white, but rather is also shades of
gray.
In RGB color representation, gray is perceived when we have the same
value on each of the color channels. Black is (0,0,0), light gray (64,64,64),
dark gray (192,192,192) and white (255,255,255). If we want to convert
a color picture to shades of gray—a grayscale image—we need to set
each of the channels to the same value. The question is what value we
should use? Clearly it must be some function of the original channel
values. We will consider an average of the values.
A picture is a collection of pixels, each having values for the three
channels. What we need to do is examine each pixel in turn, obtain the
R,G and B channel values, compute the average and finally reset the R, G
and B values to this average. The algorithm would look like this:
Figure 5.6 is a program to convert a color picture into a grayscale
picture. The constructor creates the displayer for the picture to be
processed (line 16), loads a picture (line 17) and places it on the display
(line 18). It then (line 19) uses the waitForUser method of the
PictureDisplayer class to place a single button OK on the display.
The waitForUser method then waits until the user presses the OK
button before it returns. This allows the user to see the picture before it
is converted.
After the user has pressed OK, the program uses the helper method
makeGray to convert the picture to a grayscale image (line 20). Since
the picture is visible on the screen while the makeGray method is
changing the pixel values, we can see the change on the screen as it
happens. The constructor places a Close button on the display (line 21).
After the user has pressed Close, it saves the picture to disk (line 22)
using a save dialog allowing the user to either replace the original image
or save the grayscale as a new image.
The actual work of changing the picture to a grayscale image occurs in
the method makeGray. It takes a single parameter aPic of type
Picture. Although we have only seen methods that take numeric types
as parameters so far, we know that a parameter can be of any type.
Remembering that parameter passing is like an assignment, the effect of
the method call in line 20 to the makeGray method in line 29 has the
same effect as the assignment:
meaning that the parameter aPic references the same object as pic, the
picture loaded in line 17. In makeGray, any method calls using the
variable pic are calls to that same Picture object and changes of state
(e.g. pixel channel values) are to the state of that picture (see also Section
2.5).
The Media library represents the individual pixels of a picture as objects
of class Pixel. The local variable p (line 31) is used to reference the
pixel currently being processed. The local variables r, g and b are used
to store the red, green and blue color channel values of the current pixel.
The variable v is used to store the average channel value.
Figure 5.6 Example-Converting to Grayscale
As discussed, the algorithm for converting to grayscale involves
processing each of the pixels in the picture. What we do with each pixel is
essentially the same, so we need a loop. Unfortunately, without knowing
how many pixels there are, we cannot use a for-loop like we did in
Section 2.3. Fortunately, Java provides a number of other loops including
the while loop:
A while loop (also called an indefinite or conditional loop) repeats the
body (a sequence of statements) as long as the condition is true. A
condition is an expression that evaluates to a truth (or boolean) value
rather than a numeric value. Essentially it is like a question such as “Are
there any more pixels left to process?” The loop evaluates the condition,
if it is true—the answer to the question is yes—the body is executed. The
loop then evaluates the question again and so on until the condition
evaluates to false (no). At that point, the loop is complete and the next
statement (the one after the } ) is executed.
Lines 37–46 are a while loop that processes all the pixels in the picture
aPic. The method hasNext of the Picture class is a function method
that returns a boolean (truth) value. It returns true if there is at least
one more pixel in the picture that hasn’t been accessed and false when
all pixels have been accessed. The expression aPic.hasNext() thus
evaluates to true (continuing the loop) when there is at least one more
pixel in aPic left to process.
The method next of the Picture class accesses the next pixel in the
picture. It is a function method returning a (reference to a) Pixel
object. Eventually, if the method next is called repeatedly, the last pixel
will be accessed and the next call of the hasNext method will return
false. Thus the combination of the while loop with the condition
aPic.hasNext() and the statement p = aPic.next() sequences
through all the pixels in the picture.
Once the pixel has been accessed, the values (int) of its three color
channels are accessed and stored into the variables r, g and b (lines 39–
41) using the Pixel function methods getRed, getGreen and
getBlue, respectively. The average of these three values is computed
and stored in v (line 42). Finally the color channel values of the pixel—
the actual pixel in the picture—are reset to the value of v using the
Pixel methods setRed, setGreen and setBlue (lines 43–45),
changing the perceived color of the pixel.
When all the pixels have been accessed, the makeGray method
terminates and returns. Since the set methods (lines 43–45) changed
the color channels—the state—of the individual pixels of the picture
referenced by aPic, and aPic references the same picture as pic
placed on the screen in the constructor (line 18), the actual color values
of the picture on the screen have changed. When display executes the
save method of the Picture class, this modified picture is what is
saved.
5.3
THE PictureDisplayer, Picture AND Pixel
CLASSES
Table 5.1 shows the methods of the PictureDisplayer class. When
the constructor is executed, a PictureDisplayer object is created
and made visible as a window on the screen (without a picture on the
canvas). When the placePicture method is executed, the picture is
placed on top of the canvas (and whatever else may be visible on the
canvas). This is like hanging a picture on the wall (the display). The
picture becomes visible in the window with its resolution (dimensions)
displayed underneath. If the picture is large, scroll bars will be displayed
allowing the user to scroll around the picture, seeing different parts of it
in the window.
Table 5.1 The PictureDisplayer Class
The waitForUser method places a button labeled OK on the window
and suspends execution of the program until the user presses the OK
button. This allows the user to examine the picture before the program
continues. Finally, the method close places a button labeled Close on
the window and suspends execution of the program. When the user
presses the Close button, the window is closed, the
PictureDisplayer is disabled and the program continues execution.
The PictureDisplayer should not be used again after close has
been executed.
Table 5.2 shows the methods of the Picture class. There are two
different constructors for Picture objects. The first loads the picture
from a .jpg file. The second creates a new blank (all pixels white)
Picture with specified dimensions.
Table 5.2 The Picture Class
The function hasNext returns true if the Picture has at least one
more Pixel that hasn’t been accessed. If the Picture has no pixels (i.e.
has width and height of 0), it will return false immediately, otherwise
it will return false after some number of calls to the method next. The
method next returns another (the next) Pixel from the Picture, if
there are any that have yet to be accessed. This pair of methods allows a
program to sequence through all of the pixels in a picture. Note that it is
only possible to sequence through the pixels once. Once they have all
been accessed, hasNext returns false and if next is called again, the
result is not defined.
Finally, the method save presents a save dialog allowing the user to
specify a file in which to save the (possibly) modified picture. If a picture
is loaded (constructor) and modified, but not saved, or the user chooses
to save it with a different file name, the original picture on disk is not
modified. That is, the Picture object being manipulated by the
program is a copy of the picture loaded from the file.
Table 5.3 shows the methods of the Pixel class. The getxxx methods
return the current red, green and blue channel values (between 0 & 255)
of the Pixel. The setxxx methods change the red, green and blue
channel values of the pixel, changing the color of that picture element
within the picture. The method getColor returns the color represented
by the pixel’s channel values as an object of type Color (one of the
standard classes within Java). The method setColor sets the color
channels of the pixel to represent the color of the Color object passed
as the parameter. Finally getDistance computes the color distance
(see Section 5.4) between the color of the pixel and a color as
represented by the Color object c.
Table 5.3 The Pixel Class
5.4
CONDITIONAL PROCESSING
In Section 5.2, we saw how to access and modify all the pixels of a
picture. This works well if we want to do the same action for every pixel
(e.g. change it to gray), however we often wish to modify different pixels
in different ways. To do such conditional processing, we need a new
feature in the Java language, the if statement,
THE RED-EYE EFFECT
Consider the problem of red-eye correction in photographs. When flash
photography is used, the light of the flash can reflect from the back of the
eye back through the pupil. The pupils in the picture then appear red as
the light passes through the back of the eye picking up the red color from
the blood supply there. This is known as the red-eye effect. Figure 5.7
shows an example of the red-eye effect.
To correct this in a picture, we need to change the red pixels in the image
to black (the usual color of the pupil). As before, we can sequence
through all the pixels and, if the pixel is red, we can change it to black.
The algorithm might look like:
Figure 5.7 Red-eye Effect
[10]
The question now is “what is red?” Surely the RGB value (255,0,0) would
be red—it is pure red light! But what about (254,0,0) or (255,1,1)? They
are so close to pure red could we tell the difference? What color will
actually show up on the photograph? For this algorithm to work, we
need to determine not if the color of the pixel is pure red, but if it is
“close” to pure red. The term “close” is a relative distance measure. I am
close to my desk if I’m within say 2 meters, but I’m not close if I’m 10
meters away. Euclidian distance on a coordinate system between two
points (x1,y1) and (x2,y2) is computed as:
being the root of the sum of the squares of the difference in the xdimension and the difference in the y-dimension. What is the distance
between two colors? Consider that a color is represented as a triple
(r,g,b). Colors are points in a three dimensional space! The difference
between two colors (color distance) can be computed as the distance
between the colors as points in three-space using the Euclidian distance
formula:
being the root of the sum of the squares of the difference in the red-
dimension, the green-dimension and the blue-dimension. Colors close to
pure red will have small color distance from (255,0,0). Colors distinct
from pure red (say pure green, (0,255,0) ) will have large color distance
from pure red. The Pixel method getDistance (see Table 5.3 above),
computes color distance.
THE IF STATEMENT
The if statement in Java allows us to conditionally perform some action
based on the answer to a question. It has the following form:
The condition, as in a while loop, is an expression that evaluates to a
truth value (a question). The statements are the actions that we wish
to perform if the condition evaluates to true (the answer to the
question is yes). Assuming p is a reference to the pixel, our intent can be
expressed as:
If the distance between the color of the pixel p and the color red is small
enough (less than some tolerance value), the color of p is set to black.
The Pixel function getDistance (see Table 5.3) returns a double
value which is the color distance. The less tan operator (<) compares two
numeric values and computes true if the first is smaller than the second
and false otherwise. Assuming the tolerance is some double value
(say 155.0), the result is the boolean answer to the question “Is the
color of p close to red?”
RED-EYE CORRECTION
Figure 5.8 is a program that does red-eye correction for a picture with
the result shown in Figure 5.9. The constructor (lines 16-28) and is much
the same as in Figure 5.6, creating a display, loading a picture and placing
it on the display, doing red-eye correction on the picture by calling the
method correct (line 24) and finally saving the result.
The method correct (lines 33–44) does the actual red-eye correction
on the pixels of the picture passed as the argument. It uses a while loop
as before to sequence through all the pixels of the picture (lines 37–42).
At each pixel p, it checks if the color distance between that pixel and pure
red is smaller than some tolerance (the if statement on line 39). If so, it
sets the color of the pixel to pure black (line 40).
The color “red” is provided as a Color object—the constant RED used
as the parameter to getDistance. The resulting color
distance value is compared to TOLERANCE (a constant declared in line
12). The color channels of the pixel p are set to black via the setColor
method of the Pixel class (Table 5.3). This method takes a Color
object (an RGB triple) as a parameter and sets the three color channels of
the pixel appropriately.
The constants RED and BLACK are imported (line 4) from the standard
Color class in the java.awt library just we have imported PI from the
Math class previously. They represent RGB triples for pure red and pure
black. Other color constants are also available from the Color class.
Figure 5.8 Example—Red-eye Correction
The condition on the if statement in line 39 uses a constant TOLERANCE
instead of writing literally the value 155.0. It is better form to use a
name for the tolerance value and declare it using a constant declaration
(line 12). The tolerance value of 155.0 was determined experimentally
to get desirable results; it is not an absolute measure of closeness. Should
we need to change the tolerance value, we can change it on line 12 rather
than having to hunt for it throughout the code. A constant declaration
looks like an instance variable declaration, except for the modifiers
static final and the assignment of a constant value to the name.
Constants in Java are by convention written in uppercase (like PI and
RED).
Looking at Figure 5.9, you will notice that the result is not perfect. There
are still some “red” pixels that are not changed. This is because they are
beyond the tolerance selected. The tolerance can be increased, but if it is
increased too much other pixels that are not part of the pupil will be
changed as well (try it!). Note also that in a picture with a lot of other red
in it (e.g. a person in a red sweater) many other pixels will be changed,
changing the sweater to black. In Chapter 8 we will see how to limit our
modifications to a region of the picture, rather that always processing all
of the pixels as we do here. That way we could limit the changes to just
that region of the picture including the eyes and safely increase the
tolerance.
Figure 5.9 Red-eye Corrected
5.5
CREATING A PICTURE
In the previous sections, we have worked with an existing picture,
modifying some or all of the pixels. It is also possible to create a picture
from scratch. If you consult Table 5.2, you will see that there is a second
constructor for a Picture object that takes two integers as parameters
being the width and height (in pixels) of the picture to be created. The
newly created Picture object has all white pixels, that is each pixel has
RGB value (255,255,255). We can then modify some or all of these pixels
to paint any picture we want.
Figure 5.10 is a program to produce a color swatch—a sample of what
some specific color value looks like.
The method makeSwatch takes three parameters: the width and height
(in pixels) of the swatch (a Picture) to be created and a color for the
pixels of the swatch. Since it is not modifying an existing picture (such as
did makeGray and correct from the previous examples), it does not
take a Picture as a parameter. Rather it is creating a new picture so it
is written as a function method that returns an object of type Picture.
Note that function methods can return any type, including references to
objects. In declaring makeSwatch as a function method (line 37), the
class name Picture is used as the return type indicating that the
function will return a reference to a Picture object.
The method creates a new, all white, picture using the second version of
the Picture constructor (line 42). It then sequences through all the
pixels (lines 43–46) in the picture, setting the color of each pixel to the
parameter value (aColor) (line 45). The result is a picture of the
specified size in which every pixel is the specified color.
A function method must end with a return statement that indicates the
value to be returned. Since the method is declared to return a Picture
object, the return statement (line 47) has the local variable result as
the expression. The expression is evaluated and the result (a reference to
the picture object) is returned. This is the reference assigned to pic in
the constructor at line 23.
As was mentioned above, a Color object is an RGB triple. The Color
class has a constructor that takes three integers as the values of the three
color channels and produces a Color object representing this triple.
This constructor is used in line 22 to create the color for the swatch. Note
that the constants R, G and B are declared in lines 10–12. To change the
color of the swatch, it is necessary to modify these declarations. Later we
will see how we can ask the user of the program to provide input values
when the program is run, rather than coding them into the program
itself.
Figure 5.10 Example-Making a Color Swatch
SUMMARY
To be able to write programs to manipulate still images (pictures), it is
necessary to represent the picture digitally. Light is made up of waves of
different wavelengths. The human eye has three cones that produce a
current when exposed to light containing wavelengths close to the
perceived colors red, green and blue. Thus a biologically inspired
representation of color is as a triple of values indicating the intensity of
red, green and blue in the light. This representation is called RGB.
It is possible to present an image as a collection of dots of color.
Photographic film was coated with material that reacts to light to
produce areas of color. Pointillists such as George Seurat painted
pictures by dabbing small dabs (points) of paint on the canvas.
Television screens and computer displays light up small regions of the
screen with colored light to make up an image. Since the human eye has
low acuity, viewed from sufficient distance such images look like
continuous pictures rather than a collection of dots.
A picture then is represented as a collection of pixels (picture elements)
each representing a point of color. The individual pixels are represented
as an RBG triple as three one-byte integers (range 0-255) for the red,
green and blue intensities of the color.
To manipulate a picture the color of the individual pixels is modified. It is
necessary to sequence through all of the pixels. An indefinite loop—the
while loop—can be used along with the methods hasNext and next of
the Picture class, to access the individual pixels. The color channels of
each pixel can be modified to change the picture.
Some forms of processing of pictures require changing only some of the
pixels. The if statement can be used to conditionally perform some
processing based on the value of a condition. A condition is an
expression that evaluates to a truth value—true or false. One example of
such a condition is whether the color of the pixel is “close” to a particular
color. Closeness is a relative distance measure. Color distance is
measured as the Euclidian distance the in three-dimensional space
defined by the three dimensions of the color triple. Closeness can then be
defined as a distance smaller than some value. This processing was used
to decide which pixels were “red” in doing red-eye correction on an
image.
REVIEW QUESTIONS
1.
T
F The “red” cone of the human eye is sensitive only to red
light.
2.
T
F Since the human eye has only one kind of rod sensitive to
low light, human night vision is monochromatic.
3.
T
F
4.
T
F Using a single byte per color channel, we can represent
more colors than the human eye can detect.
5.
T
F On a standard display, a pixel can emit a light of any
wavelength (“color”).
6.
Which RGB-triple represents gray?
a)
b)
c)
d)
7.
(0,0,0)
(255,255,255)
(128,128,128)
all of the above
The method next of the Picture class
a)
b)
c)
d)
8.
“White” light triggers all cones at their minimum potential.
advances to the next Pixel in the picture
returns a Pixel object
has no meaningful value if hasNext returns false
all of the above
How many times is the body of the following while loop executed if
pic has 1000 pixels?
a)
b)
c)
d)
9.
999
1000
1001
none of the above
When imported from the java.awt.Color class, the identifier
RED is:
a)
b)
c)
d)
a Color object
a constant
the color (255,0,0)
all of the above
10. If p is a Pixel of a picture, the result of the following statement is:
a)
b)
c)
d)
a red pixel is changed to black
a pixel that isn’t red is changed to black
the color of the pixel is not changed
none of the above
EXERCISES
1.
Write a program to produce a color negative (such as could be used
in a printing process) of a picture. A color negative is a picture in
which the color channels for each pixel have had their value inverted.
That is, if the red channel value is r, it is replaced by 255-r. The same
is true for the green and blue channels.
Write a method:
that changes all the pixels in aPic to their inverse, producing a color
negative. The program should allow the user to select a picture,
present it on the display, modify it to a color negative and save the
result. For example, if the original image is:
the negative image is:
2.
Write a program to convert an image into a night-time image. For
example the beach image:
converted to night-time looks like:
The night-time effect is created by modifying each pixel’s color based
on how different (far) it is from “sky-blue” (Color(58,117,197)).
For each pixel, the color distance between the pixel’s color and “sky
blue” is computed. Each color channel of the pixel is modified by the
ratio of this distance value and 311 (the maximum distance) such that
“sky-blue” becomes black (the ratio is 0) and the color most distance
from “sky-blue” remains unchanged (ratio is 1).
3.
When a poster is printed, it is sometimes printed with a reduced
color palate, for example instead of 256 levels for each color
component (i.e. 16 million colors), only a small number such as 4
(giving only 64 distinct colors) is used. The effect on the image is
called “posterizing”. For example, the beach picture:
after being posterized would look like:
Write a program that inputs a picture, displays it (waiting for the
user) and then posterizes it to have only 4 levels per color channel.
What we want to do is reduce each color channel from the range 0255 (8 bits) to the range 0-3 (2 bits). We can do this by dividing the
color channel value by 64. However, since our actual display still uses
1 byte per color channel, all values 0-3 will all look very much like
black (very low color intensity). To make it look right, we need to
scale the values back up to the original range (multiply by 64). Note
that, if integer division is used, this means that only 4 color channel
values will occur: 0, 64, 128 and 192, imitating a 2-bit color palate.
6 CONTROL STRUCTURES
CHAPTER OBJECTIVES
Explain the functioning of the common control structures in Java.
Understand difference between a pre-test loop, an in-test loop
and a post-test loop.
·
Understand the difference between if-then, if-then-else and
switch decision structures.
·
Explain how and when to use a definite loop as opposed to an
indefinite loop.
·
Choose the appropriate control structure for a particular
programming situation.
·
·
When a Java program executes, execution begins with the first statement
in the main method of the main class. Once execution begins within a
method or constructor, it proceeds in order through the statements of
the method until the end (or return statement). The method returns to
the place from which it was called and execution continues in that
sequence of statements, and so on, until execution reaches the end of the
main method, at which point the program terminates. This is called
sequential execution. There is another form of execution—parallel
execution—which Java supports via threads. We will not discuss threads
in this book.
Often, we do not want execution to simply proceed from one statement
to the next. Sometimes we wish to loop—execute a sequence of
statements repeatedly—or to make a decision—a choice between
executing a number of different statements. Java provides control
statements (structures) that allow us to perform loops and make
decisions. We have already seen some uses of control statements—the
for and while loops and the if statement. In this chapter we will see
additional loop and decision structures.
6.1
THE WHILE LOOP
One of the most common kinds of loops is one in which we want to
repeat an action as long as a particular situation exists. The while
statement in Java supports this type of loop. It is called an indefinite loop
or conditional loop since we cannot predict ahead of time how many
times the repetition will occur. We used this statement in Chapter 5
when we didn’t know how many pixels were in the picture to be
processed. The form of a while statement is:
The statement is introduced by the reserved word while followed by an
expression in parentheses—called a condition. Next is a sequence of
statements—called the body of the loop—enclosed in braces. The effect
of the while statement is to repeat the sequence of statements some
indefinite number of times—zero or more.
It is the expression that determines how long this repetition
continues. The expression is a special kind of Java expression called a
condition or boolean expression. You will remember from Chapter 1
that the ALU of the computer is capable of performing arithmetic
operations, such as addition and subtraction as well as performing
logical operations, such as comparing two values. In Java and most other
programming languages, these logical operations are called boolean
operations after the English Mathematician George Boole. We will see
more about boolean expressions in Section 6.5. Boolean expressions
result not in a numeric value but in a truth (or logical, or boolean) value:
true or false.
The execution of the while statement is shown in the flow diagram in
Figure 6.1. Repeatedly, the condition is evaluated. If it evaluates to true,
the body is executed and the condition evaluated again. If the condition
evaluates to false, the while statement ends. If the expression is
immediately false, the body of the loop is not executed at all. The body
is always executed in its entirety—if one statement is executed, they all
are.
Figure 6.1 Execution of a While Statement
6.2
THE FOR LOOP
We saw the for statement in Chapters 2 and 3. We used it when we
wanted to repeat a sequence of actions a specific number of times. This
use of the for statement, called the iterative for loop, provides a
definite loop—one for which the number of loop iterations is known or
can be computed before the loop is executed.
The for statement is actually more general that the use we previously
made of it. The form of the for statement is:
As in other loops, statements is called the body of the loop. The loop
repeats the body some number of times as controlled by the forInit,
forCondition, and forUpdate parts. The for statement is defined to
be equivalent in execution to the following while loop:
In execution of the for statement, first the forInit is executed to
initialize the loop. Then, as long as the forCondition is true, the
body is executed, followed by the forUpdate to update for the next test
of the condition. The forInit and forUpdate are actually statements
and forCondition is an expression. Technically any statement can be
used for forInit and forUpdate. Note also that all three may be
omitted. If forInit or forUpdate is omitted then there is no
initialization and/or update in the resulting loop. If forCondition is
omitted, it is treated as true, creating an infinite loop—one that never
terminates—unless some other statement is used to terminate the loop
from within the body (see Section 6.4). The execution of the for
statement is shown in Figure 6.2.
Figure 6.2 Execution of a For Statement
The iterative for loop we have used previously is equivalent to:
The forInit is used to declare and initialize the loop index. The
forCondition is the condition for continuation of the loop For the
forUpdate we have used i++. Clearly this must be a statement and in
Java it is actually a shorthand for the statement i=i+1. This shorthand
should be used sparingly and the usual longhand used in most other
cases.
The iterative for statement has greater flexibility than we have used so
far. The initial value of the loop index may be determined in any way, and
the index updated as appropriate. It may be incremented or
decremented, possibly by a value (often called the increment) other than
1.
Because of the equivalence of the for and while loops, many
programmers—especially those raised on C—use a for as a shorthand
for a while. Unless the loop is clearly a definite loop, this practice should
be avoided and a while loop written instead. It is misleading to see the
keyword for, which implies a definite loop when the loop is really
indefinite. The index should be initialized in the forInit, tested in
forCondition, and updated in forUpdate. The index should not be
modified within the body.
6.3
THE IF STATEMENT
To be useful, computer programs must react to user needs. The user
must be able to indicate what she wishes to do, and the program must
respond. This means that as a result of user input the program must
make a decision about what steps to follow next. Decision structures
are the control structures that allow this to happen.
The most common kind of a decision structure is the if statement. It has
two forms, one (sometimes called the if-then) allows selective execution
of a group of statements. It allows the program to do the statements one
time or not at all. A second form (often called the if-then-else) allows a
choice between execution of two different groups of statements. It allows
the program to choose to do one or the other group of statements once.
The form of the if-then is:
As in the while statement, the expression must be a boolean
expression (or condition) and thus evaluates to either true or false. If
the condition evaluates to true the sequence of statements—called the
then-part—is executed. On the other hand, if the condition is false the
then-part is not executed—nothing is done. In either case, the statement
following the if statement—after the close brace—is executed next as
usual. This is shown in the flow diagram in Figure 6.3.
Figure 6.3 Execution of an If-then Statement
The form of the if-then-else is:
Again the expression must be a condition. If the condition is true, the
first set of statements (statements1)—again called the then-part—is
executed. If the condition is false, the second set of statements
(statements2)—called the else-part—is executed. After the execution
of either the then-part or the else-part, the statement following the if
statement is executed. This is shown in Figure 6.4.
Figure 6.4 Execution of an If-then-else Statement
6.4
OTHER CONTROL STRUCTURES
Java includes a variety of additional control structures, most of which are
used much more rarely than those already discussed. These include two
additional loop structures (do and for-each), an additional decision
structure (switch) and two statements (break and continue) that affect
the execution of a loop or control structure and can be used to create
additional control structures not provided in the language. We will see
the for-each statement in Chapter 8. We will not discuss the continue
statement as it is almost never used.
If you consider an indefinite loop, the body is executed some number of
times as determined by the condition. There are three places that the
condition can be tested and the loop terminated: before the body is
executed (called a pre-test loop), after the body is executed (called a
post-test loop) and at some point within the execution of the body
(called an in-test loop). The while statement is the pre-test loop and
executes the body zero or more times. The do statement is the post-test
loop and executes the body one or more times. There is no in-test loop in
Java, however one can be constructed using a for statement, an if
statement and a break statement.
An if-then-else allows the choice between two alternatives. If we need to
choose between more than two, we can either nest if statements (i.e.
write an if statement as the then-part or else-part or both of another if
statement). Java also includes a switch statement which, when coupled
with the use of a break statement, allows the choice between one or
more alternatives depending on the value of an integer expression.
THE DO STATEMENT
The do statement is Java’s post-test loop, in which the test for loop
termination is after the body. It guarantees that the loop body is
executed at least once and so is used whenever it is necessary to execute
a sequence of statements one or more times. The form of the do
statement is:
As in a while statement, the statements are called the body and the
expression is a boolean expression called the condition. The do
statement executes the body and then tests the condition. If the
condition is true, the body is executed again as is the test and so on,
until the condition is false. Then the next statement—the one
following the ;—is executed. This is shown in Figure 6.5.
Figure 6.5 Execution of a Do Statement
THE BREAK STATEMENT
The break statement, although not technically a control structure, allows
us to construct additional control structures such as an in-test loop. The
form of the break statement is:
When executed, the break statement causes immediate termination of
the encompassing loop or other control structure such as a switch. This
is unconditional unless coupled with an if statement.
MANUFACTURING AN IN-TEST LOOP
To write an in-test loop, we need a loop structure in which the
continuation condition is always true and the loop will always continue.
The loop body contains a break statement that is executed when we wish
the loop to terminate—to break out of the loop. It could look like the
following:
The statements1 and statements2 together make up the loop body
and the expression is a condition. The for statement has no forInit,
forCondition or forUpdate. As described in Section 6.2, when
these are omitted, the forCondition is considered to be true and the
loop is an infinite loop. After first part of the body (statements1) has
been executed, the if statement tests the termination condition—the
condition on which to terminate as opposed to a continuation condition
as in the while, for and do. If the termination condition is true, the
break statement is executed, terminating the loop and causing the
statements following the } to be executed. If the termination condition is
false, the break is not executed and statements2 is executed
followed by statements1 and the if statement, and so on until the
termination condition evaluates to true. The result is that
statements1 is executed one or more times and statements2 is
executed zero or more times. The execution of an in-test loop is shown in
the flow diagram in Figure 6.6.
Figure 6.6 Execution of an In-test Loop
The if statement in the middle of this loop is a variation on the if-then
statement described in Section 6.3. When the then-part of the if-then
consists of only one statement (here a break statement), there is no
requirement to include the braces around the then part. Generally this is
not a good idea since, at a later date it may be determined that more than
one statement is needed but the individual may neglect to add the
braces, causing a bug. In this special case, it is appropriate since loop
termination is the only desired action. For compactness, the break is
written on the same line. Note also that the if is outdented to the same
level as the for. This makes it easier to locate the termination test,
especially if the loop contains a number of statements, possibly including
other if statements.
THE SWITCH STATEMENT
The switch statement is a decision structure—like the if statement—
except that it chooses between any number of alternatives. Since the
number of alternatives is both finite and discrete, the choice is based on
an integer expression as opposed to a boolean expression, where there
are only two possible alternatives.
The form of a switch statement to choose between n alternatives is:
The expression must be an integer expression. The valuei are
integer literals or constants (e.g. 1) and must be distinct. The expression
is evaluated. If the result is equal to valuei, statementsi is executed
and the following break statement exits the structure, continuing
execution with the statement after the final }. If the result does not
match any of the valuei, statementsn+1 (after the keyword
default) is executed followed by the break statement and continuing
with the statement following the final }. The execution of this form of the
switch is shown in Figure 6.7.
Figure 6.7 Execution of a Switch Statement
Java does not require the break statements to be included within the
switch statement. If there is no break at the end of a case, the execution
just continues with the next written case and so on until either the end of
the switch is reached or a break is executed. Typically this is a bug—the
cases are intended to be mutually exclusive. In many languages the case
statement (the rough equivalent of Java’s switch) ensures mutual
exclusivity of the cases and thus does not require the inclusion of a break
statement. Using the switch without the breaks is an undesirable
practice.
The sequence beginning with the keyword default can be omitted. In this
case, if the value of the expression does not equal any of the cases, none
of the statementsi is executed and it is as if nothing occurred. It is
good practice to always include the default clause unless it can be proved
that the expression must match one of the cases.
In an object-oriented language such as Java, switch (or case) statements
are infrequently used as there are better techniques to handle most
multi-way alternatives. The most common use of the switch is to choose
between the different responses a user could make to a dialog, for
example, doing different things depending on which of n buttons is
pressed.
6.5
THE BOOLEAN TYPE AND BOOLEAN EXPRESSIONS
As we have seen there are expressions in Java—called boolean
expressions—which compute truth values as their result. Since we know
that every expression has a type, there must be a truth value type in Java.
This type is called boolean—after George Boole, an English
Mathematician.
Boolean values are limited to the two possible truth-values: true and
false; hence boolean values require only one bit for representation,
however Java uses 1 byte since this is the smallest addressable memory
unit on most computers. The words true and false are reserved
words in Java as boolean literals. That is they are literal representations
of truth values, just as 1 and 2 are literal representations of integer
values.
A variable (instance or local or a parameter) can be declared to store a
boolean value and an assignment statement used to set its value:
The first line declares tryAgain as a boolean variable capable of
storing a truth value. The second line assigns the truth value true to
tryAgain. The third line computes the boolean expression yielding
true when i is less than or equal to 10 and false otherwise. This
truth value is assigned to tryAgain. As always, assignment
compatibility is required across an assignment statement. Boolean
literals, boolean variables and boolean expressions all produce values of
type boolean and are hence assignment compatible to boolean variables.
No other types are assignment compatible to boolean.
The while statement demonstrates that a boolean variable is a boolean
expression or condition. The effect is that if the value of tryAgain is
true, the while statement will execute its body and the value of
tryAgain will again be checked. For this not to be an infinite loop, some
statement within the body would have to modify tryAgain.
BOOLEAN EXPRESSIONS
A boolean expression is any expression that evaluates to a boolean
value, just as an int expression is one that evaluates to an int value.
Boolean literals and boolean variables are the simplest forms of a
boolean expression. However, as we have seen, there are more complex
expressions involving special operators, which evaluate to boolean
results. One such set of operators called the relational operators can be
used to compare two values. The relational operators are enumerated in
Table 6.1.
Table 6.1 Relational Operators
Note the form of the operators. They must always be written as shown,
with no extra spaces; that is <= is written, not => or < =. Note also the
unusual notation for equality (==). This is to distinguish it from
assignment (=). The use of the ! in the not equal to operator (!=) is
consistent within Java with ! meaning not.
Numeric values can be compared for any of the six possible relationships
between ordered values. As for arithmetic operations, conversion is done
as necessary (widening only) to put the two operands
Note that the equality and non-equality operators can be used with any
type of operands. This means that we can compare not only numeric
values for equality but also boolean values where equality means both
true or both false. It is also possible to compare object values for
equality, where equality means referencing the same object (See also
Section 2.9).
It is important to understand the difference between the comparison of
values of primitive or value types (numeric, boolean) and the
comparison of object references. Comparison of value types compares
the actual values; this is called value equality. Comparison of object
types, on the other hand, compares the references. It sees if the two
reference variables refer (point to) the same object. This is called
reference equality. In considering comparison of objects, think of
identical twins. They may look identical, but they are different people.
The same is true for objects. Two different Turtles or Pictures may
appear identical, however they are different objects. Object comparisons
are consistent with this idea. The two Turtles or two Pictures would
not be considered equal by the equality operators.
In addition to the relational operators that apply to numeric and other
operands to produce a boolean result, there are also boolean operators
that apply to boolean operands—literals, variables and other
expressions—and produce boolean results. The boolean operators are
listed in Table 6.2.
Table 6.2 Boolean Operators
The easiest way to understand the boolean operators is to use truth
tables. A truth table is like an addition or subtraction table—it shows
the possible values of the operands and gives the result of the
expression. Since boolean variables have only two possible values, it is
possible to write the complete table, unlike the table for addition which
would contain an infinite number of integer values. Table 6.3 gives the
truth table for not (!).
Table 6.3 not Truth Table
The first column is the value of the operand, here represented by the
boolean variable b. The second column is the result of the expression !
b using T for true and F for false. As can be seen, not inverts the value
of its operand so that when b is true, ! b is false and vice versa.
Table 6.4 and Truth Table
Table 6.4 gives the truth table for and (&). The first two columns give the
values of the operands represented by the boolean variables a and b.
Note that & takes two operands (like *) while ! takes only one operand.
Operators that take two operands are called binary or dyadic operators
whereas operators that take only one operand are called unary or
monadic operators. The last column gives the result of the expression a
& b. Note that & yields true only when both of its operands are true,
and it yields false otherwise. This is consistent with the usual meaning
of “and” in English, meaning that both statements are true. “He is rich
and famous.” is a true statement in English only if the person being
referred to is both rich and famous.
Table 6.5 or Truth Table
Table 6.5 gives the truth table for or (|). Note that | yields true when
either of its operands is true, and it yields false otherwise. This is
consistent with the usual meaning of “or” in English, that one or the
other statement is true. “He is rich or famous.” is a true statement if the
person being referred to is either rich or famous or both rich and famous.
The two other boolean operators are special forms of and and or, called
short-circuit or McCarthy operators, after the designer of LISP, the
first language in which so-called lazy evaluation was used. Usually the
way an expression is computed is that the two operands are first
evaluated by simply obtaining their values if they are literals or variables
or by evaluating the sub-expressions if they are expressions. Then the
operation such as addition or subtraction is performed. For and and or,
however, this sequence isn’t always necessary. Look at the last two lines
of the truth table for and. When a—the left operand—is false the value
of b—the right operand—doesn’t matter; the result is always false.
Similarly, the first two lines of the truth table for or reveal that when the
left operand is true, it doesn’t matter what the value of the right
operand is; the result is always true. This means that for and when the
left operand is false, it is not necessary to evaluate the right operand at
all because the result is already known to be false. For or if the left
operand is true, it is unnecessary to evaluate the right operand and the
result is true. The so called short-circuit operators take this approach.
Having evaluated the left operand, they only evaluate the right operand
when necessary, hence the term lazy evaluation.
Table 6.6 Short-circuit and
Table 6.6 shows the short-circuit and (&&, again no spaces) and Table 6.7
shows the short-circuit or (||). The use of – in the b column indicates a
“don’t care” condition; in other words, the operand is not evaluated.
Otherwise, the tables are the same as those for the usual and and or.
Table 6.7 Short-circuit or
Normally we will use the standard forms (& and |). However, there are
some situations in which there could be a problem if the right operand
were evaluated, such as causing the program to crash. Often a properly
written short-circuit expression can provide a good solution. Some
programmers use the short-circuit operators in preference to the normal
operators in belief that they result in more efficient code. This is not
necessarily the case (unless the right-hand operator involves significant
computation) and doesn’t usually warrant the reduction in ability to
reason about the resulting code.
OPERATOR PRECEDENCE
We have introduced a number of new operators, so it would be
instructive to reconsider operator precedence as previously described in
Section 4.2. The operator precedence levels including the relational and
boolean operators are shown in Table 6.8. There are still more operators
and additional levels of precedence in Java, but we will not discuss them
in this book.
Table 6.8 Operator Precedence
As usual, parentheses can be used for grouping to affect any evaluation
order desired. However, the precedence levels have been carefully
chosen so that most common expressions do not require extra
parentheses. Some examples of expressions and their evaluation order
using complete parenthesization are given in Table 6.9.
Table 6.9 Example Expressions
In the first example, to negate the sub-expression a<b, the subexpression must be in parentheses since the precedence level of ! is
higher than <. Of course, this could also be written as a>=b. Since the
arithmetic operators have higher precedence than the relational
operators, arithmetic expressions can be compared without resorting to
use of parentheses. Similarly, since the boolean operators have lower
precedence than the relational operators, it is not necessary to use
parentheses when combining relational and logical operators (with the
exception of !). Note the use of the short-circuit and in the fourth
example. When a is zero the value of b/a is undefined. The program will
crash if b/0 is evaluated. The short-circuit operator prevents the
evaluation of b/a exactly in the case where it would be a problem, and it
still produces the desired result. In the last example (assuming d is
double), the value of d is converted to int because of the cast. The
conversion occurs before the division, resulting in integer division. Thus,
when the whole part of d is even, the arithmetic sub-expression yields 0
and yields 1 if the whole part of d is odd. Hence, the entire expression
tests to see if the whole part of d is even.
DE MORGAN’S LAWS
Before we leave boolean operators, there is one last issue to discuss: de
Morgan’s laws, after the logician de Morgan who first described them.
These define the interpretation of complex expressions involving the
boolean operators and and or with not. de Morgan’s law for and (stated
as a Java expression) states:
and de Morgan’s law for or states:
Table 6.10 demonstrates de Morgan’s law for and using a truth table and
Table 7.11 shows de Morgan’s law for or.
Table 6.10 de Morgan’s Law for and
Table 6.11 de Morgan’s Law for or
See that in both cases, the right-hand two columns are equivalent. de
Morgan’s laws define the correct way to negate the expressions a&b and
a|b. When expanding the expression and distributing the ! through the
expression, & changes to | and | to &. de Morgan’s laws often become
important when writing while loops. Sometimes the thing that is evident
is the termination condition for the loop—the condition that is true
when the loop is to terminate. However, the while statement requires a
continuation condition. Recall that this is the condition under which the
loop is to continue, and is the opposite or logical negation of the
termination condition. When the termination condition is complex, for
example, involving & or |, it is important that we know the correct way
to negate it to write the continuation condition.
6.6
TESTING AND DEBUGGING WITH CONTROL STRUCTURES
Before we introduced control structures, there was only one path
through a method or program. To ensure a method was tested, all we had
to do was to make sure it was executed. Control structures introduce
alternative paths. Now it is possible that executing a method will not
completely test it if one of the alternative paths is not followed.
This introduces a new complication in testing. All paths through a
program must be tested. When designing the test data for a method, we
must consider all conditions including loop conditions, if conditions or
switch cases, and ensure that the data we choose for testing guarantees
that each path is executed.
One common error in programming is making an error at the boundary
of a condition, for example using > instead of >=. For this reason, it is
desirable to include a test that is right on the boundary of each condition.
One final test is needed for loops—that the program works when
immediate exit occurs. In a pre-test loop this means zero executions, in
an in-test loop it means one-half execution where the first part executed
but not the second, and in a post-test loops it means one execution.
In debugging a program with control structures, we usually have to
determine which path is being taken erroneously. If we cannot deduce
the problem from simply looking at the test results, we must try another
technique. One technique is to put a debugging output statement using
System.out.println (see Section 3.5) that uniquely identifies the
path, on each path. The program can then be run with the data values
that fail, and it is possible to determine where the program went wrong
by seeing which incorrect path was taken. Now the program can be
corrected.
Be careful to rerun the complete test suite (all test sets) after any change
is made to the program, in case the change causes a new bug. Of course,
once all tests work successfully, the debugging statements may be
removed.
SUMMARY
Control structures allow a program to adapt to the data presented or to
respond to user requests. There are two kinds of control structures:
loops and decision structures. Loops repeat a sequence of code—the
loop body—some number of times. Decision structures choose between
some number of alternative sections of code, executing at most one of
them.
There are four kinds of loops. The pre-test loop, represented by the while
statement in Java, tests the continuation condition before executing the
loop body. This leads to zero or more executions of the loop body. The
post-test loop represented by the do statement in Java, tests the
continuation condition after executing the body. This leads to one or
more executions of the body. The in-test loop is not represented by a
statement in Java but can be manufactured from a for loop, an if
statement and a break statement. It tests the termination condition in
the middle of the loop body, executing the first-half one more time than
the second-half. The iterative loop, represented by the for statement in
Java, executes the loop body, counting through a sequence of values of
the loop index. The number of times the loop body is executed is
computable before the loop is executed.
The decision structures include two forms of the if statement. The if-then
chooses to perform a sequence of statements or not. The if-then-else
chooses between two alternative sequences of statements. The case
statement which is created from a switch and break statements in Java,
allows the choice between a number of alternative sequences of
statements based on the value of an integral expression.
REVIEW QUESTIONS
1.
T
F A boolean expression is an expression the results in a truth
value.
2.
T
F
3.
T
F The then-part of an if statement is executed when the
condition is true.
4.
T
F
The while statement in Java is an in-test loop.
The statement:
is equivalent to:
5.
T
F
In Java, a pre-test loop is written using the do statement.
6.
T
F “Lazy evaluation” occurs when not all of the sub-expressions
of an expression are evaluated.
7.
T
F In Java the boolean operators not (!) and or (|) are called
unary operators because they consist of one symbol.
8.
In the code:
some statements are executed:
a)
b)
c)
d)
0 times
1 time
10 times
this is an infinite loop
9.
In the code:
some statements are executed:
a)
b)
c)
d)
0 times
1 time
10 times
this is an infinite loop
10. In the code:
some statements are executed:
a)
b)
c)
d)
0 times
1 time
10 times
this is an infinite loop
11. If pumpkin = 1, what is the value of spooks after executing the
following?
a)
b)
c)
d)
1
2
3
6
12. What is the effect of the following?
a)
b)
c)
d)
there is an infinite loop
13 treats and 1 trick
13 treats and 13 tricks
12 treats and 1 trick
13. After the following statements, which of the boolean expressions is
true?
a)
b)
c)
d)
t == u & i != j
t != u | i == j
t == u & i == j
b and c
14. The inverse (boolean negative) of the expression:
is:
a)
b)
c)
d)
i>j & k<j
j<=i | j>=k
i>j | j>k
j>i & k>j
15. Which values of i make the following expression true?
a)
b)
c)
d)
none
6
5, 6, 7
all
EXERCISES
1.
Write a program that prints a numeric triangle to the console
(System.out). For a single digit number, it prints a sequence of lines
each repeating the digit one more time up to the number, and then
each with one less digit back to one, making a triangular pattern. For
example, for the number 5, the program would print:
2.
In the dice game craps, a pair of dice is repeatedly rolled. If on the
first roll the total on the dice is 7, the shooter wins. Write a program
that simulates a number of trials (a constant say 1000) of rolling a
pair of dice and computes and displays to the console (System.out)
the proportion of times that the count is seven. This is an
approximation of the probability of winning in the game of craps on
the first roll. The roll of a single dice can be simulated by generating a
random number between 1 and 6 using the random function of the
java.lang.Math library.
3.
Write a program that computes the square root of a number (a) to
within the degree of accuracy indicated by a tolerance (both
constants). A recurrence relation for square root of a is:
That is the next approximation (xn+1) is computed from the last
approximation (xn) according to the formula. Starting with a first
approximation of a/2, apply the formula until the square of the new
approximation is within the specified tolerance of a.
4.
A prime number is an integer greater than 1 that is evenly divisible
by only itself and 1. The integers: 2, 3, 5, 7, 11, 13, 17, 19 are all
primes. Write a program that lists to the console (System.out) all
the prime numbers up to a maximum (say 100). The first prime is 2.
After that each prime is an odd number (else it is divisible by 2). A
number (p) can be tested to determine if it is prime by dividing by
each consecutive odd number (d) from 3 up to p. If any d evenly
divides p (p%d == 0), the number is not prime.
7 SOUNDS
CHAPTER OBJECTIVES
·
·
·
·
·
·
·
Describe the physical properties of sounds and human hearing
that lead to the computer representation for sound.
Explain the representation (digitization) of sounds.
Use the Sound API to write programs to manipulate sounds.
Apply a while loop to process the samples in a sound.
Explain the execution of a for-each loop.
Apply a for-each loop to process the samples in a sound.
Explain normalization as it applies to sounds.
Many electronic devices—from MP3 players to iPods, tablets and
computers themselves—are used to play music. To make this possible,
sounds (the music) must be digitized—converted into a digital (as
opposed to analog) representation for storage and processing by
computer.
In this chapter we will consider sounds (including music) as a medium
for manipulation. Free and commercial software—such as GarageBand™
and Audacity™—is commonly used to splice, cut and modify recorded or
downloaded sounds. After examining the properties of sound and human
hearing, we will consider the most common representation of sounds as
a sequence of samples of the amplitude of a sound wave.
A sound is modified by manipulating the amplitudes of the samples in
the sound. Sequencing through the samples can be done using the “builtin” iterator and a while loop or using a for-each loop (which uses its own
iterator).
7.1
REPRESENTING SOUNDS
Sounds occur as waves of air pressure much like the ripples on a pond
into which a stone is dropped. The waves radiate out from the source of
the sound and reach the ear where the inner ear transforms them into
electrical impulses sensed in the brain as sound.
Like waves in water, sound waves have crests (increased pressure)
called compressions and troughs (decreased pressure) called
rarefactions. Figure 7.1 shows a single wave or cycle of a sound. The
height of the wave is called the amplitude. The distance (or time) from
start to finish (or crest to crest) is called the cycle length. The
frequency of the sound is the number of cycles per second. The sound
we hear depends on the frequency, amplitude and shape of the waves. A
sine wave is a pure sound having fully regular shape and constant
frequency and amplitude. Natural sounds (such as a bassoon playing a
particular note as shown in Figure 7.2) are more irregular—that is what
makes them interesting to listen to!
Figure 7.1 A Sine Wave
Figure 7.2 Waveform of a Bassoon Playing a Single Note
When we hear a sound it can be quiet or loud. We call this volume.
Volume is related to amplitude in that a sound with greater amplitude
sounds louder. Figure 7.3 shows a louder sound on top and a quieter
sound on the bottom.
What the ear is actually detecting is change in amplitude. Such a change
is measured as a ratio as decibels (dB). Commonly volume is expressed
in decibels as a ratio to the threshold of audibility—the softest sound we
can hear—as 0 dB SPL (Sound Pressure Level). Normal speech is around
60 dB SPL and shouting is around 80 dB SPL. Hearing damage is likely to
occur at 120 dB and the threshold of pain is 130 dB. A 3 dB change
represents a doubling of the volume.
All sounds, regardless of their regularity, have cycles. The frequency or
pitch of a sound is the number of cycles per second measured as Hertz
(Hz). The greater the frequency of the cycles, the higher the pitch. Figure
7.4 shows a low pitch sound on top and a higher pitch sound on the
bottom. The A above middle-C has frequency 440Hz. The standard
western tuning has notes in neighboring octaves with a ratio of 2:1. That
is, A in the octave above middle-C is 880Hz. The human ear can detect
frequencies in the range 2Hz to 22,000Hz or 22kHz. A kilo Hertz (kHz) is
1000 Hz.
Figure 7.3 Volume
Figure 7.4 Frequency
Sounds from different sources (e.g. a flute and a piano) sound different
even if they have the same frequency and amplitude. Natural sounds are
not composed of a single frequency but contain overtones of other
frequencies at lower amplitude. The central (greatest amplitude) tone is
called the fundamental tone. The sound can also vary by attack, fade,
envelope and other characteristics. A music synthesizer allows the artist
to choose waveforms, and other features when constructing a tone.
When a sound is recorded, the microphone measures the air pressure
and produces an electric current. As the amplitude of the sound
increases, the voltage increases resulting in an electric signal varying in
voltage that mimics the sound wave. An Analog to Digital Converter
(ADC) converts the analog electric signal produced by the microphone
into a sequence of digital values by sampling the voltage at fixed
intervals (sampling rate) and recording the voltage value as an integer
sample. Figure 7.5 shows this process.
Figure 7.5 Digitizing a Sound
The sampling rate (number of samples per second) depends on the
maximum frequency we wish to be able to capture. Nyquist’s
[11]
Theorem
states that to capture a frequency up to n Hz, it is necessary
to capture 2n samples per second. Since the human ear detects up to
about 22,000 Hz, we need a sampling rate of about 44,000 samples per
second to capture any frequency that we can hear. Audio CDs are
digitized at 44,100 samples per second.
Each sample is an integral value. The range of values depends on the
sample size—the number of bytes used to record each sample. With a
sample size of 2 bytes, we can capture the range from ‑32,768 to 32,767.
This is the sample size used on audio CDs. To record 80 minutes of music
sampled at 44,100 samples per second with 2 bytes per sample and two
tracks (stereo) we require 846,720,000 bytes or essentially 800Mb.
7.2
PROCESSING SOUNDS
The Media library provides classes that allow the manipulation of
sounds through access to and modification of the samples of the sound.
Figure 7.6 shows a simple program that loads a sound from disk and
then allows the user to play the sound.
Figure 7.6 Example—Play a Sound
The class SoundPlayer presents a window on screen with a button to
be pressed to play a sound as seen in Figure 7.7. Once the player has
been created (line 20), a sound can be placed in the player (line 22)
making it available to be played. When the close method is executed
(line 23), the user is given the opportunity to play the sound and then
press the Close button to make the window disappear.
The class Sound represents a single sound to be processed. The class
provides methods to determine attributes of the sound (such as number
of samples, sampling rate, sample size) and access to the individual
samples. The creation expression (line 21) creates a sound object by
loading its samples from a data file stored on disk. The Sound
constructor presents an open dialog to allow the user to select a sound
file—a file with the .wav extension.
Figure 7.7 Sound Player
MAKE IT LOUDER
As discussed in Section 7.1, the volume of a sound is related to the
amplitude of the sound’s waveform. The samples of a digitally recorded
sound are individual measurements of the height (amplitude) of the
waveform at the sampling intervals. An increase or decrease to the
volume of the sound can be achieved by increasing or decreasing the
amplitude values of the samples of the sound.
A sound is a collection of samples, each having an amplitude value just
like a picture is a collection of pixels each having three color channel
values. To raise the volume, we need to examine each sample in turn,
obtain its amplitude, and reset the amplitude to a larger absolute value.
The algorithm would look like this:
Figure 7.8 is a program to increase the volume of a sound. The
constructor creates the player for the sound to be processed (line 17),
loads a sound (line 18) and places it in the player (line 19). Then (line
20) uses the waitForUser method of the SoundPlayer class to place
a single button OK on the display. This is just like waitForUser method
in the PictureDisplayer. After the user has pressed OK, the program
uses the helper method makeLouder to increase the amplitude of the
sound (line 21) by a factor of 10 (the constant FACTOR declared in line
10). The constructor then places a Close button on the display (line 22)
giving the user an opportunity to play the louder version of the sound.
After the user has pressed Close, it saves the sound to disk (line 23)
using a save dialog allowing the user to either replace the original sound
or save the louder sound as a new file.
The method makeLouder (lines 28–38) raises the volume of the Sound
aSound by an int factor called by. The Media library represents the
individual samples of a picture as objects of class Sample. The local
variable s (line 29) is used to reference the sample currently being
processed. The local variable amp is the amplitude value of the current
sample. The variable newAmp is used to store the new (raised) amplitude
value.
Figure 7.8 Example—Make a Sound Louder
Like the Picture class, the Sound class provides the methods
hasNext and next which, coupled with a while loop allow processing
of all the samples in a sound. This is seen in lines 32–37. As each sample
is accessed (line 33), the amplitude of the sample is obtained via the
Sample method getAmp. The new amplitude is computed (line 35) and
then the amplitude of that sample of the sound is reset to this higher
value (line 36) via the Sample method setAmp. These methods
correspond to the getRed, setRed, … methods for Pixels.
When all the samples have been accessed, the makeLouder method
terminates and returns. The calls to the set method (line 36) change the
amplitudes—the state—of the individual samples of the sound
referenced by aSound. Since aSound references the same sound object
as theSound—which is the sound in the player—(see Sections 5.2 and
3.2) the louder version of the sound is played when the user hits the
Play me button (at line 22). When play executes the save method of
the Sound class, this louder sound is what is saved.
Figure 7.9 Square Wave at Original Volume
Figure 7.9 shows a 440 Hz square wave at original volume. A square
wave is a waveform where amplitude oscillates between +n and –n for
some amplitude n. In this case, the amplitude of each sample is either
+3000 or -3000. At 440 Hz, the value changes 880 times per second
(remember, each wave includes both the positive and negative amplitude
value.)
Figure 7.10 Square Wave at 10x Amplitude
Figure 7.10 shows the resulting waveform after the MakeLouder
program has run. It can be seen that the shape of the waveform and the
frequency (440 Hz) are unchanged. The change is that the amplitude of
each sample is now either +30,000 or -30,000.
7.3
THE SoundPlayer, Sound AND Sample CLASSES
Table 7.1 shows the methods of the SoundPlayer class. When the
constructor is executed, a SoundPlayer object is created and made
visible as a window on the screen (without a sound to be played). When
the placeSound method is executed, the sound is placed in the player.
The sound becomes playable (by pressing the Play me button) and the
number of samples in the sound is displayed.
Table 7.1 The SoundPlayer Class
The waitForUser method places a button labeled OK on the window
and suspends execution of the program until the user presses the OK
button. This allows the user to play the sound before the program
continues. Finally, the method close places a button labeled Close on
the window and suspends execution of the program, again allowing the
user to play the sound. When the user presses the Close button, the
window is closed, the SoundPlayer is disabled and the program
continues execution. The SoundPlayer should not be used again after
close has been executed.
Table 7.2 shows the methods of the Sound class. There are three
different constructors for Sound objects. The first loads the picture from
a .wav file. The other two create a new silent (all samples with
amplitude zero) Sound with specified number of samples. The third
constructor also sets all of the other attributes of the sound (e.g.
sampling rate, sample size) to the same as the parameter sound.
Table 7.2 The Sound Class
The function hasNext returns true if the Sound has at least one more
Sample that hasn’t been accessed via next. If the Sound has no
samples, it will return false immediately, otherwise it will return
false after some number of calls to the method next. The method
next returns another (the next) Sample from the Sound, if there are
any that have yet to be accessed. This pair of methods allows a program
to sequence through all of the samples in a sound. Note that it is only
possible to sequence through the samples once. Once they have all been
accessed, hasNext returns false and if next is called, the result is not
defined.
Finally, the method save presents a save dialog allowing the user to
specify a file in which to save the (possibly) modified sound. If a sound is
loaded (constructor) and modified, but not saved, or the user chooses to
save it with a different file name, the original sound on disk is not
modified. That is, the Sound object being manipulated by the program is
a copy of the sound loaded from the file.
Table 7.3 The Sample Class
Table 7.3 shows the methods of the Sample class. The getAmp method
returns the current amplitude of the Sample. The range of values
depends on the sample size of the Sound. With a sample size of 2 bytes
the range is -32,768–32,767. The setAmp method changes the amplitude
of the Sample. The new value should be valid for the sample size of the
Sound.
7.4
NORMALIZING A SOUND
In Section 7.3, we wrote a program to make a sound louder. If we try to
make a sound too loud, It begins to sound distorted just as if you turn the
volume up too high on your MP3 player. What is happening?
Figure 7.11 shows the result of applying the MakeLouder program
(Figure 7.8) to a 440 Hz sine wave with original amplitude 10,000. Notice
how the tops and bottoms of the waveform have been flattened. The
MakeLouder program took each sample amplitude and multiplied it by
10. Since the original sound had sample amplitudes between -10,000 and
10,000 (the wave had amplitude of 10,000), the result would have
sample amplitudes between -100,000 and 100,000. However, with a
sample size of 2 bytes, the range for the sample values is just -32,768 to
32,767. When an attempt is made to set a sample value (using setAmp)
to a value outside this range, the value is limited to the maximum
(minimum) value of the range (i.e. -32,768 or 32,767). This is called
clipping and is the same thing that happens in an audio amplifier when
you set the volume control too high. The resulting amplitude is limited by
the power rating of the amplifier—the amplitude can go no higher!
Similarly, in the digital representation of the sound, each sample
amplitude is represented by 2 bytes and there are not enough bits to
represent values greater than 32,767 (or less than -32,768).
Figure 7.11 Sine Wave at 10x Amplitude
Let us write a program that makes a sound as loud as possible without
clipping. This is called normalizing the sound. To do this, we need to
multiply each sample amplitude by a factor that will increase the largest
amplitude to the maximum amplitude for the sample size. By largest, we
mean the largest absolute amplitude value. We don’t care whether that
value is positive or negative.
FINDING MAXIMUM AMPLITUDE
In processing a sound, we see the samples in sequence. At any time
during examination of the sequence of samples, some sample must have
the largest amplitude of those examined. Let’s call that value maxAmp.
When we see the next sample there are two possibilities. If the amplitude
of the next sample is greater than maxAmp it is the largest so far and
becomes the new value for maxAmp. Otherwise maxAmp is still the
largest value so far. When all samples have been examined, maxAmp will
represent the largest value.
How do we start the process? Before we examine the first sample, what
value should maxAmp have? Consider that, after examining the first
sample, maxAmp should be the amplitude of that sample. That means
that before this, maxAmp must have a value that is guaranteed to be
lower than any other sample amplitude. Since we are talking about
absolute values here, the smallest amplitude will be 0. Thus any value
less than zero (say -1) can be our initial maxAmp value.
The process of finding the maximum (or minimum) of a sequence of
values is very common in Computer Science. The algorithm (as described
above) can be generalized as:
where max is the maximum value in the sequence and small is a value
smaller than any value expected in the sequence. To find the minimum,
the initial value is chosen larger than any expected value and the test is
changed to “smaller”.
SEQUENCING TWICE THROUGH THE SAMPLES
To normalize the sound we need to sequence twice through the samples
in the sound: once to find the maximum amplitude and once to actually
increase the amplitude of each sample. These must be done in sequence
since we do not know the factor to increase the amplitude of the first
sample until we have found the maximum of all samples. Unfortunately,
the combination of hasNext and next we have been using to sequence
through the samples in a sound can only be done once—once we reach
the end we cannot restart at the beginning!
A Collection in Java is a class that contains or is made up of a number
of objects. Since Sound is made up of a number of Samples, it is defined
as a Collection. Java has a special construct for sequencing through
all of the objects within a Collection called the for-each statement:
type is the type of the objects within the collection (e.g. Sample).
expression is an expression that results in a Collection object (e.g.
a Sound variable). The statements are executed a number of times
with variable referencing each of the objects contained in the
collection, in turn.
The statement creates a new iterator over the specified collection. An
iterator is an object that is used to sequence through a collection. It
provides two methods hasNext and next similar to the methods
deified for Sound (Sound essentially has a built-in iterator). The foreach statement, like the for statement, is defined as the equivalent of a
while statement as:
To sequence through all of the Samples in aSound, we can use the foreach statement:
Note that the variable in the for-each (s above) is similar to the index
variable (e.g. i) in the iterative for statement. It takes on each of the
values of interest (samples of the sound) on successive passes through
the loop.
WRITING THE PROGRAM
Figure 7.12 shows a program to normalize a sound. The constructor
is essentially the same as for the MakeLouder program, calling the
method normalize to convert the sound to its normalized form.
The normalize method implements the “find maximum” algorithm
(above) to find the largest (absolute value) amplitude of any sample in
the sound (as maxAmp) (lines 34–40). It uses a for-each loop (lines 35–
40) to sequence through the samples (as s) of the sound. For each
sample, it determines the absolute value of the amplitude (line 36) and
then updates maxAmp if this value is the largest so far (lines 37–39).
Once the maximum amplitude is found, the method computes the factor
by which each sample must be increased to bring the sound to its
normalized amplitude (line 41). CLIP_AMP is a constant (line 11) that is
the largest amplitude that can be represented (32,767 for a 2 byte
sample size). By dividing CLIP_AMP by the maximum amplitude found,
we get the factor. Note that factor is declared as double to ensure
that we reduce round-off error. The computation involves a cast since
the two values divided are integers.
Figure 7.12 Example—Normalize a Sound
Finally, normalize calls a modified version of the makeLouder
method from Figure 7.8 to actually change the samples in the sound. This
new makeLouder method takes a double factor (by) instead of an int
one (again to avoid round-off error). It also uses a for-each to sequence
through the samples of the sound for a second time (lines 49–52),
updating the amplitudes. Note the cast in line 51. Since by is now
double, we need to cast the result (after the multiplication) to int
because the amplitude of a sound is an integer.
Figure 7.13 shows the result of normalizing the 440 Hz sine wave with
original amplitude 10,000. The factor computed was 3.2676
(32767/10000) so that the maximum amplitude generated is 32767 and
clipping does not occur. Compare this with Figure 7.11 which used a
factor of 10 and encountered clipping.
Figure 7.13 Normalized Sine Wave
In general is safer to use for-each to sequence through the samples
rather than the built-in iterator. When writing the method, we cannot be
sure that this will be the first time iterating through the sound (in fact it
isn’t in this case). The method is more portable if it doesn’t have this
extra constraint—that the sound cannot have been previously iterated.
SUMMARY
To be able to write programs to manipulate sounds, it is necessary to
represent the sound digitally. Sounds are pressure waves in the air that
the human ear detects as sound. A microphone converts the air wave
into an analog electric signal varying in voltage as the wave in the air
varies in amplitude (height). An analog-to-digital decoder samples the
electric current at intervals producing a series of sample amplitudes
(integers) representing the height of the sound at the sampling instant.
The reverse process, using a digital-to-analog decoder can reproduce the
electrical current that can be fed through an amplifier to speakers
reproducing the air wave (the actual sound).
A sound then is represented as a collection of samples each representing
the height (amplitude) of the wave at an instant of time. To manipulate a
sound the amplitude of the individual samples are modified. It is
necessary to sequence through all of the samples. As for pictures, a while
loop can be used with the built-in iterator methods hasNext and next
of the Sound class. Alternatively, since a Sound is a Collection, a foreach loop can be used to access the samples. The built-in iterator can
only be used once for a sound, however the for-each loop can be used as
many times as necessary since it uses its own iterator, and is generally
preferred.
REVIEW QUESTIONS
1.
T
F The cycle length in a sound is the distance (time) between
crests of the sound waves.
2.
T
F
The volume of a sound is determined by the cycle length.
3.
T
F
The amplitude of a sound is the number of cycles per
second, measured in Hz.
4.
T
F
5.
T
F A Sample of a sound contains 1 amplitude value in a
monaural recording, 2 amplitude values in a stereo recording or
4 amplitude values for a surround sound recording.
6.
The storage required for a digitized sound depends on:
a)
b)
c)
d)
7.
the sample size
the highest frequency to be recorded
the duration of the sound
all of the above
In the following code, if aSound has 1000 samples how many times
is the second loop executed?
a)
b)
c)
d)
8.
The human ear typically cannot hear sounds above 22kHz.
0 times
999 times
1000 times
1001 times
In the following code, if aSound has 1000 samples how many times
is the second loop executed?
a)
0 times
b)
c)
d)
9.
999 times
1000 times
1001 times
Amplifying a sound:
a)
b)
c)
d)
requires the amplitude value of each sample to be increased
may cause clipping
requires sequencing through the samples of the sound
twice
all of the above
10. The following code:
a)
b)
c)
d)
normalizes aSound
sets each sample in aSound to its maximum value
sets maxAmp to the maximum amplitude of the samples in
aSound
none of the above
EXERCISES
1.
Write a program to make a sound quieter. The program should
include a method:
which modifies the samples of the sound aSound to reduce the
amplitude by a factor of by. Try a factor of 0.5. Does this seem to
make the sound half as loud? Why not?
2.
The bane of recording engineers and audiophiles is noise. What is
noise? Noise is an unwanted signal (waveform) that distorts the
desired signal (e.g. the music). Noise occurs from many different
sources. Background noise is extraneous sounds occurring in the
background such as conversations at other tables in a restaurant.
Electronic noise or hum that is generated by electronic circuits and
high voltage lines. Hiss is noise that is produced by random effects
such as cosmic rays, random fluctuations in current etc.
Write a program to generate hiss. Hiss is just a random signal. We can
generate it by choosing random values for the amplitudes of the
samples in the sound. The waveform produced will be highly irregular
and we will hear the result as hiss.
To generate the random amplitude values for the samples, we can use
the random method from the java.lang.Math library. random
generates a random value between 0.0 and 1.0. We need amplitudes
between –maxAmp and maxAmp if the amplitude of the hiss is to be
maxAmp.
Write the code that generates hiss as a method:
where nSamples is the number of samples for the sound and
maxAmp is the maximum amplitude of the noise. For this exercise, try
22000 samples with maximum amplitude of 10000.
3.
A sine wave is a pure sound. It is generated by the function sin (in
the java.lang.Math library) and has a period (cycle) of 2π. If we
want to generate a sine wave with a specific frequency (e.g. a note
such as A above middle C with frequency 440Hz), we need to have 440
cycles per second (i.e. the values for the parameter for the sin
function have to move through 0-2π, 440 times each second. Since
sin itself is cyclic, (i.e. the value of sin(0) is the same as the value of
sin(2π) which is the same as the value of sin(4π)), all we have to
do is keep incrementing the parameter (coordinate) by an amount
such that it goes from 0-2π×440 in one second. Since the sampling
rate tells us the number of samples in one second we need an
increment of 2π×440/sampling rate.
Write a method:
that creates a new sound (with default attributes including sampling
rate) which is a sine wave with the specified duration (in samples),
frequency (cycles per second) and amplitude.
To get a specific amplitude, we need a sound whose highest sample
amplitude is the given amplitude. Since the values produced by sin
are between -1 and 1, we simply have to multiply the result from the
function by amp when generating the wave.
Try generating some waves of specific frequencies (e.g. 440Hz,
880Hz). Keep the durations relatively small (22000) and the
amplitude in the range 10,000-20,000.
4.
As discussed in Exercise 1, hiss often affects a recorded sound. Write
a program that adds hiss to a sound. After loading the sound, it should
add a random value within a specified amplitude to each sample of the
sound. Use a method:
which adds a random value between –amp and amp to each sample of
aSound. Try amplitude around 3000 so the noise doesn’t drown out
the sound.
8 COLLECTIONS
CHAPTER OBJECTIVES
Explain the properties of collections
Apply an iterator in the processing of a collection
Differentiate between iteration and indexing in processing a
collection
·
Apply indexing in processing a one-dimensional collection
·
Apply indexing is processing a two-dimensional collection
·
·
·
Much of Computer Science involves organizing information for retrieval,
summary, synthesis or presentation. This means that data/information is
gathered together into groups for processing. A group of data is called a
collection. The individual pieces of data in the collection are called
items or elements.
Java supports collections as Collection types and provides ways to
access individual elements of collections and sequence through the
elements of a collection either by iteration or indexing. With iteration all
of the elements are accessed in turn. With indexing, elements can be
accessed in arbitrary order. Sounds are one-dimensional collections of
Samples and Pictures are two-dimensional collections of Pixels.
8.1
COLLECTION TYPES
A Collection type in Java is a type whose instances (objects) contain
multiple values (or objects). We have already seen two such
Collection types: Picture which is a collection of Pixels and
Sound which is a collection of Samples. We have written programs that
sequence through the individual elements (Pixel, Sample) of a
Picture or Sound. The entire picture (or sound) is represented by the
Picture (Sound) variable (such as Picture pic;). The individual
elements are accessed via the picture or sound variable (e.g. p =
pic.next();).
ITERATION
Since it is very common to iterate through the elements of a collection,
Collection types provide a mechanism called an iterator that
automatically provides such sequencing. The for-each loop uses this
feature to iterate through each element in a Collection. For example,
in Section 7.4 we wrote:
to find the maximum amplitude in a sound when normalizing the sound.
The index variable s takes on each of the elements (samples) of aSound
in turn, iterating through the sound. The for-each loop above is
equivalent to:
Every Collection type has a method called iterator which creates
and returns an Iterator object which can iterate through the
collection. Iterator objects have two methods: hasNext, which
returns true if there is another unaccessed element in the collection
and next, which returns the next element from the collection. The type
Iterator is imported from the package java.util. If we use a foreach loop we do not have to deal with the iterator directly. A for-each
loop automatically creates the iterator (calls iterator), tests for
completion (hasNext) and accesses the element (next). The iterator
created cannot be referenced directly by the code and java.util does
not have to be imported.
Since Picture is a Collection type, the for-each loop can also be
used on a picture such as:
replacing the loop (lines 37-42) in the Red-eye correction example in
Figure 5.8. The iterator is created and the index variable p takes on each
of the pixels of the picture in turn.
NOTE: Both Picture and Sound have a built-in iterator and methods
hasNext and next which we used in processing pictures and sounds in
early examples (e.g. Figure 5.6). These methods are provided for
convenience when first working with pictures and sounds. Normally a
Collection would not have a built-in iterator and programs would use
for-each loops or explicitly obtain an iterator via the iterator method.
An iterator guarantees that it will return all of the elements of the
collection through successive calls to next until hasNext returns
false. However, it doesn’t necessarily guarantee that they will be
presented in any particular order. If we want to access the elements in a
particular order, or access them repeatedly or in various orders, we need
to use another mechanism of Collection objects called indexing.
INDEXING
Items in a collection are ordered in some way—there is a first item, a
second item etc. Samples in a sound are ordered by the sampling time—
that is they are ordered along the time dimension. The first sample was
taken at time 0 (start of the sound), the second at time samplinginterval, the third at 2×sampling-interval, etc. The waveforms
we displayed in Chapter 7 are really plots of the sample values over time
with amplitude being the y-axis and time being the x-axis (see Figure
7.5). Since the elements are organized along one dimension (time), a
sound is called a one-dimensional collection.
In a collection, the elements are numbered by their order along the
dimension, with the first element being numbered 0, the second 1, etc.
This position value is called index of the element and is an int value.
The individual elements of a collection can be accessed via their index
value. The sample at index position 2 (third sample) in the sound
aSound can be accessed via the Sound method getSample:
The getSample method (like next) returns a Sample object.
Using an iterative for loop we can iterate through all the samples in a
sound in time order using the index variable of the for loop. For example,
the for-each loop from Section 7.4 (shown above) can be rewritten using
indexing as:
Remember that the getNumSamples method returns the number of
samples in the sound. The samples themselves are numbered
0…numSamples-1, hence the use of < in the for loop condition.
As mentioned above, the advantage of indexing is that we can access any
element at any time and can process the elements in any order. For
example, if we wanted to process the samples of a sound in reverse order
(i.e. last through first), we could use code such as:
NOTE: Technically, not all Collection types can be indexed in this
way. There are many different Collection types. Ones that can be
indexed are called List collections.
Pictures can also be indexed. As we saw in Section 5.1, pictures are made
up of pixels in a two-dimensional arrangement with a width (number of
columns) and a height (number of rows). A picture is thus a twodimensional collection with each pixel at a particular (column,row)
position. For indexing, the row positions start at 0 as do the column
positions and the pixel at row 3 (the fourth row) and column 7 (the
eighth column) of the picture aPic can be accessed via the Picture
method getPixel:
The pixel in the top-left corner of a picture is at position (0,0) and the
pixel in the bottom-right corner is at position (height-1,width-1).
To process an entire picture, we need to sequence through all columns in
all rows of the picture. Thus, with indexing, we will need two iterative for
loops, one to sequence through the rows and one through the columns,
nested within each other. The loop in the Red-eye correction program
(Figure 5.8, lines 37–42) can be rewritten using indexing as:
This code sequences through each column of a row before going on to
the next row (i.e. the column loop is nested within the row loop) and the
pixels are processed left-to-right, top-to-bottom (modify the example in
Figure 5.8 as above and watch the pixels change). This was the same
order as when we used the built-in iterator. We can process the pixels in
a different order, such as top-to-bottom, left-to right by nesting the row
loop inside the column loop such as:
Try it.
8.2 PROCESSING A ONE-DIMENSIONAL COLLECTION
RAISING THE PITCH OF A SOUND
As we saw in Section 7.1, the pitch of a sound is related to the frequency
or number of cycles of the sound per second (measured in Hz). To
change the pitch of a sound, we would need to change the number of
cycles per second, reducing it to raise the pitch or increasing it to lower
the pitch. For example, Figure 8.1 shows a 440 Hz square wave and
Figure 8.2 shows an 880 Hz square wave at the same amplitude.
Figure 8.1 Square Wave at 440 Hz
Considering these two pictures, it is evident that the cycles in the 880 Hz
sound are half as long and thus have half as many samples per cycle as
the 440 Hz sound. We could produce a new version of a sound at a higher
pitch by reducing the number of samples per cycle.
Figure 8.2 Square Wave at 880 Hz
One way to reduce the number of samples per cycle without distorting
the sound too much would be to simply select every other sample in
creating the new sound. That is the first sample of the new sound would
be the first sample of the original. The second would be the third sample
of the original, the third the fifth and so on. The new sound would have
half as many samples per cycle and would have half as many samples
overall, being thus half the duration of the original.
Figure 8.3 shows a program to raise the pitch of a sound, doubling the
frequency by selecting every other sample of the original sound. The
constructor (lines 13-24) loads a sound (oldSound) and places it on the
player. Once the user has had an opportunity to play the original sound,
it calls the method raise to produce a new sound (newSound) with
raised frequency, places it on the player and allowing the user to play it.
It then saves the higher frequency sound and terminates.
The method raise receives a sound (aSound) as a parameter. It
creates a new, silent, Sound object with half as many samples, but all
other attributes the same as aSound (see Table 7.2) (line 35). It then
iterates through the samples of result (the for loop on lines 36–41),
Using the index variable i, it accesses the next sample of newSound
(line 37) and the sample at index position i*2 from aSound (line 38). It
then obtains the amplitude of the sample from the original sound (line
39) and changes (from 0) the amplitude of the sample from the new
sound (line 40). When the amplitudes of all of the samples of the new
sound have been set, it returns the new sound (line 42).
Figure 8.3 Example—Raise the Pitch of a Sound
Consider the indexing of the two sounds: result and aSound (lines 37
and 38). The index values for result are 0, 1, 2, … (the values of
i). The index values for aSound are 0, 2, 4, … (the values 2*i).
Thus the first (index 0) sample amplitude from aSound becomes the
amplitude of the first (index 0) sample of result. The third (index 2)
sample amplitude from aSound becomes the amplitude of the second
(index 1) sample of result and so on and our goal of doubling the
frequency has been achieved. Using indexing we were able to access
samples of aSound other than in sequence.
REVERSING A SOUND
Let’s consider how to play a sound backwards such as a DJ might do
when back spinning a vinyl record on a turntable. The cycles would occur
in reverse order and each cycle itself would be reflected (i.e. in reverse).
All we have to do is reverse the order of the samples so the last sample is
first and the first, last, etc.
This can be done reasonably easily using indexing. We copy the
amplitude of the last sample of the original sound as the amplitude of the
first sample of the new sound. Then the amplitude of the second last
sample of the original sound as the amplitude of the second sample of the
new sound and so on until all samples of the new sound have been set.
Figure 8.4 is a program to reverse a sound. The constructor (lines 13-24)
loads the original sound, gives the user an opportunity to play it and then
calls the function reverse to produce a new sound as the reverse of the
original. After allowing the user to play the reversed sound, it saves it
and terminates.
The method reverse does the reversal. The two sounds will have the
same number of samples and other attributes (lines 34 & 35). The
samples of the new sound (result) must be accessed in the order 0,
1, 2,… while, at the same time, the samples of the original sound
(aSound) must be accessed in the order n-1, n-2, n-3,… where n is
the number of samples. An iterative for loop can provide the sequence
for the new sound (lines 37-41). We can produce the sequence for the
original sound using a local variable pos that starts at n-1 (line 36) and
decreases by 1 each time through the loop (line 40). The amplitude of the
sample at position pos in the original sound is obtained (line 38) and
then this amplitude is used to set the amplitude of the sample at position
i in the new sound (line 39).
CASCADING METHOD CALLS Lines 38 and 39 require some
explanation. You will notice that each references two methods
(getSample and getAmp on line 38 and getSample and setAmp on
line 39). Consider that the result of the expression:
is a Sample object. A Sample object responds to the method call
getAmp, returning an int. The method call to obtain a sample of the
sound and the method call to obtain the amplitude of the resulting
sample can be combined into one expression as a cascading method
call—where the result of one method call is the target of the second (or
subsequent) method call(s). The effect of line 38 is equivalent to the
sequence of statements:
except that there is no need for the intermediate variable aSamp.
Cascading method calls are performed left-to-right so in line 38 the call
to getSample occurs first (resulting in a temporary unnamed Sample
object) then the call to getAmp is made to that object. The situation is
essentially the same in line 39.
Figure 8.4 Example—Reverse a Sound
In fact, lines 38 and 39 could be combined into a single statement:
where the result of the cascading method calls to obtain the amplitude of
the sample from the old sound is used as the parameter for the cascading
method calls to set the amplitude of the sample in the new sound. Now
even the variable amp is unnecessary. However, it is debatable that this
single line is better than the prior two lines as it is likely less readable.
Care must be taken with cascading method calls not to produce lines of
code that are too complex to be easily understood.
8.3
PROCESSING A TWO-DIMENSIONAL COLLECTION
RANDOM PAINT
Let’s write a program to produce a painting. The style will be “pointillist”,
a style pioneered by George Seurat and Paul Signac where painting was
done by placing individual dots of pure color on the canvas to produce an
image. We won’t actually try to produce an image, but rather just an
abstract collection of dots.
We start with a blank canvas (a new Picture with all pixels white). We
can then choose a color at random and place it at a random pixel position
(column,row) on the picture. Repeating this a number of times will
produce a painting such as shown in Figure 8.5.
Figure 8.5, A Painting in “Pointillist” Style.
PSUEDO-RANDOM NUMBERS To do this problem, we will need a way
of choosing random or arbitrary colors and positions. Computers are
deterministic devices, that is, with the same program code and data they
always do the same thing. We want to have our program do different
things whenever it is run (that is, generate different paintings). The
Math library provides a method called random that returns a pseudorandom double value between 0 and 1. Since a computer is
deterministic, and these values are computed, they aren’t truly random
but the sequence of values is so complex that they appear to be random.
That is why they technically are called pseudo-random numbers,
however the usual practice is to just call them random numbers.
Often we desire a random integer between two values, e.g. between 1
and 6 if we are simulating the roll of a die. We can easily convert a
random double between 0 and 1 into a random integer between n and
m using the expression:
random() generates a double value greater than or equal to 0 but
always less than 1. When we multiply this by m-n+1 (the number of
different integer values we want), we get a double between 0 and not
quite m-n+1. Casting this to an int gives us an int between 0 and m-n.
Finally adding n gives us an int between n and m.
Thus to simulate the roll of a die (an integer between 1 and 6), we could
use the expression:
Each time this statement is executed it will produce a different value
(between 1 and 6) for die and the sequence of values will be in a very
complex pattern, essentially random.
Figure 8.6 shows a program to produce a random painting in Pointillist
style. The constructor creates a new blank picture (line 19), places it on
the screen and then calls paint to produce the painting (line 21). After
the user has had an opportunity to view the painting, it saves the picture
and terminates.
The method paint does the actual painting. It receives two parameters:
the canvas (picture) upon which it is to paint and the number of points
(dots) that it is to generate. It determines the width and height of the
canvas and then executes a loop to paint the desired number of dots
(lines 37-41). Note that this loop isn’t iterating through the pixels of the
picture, it is simply repeating the process to paint one dot the required
number of times.
The actual painting of a dot occurs by first computing (lines 38 & 39) a
position for the dot (i.e. column (x) and row (y)) as random numbers
between 0 and width-1 (for x) and 0 and height-1 (for y), applying the
expression above for generating a random number. Line 40 paints the
actual dot by accessing the pixel at (x,y) and setting its color. The color
is also chosen at random. The Color class has a constructor that takes
an integer between 0 and 16,777,215 (the number of distinct colors, see
Section 5.1) and produces the color with that index. By passing a random
number in that range, a random color is chosen.
ROTATING A PICTURE
When a picture is taken with a digital camera, the camera can be held in
different orientations: horizontal for landscapes and vertical for
portraits. Since the sensor array in the camera has a fixed dimension (i.e.
height and width) and orientation, when a picture is taken with the
camera in vertical orientation the resulting image is produced on its side
(i.e. rotated 90°). In preparing the image for display, we need to correct
this rotation. Most photo editing software provides this feature.
Let’s write a program that takes a picture and rotates it 90° to correct
this effect, such as shown in Figure 8.7. The new image will have
different dimensions from the original—its height will be the original
width and its width will be the original height. The pixels of the new
image will have the same color as corresponding pixels of the original
image. We will need to create a new (white) picture with appropriate
dimensions and then set the colors of its pixels to the color of the
corresponding pixels in the original picture. The challenge is in
determining the correspondence.
Figure 8.6 Example-Produce a Random Painting
[12]
Figure 8.7 Image
Rotated by 90°
Figure 8.8 shows the correspondence of pixels of a 3×2 picture and its
90° left rotation. The top-left pixel (at (0,0) numbered 1) in the original
image corresponds to the bottom-left (0,2) position in the new image.
The top-right pixel (at (2,0), numbered 3) corresponds to top-left (0,0)
and so on.
Figure 8.8 Correspondence between Pixels
These correspondences are summarized as:
In general, pixels in row y of the original correspond to pixels in column
y of the result. Pixels in column x correspond to pixels in row width-1x, where width is the width of the original image.
Figure 8.9 shows a program to perform a left rotation on a picture. As
usual, the constructor loads and displays the original image, creates the
rotation by calling the rotate method, displays the rotated image and
then saves the result.
The default dimensions of the canvas on a PictureDisplayer are
640×480, suitable for pictures in landscape orientation. Since the image
is going to be presented in both landscape and portrait orientation, it
doesn’t make sense to try to use the same display for both versions. The
PictureDisplayer class has a constructor that takes a Picture as a
parameter, creates the display with a canvas matching the dimensions of
the picture and places the picture on the display. In the constructor, two
displays are created: one (line 19) to display the image in landscape
orientation and then a second one (line 22) to display the image in
portrait orientation. Note that the variable display refers to these
different PictureDisplayers after lines 19 and 22.
The method rotate creates the rotated version of the picture aPic
passed as a parameter. It obtains the width and height of the original
(lines 35 & 36) and then creates a new (white) picture whose width is
the height of the original and whose height is the width of the original
(line 37). It then iterates through the pixels of the original image (the
nested for loops at lines 38–43 and 39–42) obtaining the color of each
pixel in turn (line 40) and setting the color of the corresponding pixel in
the new image to that color (line 41). Finally, after setting all pixels, it
returns the new image as its result (line 44).
SUMMARY
Collections are classes representing groups of related values or objects.
The individual values or objects in a collection are called items or
elements. A Sound is a collection of Samples and a Picture is a
collection of Pixels.
Collections can be processed either by iteration or by indexing. A
Collection type provides a method called iterator which produces
an Iterator object through which each of the elements of the
collection can be accessed, in turn. This can be done implicitly using a
for-each loop or by creating the iterator explicitly and using the
Iterator methods hasNext and next.
Many collections also support processing through indexing. The
elements of the collection are numbered with an index and the index can
be used to access the element directly. The advantage of indexing is that
the elements can be processed in any order by using appropriate index
values and individual elements may be accessed more than once, or not
at all.
Figure 8.9 Example-Rotate a Picture90° Left
Collections have dimensions. In a one-dimensional collection, a single
index value is used to access an element. In a two-dimensional collection,
a pair of index values is used, and so on. Sound is a one dimensional
collection with time being that dimension. Samples with consecutive
indices are at consecutive sampling times. Picture is a twodimensional collection with width and height (in pixels) being the
dimensions. Using indexing to iterate through the items of a onedimensional collection (such as Sound) requires one for loop. Iterating
through a two-dimensional collection (such as Picture) using indexing
requires two nested for loops.
REVIEW QUESTIONS
1.
T
F A Collection in Java is a type representing multiple
values of some other type.
2.
T
F
3.
T
F An iterator is a statement that sequences through the items
in a Collection.
4.
T
F
5.
T
F Indexing involves the selection of an item in a collection
based on its position along the dimension(s) of the collection.
6.
A Picture:
a)
b)
c)
d)
7.
A Sound is a two-dimensional Collection.
A for-each loop creates an iterator on the collection.
is a two-dimensional collection
can be indexed by a row and column position
can be iterated via an iterator
all of the above
Which of the following will access a sample from within the sound
aSound?
a)
b)
c)
s = aSound.next();
s = aSound.getSample(i);
s = aSound.iterator().next();
d)
8.
If aPic is a 640x480 picture, how many times will the following
loop will execute?
a)
b)
c)
d)
9.
all of the above
640
480
307200
the code will crash after 480 iterations
Assuming p, q, r, s and c have been appropriately declared and
initialized, the following code is equivalent to?
a)
s.getPixel(y,x).setColor(r.getPixel(x,y).getColor());
b)
r.getPixel(x,y).setColor(s.getPixel(y,x).getColor());
c)
d)
setColor(r.getPixel(x,y)).getColor(s.getPixel(y,x)));
none of the above
10. Assuming aSound, result and amp are appropriately declared,
what does the following code do?
a)
b)
c)
raises the frequency (pitch) of the sound
raises the volume of the sound
lowers the frequency (pitch) of the sound
d)
none of the above
EXERCISES
1.
Sound editing involves extracting desired sounds (a clip) from a
source sound as well as putting together pieces of sounds into a whole
(splicing). In this exercise we will look at extracting a clip from a
sound.
A clip is just part of a sound. It obviously starts at some sample
position (the beginning of the desired sound) and ends at some later
sample position (the end of the desired sound). If we know the
positions, creating the clip is easy, just copy the desired samples into a
new sound of the correct length.
Write a program that loads a sound and based on constants declared
in the program, produces a new sound that contains the desired clip.
Use a method:
that returns a new Sound that contains the samples from positions
from to to inclusive in the orig sound.
2.
Let’s consider the other side of sound editing—splicing sounds
together. Basically, what we need to do is create a new sound whose
samples are those from the first sound followed by the samples from
the second. We’ll assume both sounds have the same attributes (e.g.
sample rate, sample size, encoding). This should be quite
straightforward.
Write a program that loads two sounds and then splices them
together to create a single sound. Use a method
that produces a new Sound that has the samples from sound1
followed by the samples from sound2.
3.
An echo occurs when sound waves produced at one place reflect off
a hard surface and return back to the original source. The time it takes
for the sound to reach the surface and then return is the delay in the
echo. Since sound attenuates (diminishes) as it travels, the reflected
sound (echo) is quieter than the original.
Write a program to load a sound and then produce a new sound that
simulates an echo of that sound. Use constants for the delay and
attenuation factor. Use a method:
which produces a new Sound with an echo based on the original
sound sound with a delay of delay seconds and an attenuation of
factor. The delay can be converted to a number of samples by
multiplying by the sampling rate of the sound. The new sound will be
longer than the original sound by the number of samples that make up
the delay. The first samples in the echo will just be a copy of the
samples from the original sound. Once the delay has elapsed, the next
set of samples (until the end of the original sound) will be the sum of
the samples in the original sound and the samples from the beginning
of the original sound at the attenuated amplitude. Finally the last part
of the result will simply be the remaining samples of the original
sound at the attenuated amplitude.
4.
Often we wish to make an enlargement of a picture, for example to
enlarge from a 4×5 to an 8×10. We can do this digitally by doubling
the number of rows and columns of the image. Where do we get the
extra pixel values from (we need four times as many pixels)? One
solution is to duplicate each pixel from the original image as a block of
4 neighboring pixels in the new image. This means the pixel (actually
the color of the pixel) at position (c,r) in the original image will be
duplicated into the four pixels: (2c,2r), (2c+1,2r), (2c,2r+1)
and (2c+1,2r+1) in the new picture.
Write a program to load a picture and then enlarge it by a factor of
two by copying the colors of the original picture into a new picture.
Use a method:
that produces a new picture which is twice the size of aPic with
each pixel replicated four times.
5.
Write a Java program to do simple edge detection on a picture. The
resulting image will have black pixels wherever there is an edge in the
original picture and white pixels elsewhere. For example, the picture:
after edge detection looks like:
To do edge detection: on each row we compare the intensity of the
pixel with the pixel immediately below it (i.e. on the next row). If the
absolute difference in the intensities is smaller than a value
TOLERANCE (a constant with value 10.0), we set the pixel to white,
otherwise we set it to black. Since the last row has no row below it, we
treat it as if the row below it is the same (i.e. all the intensities are the
same).
Use a method:
that returns the intensity of the Color c, defined as the average of
the R, G and B components.
6.
One image can be superimposed upon another by blending their
pixels producing a transparent effect. If the images are the same size,
it is simply a matter of averaging the red, green and blue components
(i.e. summing and dividing by 2) of the two pixels to produce the new
color channel values of the pixel for the blended image.
Write a program to load two pictures and then produce a third
picture of the same size in which the color channel values of each pixel
of the new picture is the average of the color channel values of the
corresponding pixels in the original two pictures. Use a method:
which produces a new blended picture from pic1 and pic2.
9 INPUT AND OUTPUT
CHAPTER OBJECTIVES
·
·
·
·
·
·
·
Describe the concept of a stream as an abstraction of I/O.
Differentiate between a text and a binary stream.
Apply the BasicIO library to read data from a variety of
sources.
Apply the BasicIO library to write information to a variety of
destinations.
Apply Formats to produce well-formatted output.
Process a file of data to EOF.
Perform a file update with a report.
Up to now, most of our programs have been self-contained and do
precisely one thing. Most real-world computer programs, however,
process data that come from outside the program itself. To do this, a
program must be able to access input and output devices such as the
keyboard, hard disk, the monitor, and the printer that were discussed in
Chapter 1. A programming language must, therefore, provide facilities
for doing I/O (input/output).
9.1
STREAMS
Different kinds of I/O hardware such as keyboards, disk, and magnetic
stripe readers behave in different ways and even similar hardware
behaves differently from one manufacturer to another. Most
programming languages standardize their view of the way I/O works
using the concept of a stream. A stream is a sequence of information—
either bytes for binary information or characters for text information. A
stream is connected to a source for input or destination for output. The
stream handles the details of the different types of hardware. The
connection of a stream to a source/destination is called opening the
stream, and the disconnection, which is done when the
source/destination is no longer being used, is called closing the stream.
The act of obtaining information from an input stream is called reading
and the act of appending information to an output stream is called
writing.
On input, the information is read starting with the first byte (or
character), and each successive read performed by the program reads
the next bytes or characters. Similarly on output, the stream starts as
empty and each write appends to the end of the stream. On reading, since
the amount of information contained in a stream is finite, there will be a
situation when there are no more (or not enough) byte(s)/character(s)
remaining in the stream. This situation is called reaching end-of-file
(EOF) since, traditionally, files have been the usual source for a stream.
THE BASICIO PACKAGE
The I/O facilities that are standard with Java, although based on the
stream concept, are not easy to use and involve concepts beyond the
scope of an introductory course. They deal with complicated graphical
user interfaces (GUI) and unreliable communications media such as the
Internet. Instead, we will use a nonstandard library (the BasicIO
package) that provides basic input/output without the complexity (or
flexibility) of the standard Java facilities.
The BasicIO package provides a facility for stream I/O. Since input may
come from and output may go to a variety of I/O devices, there is a
variety of kinds of streams that can be created represented by classes of
the BasicIO package. Table 9.1 summarizes the classes.
Table 9.1 BasicIO Classes
I/O can be used for two basic purposes: temporary or permanent storage
of information by a program for later use by the same or another
program and acquisition or presentation of information from/to a
human user. Since human users are more comfortable working with
information represented by sequences of letters and digits, I/O intended
for human consumption is presented as text. Text refers to sequences of
characters that are typically represented according to the ASCII coding
scheme (see Chapter X). On the other hand, computers use binary
representation of information, so I/O intended for consumption by
another computer program is presented in binary form, as a sequence of
bytes. The classes BinaryDataFile and BinaryOutputFile are
intended for computer program-to-computer program I/O.We will
discuss these further in Chapter 11 . The classes ASCIIPrompter and
ASCIIDisplayer are intended for immediate (and transient)
computer-human interaction. ASCIIDataFile can be used in
situations where a human prepares information for a program ahead of
time. ASCIIOutputFile and ASCIIDataFile can be used together
where computer program-to-computer program I/O is desired but with
human intervention or interpretation.
All of these combinations could make things quite complicated. However,
the BasicIO package has been designed so that the input classes work
in the same way, that is, they have all the same methods. The output
classes also work in a uniform way. To change a program to get input or
output from/to a different source or destination all that is necessary is to
change the type in the declaration of the stream object and the
corresponding object creation expression.
APPLICATION PROGRAMMING INTERFACE (API)
An application programming interface (API) is a description of the
resources provided by a package used in writing an application (piece of
software). The resources are the classes and methods that are available
for use when a package is imported. All the programs we have written so
far make use of classes in other packages and thus the API of that
package.
In Java, there is a program called JavaDoc that produces on-line
(browser-based) documentation of the API for a package. It generates
the web pages from the comments written in the program text and
interprets the tags (such as @param) that we write in these comments.
We have been using JavaDoc comments to annotate our programs and
we could use the JavaDoc application to generate web pages describing
our programs (packages).
Figure 9.1 Package page of API for BasicIO package
Figure 9.1 shows the Package page for the BasicIO package. Among
other things, it lists the classes that make up the package with a brief
description (as written in the comment at the beginning of the package
declaration). If you click on a class name, the Class page for that class is
presented.
Figure 9.2 Method Summary on the Class Page of the
ASCIIPrompter API
Figure 9.2 shows the Method Summary on the Class page of the
ASCIIPrompter class. Each method provided by the class is listed with
its return type and parameter types and a brief description. These are
generated from the JavaDoc comments at the beginning of the method
declarations. If you click on a method name, the Method Details of
the method are displayed.
Figure 9.3 Method Details for the readInt method of the
ASCIIPrompter API
Figure 9.3 shows the Method Details for the readInt method of the
ASCIIPrompter class. It gives the complete method header, a
description of the method and for each parameter, a description of what
is expected and for the return value, a description of what is returned.
These are generated from the @param and @return tags in the method
comment.
The APIs for the Brock packages used in this text (Media and BasicIO)
are available via URL:
http://www.cosc.brocku.ca/sites/all/files/documentation/Brock_packages/index.ht
The APIs for the standard release of Java (i.e. packages such as
java.lang.Math) are available via URL:
http://docs.oracle.com/javase/7/docs/api/
9.2
COMPUTING CLASS AVERAGE
After the mid-term test, the instructor in COSC 1P02 wants to know what
was the average mark obtained by students on the test. The average is
the sum of the values (individual student marks) divided by the number
of values (students). The algorithm for computing average can be
expressed as:
Each time through the loop, sum is increased by the next value until, at
the end of the loop it is the sum of all values. The average can then be
computed. Where do the values come from? Assuming the tests were
written and marked on paper, the instructor will have to provide them to
the program. For this, we can use an input stream. Once the average is
computed, the instructor must be informed of the result, for this we can
use an output stream.
CLASS AVERAGE—SIMPLE VERSION
The simplest way to provide data to a program is for the user (the
instructor in this case) to enter the data via the keyboard. Similarly the
results can be displayed on the monitor. Table 9.1 indicates that
keyboard input is supported by the ASCIIPrompter class and monitor
output by the ASCIIDisplayer class. Our first version of the program
will use these classes.
Figure 9.5 is a simple program to compute the average mark on the midterm test. Since we are using resources from the BasicIO library
instead of the Media library, we import the BasicIO package (line 2).
This makes ASCIIPrompter, ASCIIDisplayer and all of their
methods available.
An ASCIIDisplayer is like a TurtleDisplayer or a
PictureDisplayer—it is a window in which a program can display
information. An ASCIIDisplayer is used to display text (ASCII)
instead of a drawing or a picture. When an ASCIIDisplayer is
created, a window is presented on the screen. The program can display
text in the scrolling text area in the center of the window. Figure 9.4
shows the ASCIIDisplayer (created at line 20) after the program has
completed (that is written information to the text area).
Figure 9.4 ASCIIDisplayer
An ASCIIPrompter is used to obtain information from the user. It
presents a window (Figure 9.6) which has a label telling the user what
information is requested (“# Students”) and a text box into which the
user enters the requested data as text.
At line 20, the program creates the display. The window is immediately
presented on the screen, with an empty (blank) text area. Next, at line 21,
the prompter is created. Unlike a displayer, a prompter is not
immediately presented on the screen. It is only presented when the user
is requested to input data. The label for the prompter, telling the user
what to input, is set in line 22. At line 23, the program obtains
information from the user by doing a read operation. ASCIIPrompter
has function methods to read each of the basic types called readType,
such as readInt and readDouble. When the method is called, the
prompter is displayed on the screen and the program is suspended until
the user presses a button. The user enters the requested data (as a literal
of the desired type) and then presses OK. At this point, the prompter
disappears, interprets the entered text as a value of the indicated type
and returns that value as the result of the readInt method.
Figure 9.5 Example—Computing Class Average—first version
Thus, at line 23, the program presents the prompt window with label “#
Students” (Figure 9.6) and is suspended. The user uses the keyboard
to enter an integer literal and, when ready, presses OK. The readInt
method of the ASCIIPrompter class inputs the characters entered,
interprets them as an int value and returns with that int value as the
result. The program then stores that value in the variable numStd.
Figure 9.6 ASCIIPrompter
At the next line (24), the label on the prompter is changed to “Mark”,
sum is initialized to zero (line 25) and the program then loops (lines 26–
31) to process the marks of the indicated number of students (numStd).
Within the loop, the prompter (line 27) is presented again (with “Mark”
as the label) to request a double value for a student mark on the test.
Once the user has entered a compatible value (either an int or a
double literal) and pressed OK, the input value is assigned to mark and
the mark is added to the sum (line 28).
At line 29, the program does output (writes) to the displayer.
ASCIIDisplayer has procedure methods of the form writeType to
write each of the basic types. It converts the value (its parameter) into a
text string (ASCII) and presents it as a sequence of characters starting at
the top left of the text area. Subsequent writes continue from where the
last write ended (separated by a space) along the line. The newLine
method causes the next write to start at the beginning of the next line.
Thus the repeated execution of lines 29 & 30 writes the individual
students’ marks entered on line 27 on consecutive lines of the text area.
When the loop has completed (the indicated number of student marks
have been read), the average is computed (line 32) and written to the
text area preceded by the text string “Ave:”. At line 36, the Close
button is presented on the display allowing the user to review the results
before pressing Close, dismissing the displayer and terminating the
program.
READING FROM A FILE
For a small class, the program in Figure 9.5 may be adequate. However, if
the class is large it will be tedious and error prone for the instructor to
enter the marks, one at a time, as the program runs. If any errors are
made, the program will have to be run again and the data re-entered.
It would be much easier if the instructor could enter the data into a text
file using a text editor, check and correct it, and then run the program. If
there is an error, the problematic information could be corrected and the
program rerun without having to re-enter the data.
Our second version of the class average program (Figure 9.7) uses an
ASCIIDataFile (a file of text, prepared by a text editor) as input in
place of the prompter. Lines 29-40 correspond to lines 23-37 in Figure
9.5, reading the number of students, computing the sum, writing the
student’s marks and computing and writing the average. When the
ASCIIDataFile is created (line 25), an open dialog is presented for
the user to select the text data file (.txt) from which to read the data.
Reading the number of students and the students’ marks are handled by
calls to the readInt and readDouble methods of the
ASCIIDataFile class corresponding to those of the ASCIIPrompter
class.
Figure 9.7 Example—Computing Class Average—second version
In addition to reading from a data file, the program has a few additional
enhancements. The data file is required to begin with two lines of text,
the first being the course name and the second the test name. Following
this, on a new line, the number of students is recorded. Finally, each
student mark is preceded by the student’s student number. A sample
input data file might look like Figure 9.8.
Figure 9.8 Data File for Class Average Program
These requirements allow the program to be used for any course (not
just COSC 1P02) and any test or exam. It also allows checking of the data
since the student numbers are paired with the marks. The program can
now produce a more meaningful report such as shown in Figure 9.9.
Figure 9.9 Class Average Output
A piece of text that is not being interpreted (e.g. as a number) is
represented in Java as an object of the type String (a built-in class).
BasicIO classes provide methods readString and writeString to
read and write pieces of text. Line 26 reads the course name (first line in
the data file) and line 27 reads the work name (i.e. Term Test) from
the second line in the file. Line 32 reads the student number as a piece of
[13]
text
before each read of the mark (line 33). A string literal is a piece
of text enclosed in quotes (″) such as ″Ave″ used in line 33 in Figure 9.5.
Technically, each readType method in ASCIIDataFile reads a single
field (sequence of characters). The fields are separated by tabs or
newlines. This kind of text file is called tab-delimited text and is a
common format. In Figure 9.8, there is a newline between the course
name and work name, work name and number of students, number of
students and the first student number and after each mark. There is a tab
between each student number and mark. The data can be organized in
any way, as long as each field is separated from the next somehow. All
fields could be on one line if only tabs are used. Alternatively, each field
could be on a different line if all newlines are used. The combination
allows the person who prepares the file to arrange it in a convenient,
human-readable format.
Figure 9.10 Example—Helper Methods for Class Average—second
version
The actual output of the report to the ASCIIDisplayer is relegated to
helper methods (lines 43–72 in Figure 9.10). Although not complicated,
the methods remove some details that are unnecessary in the
constructor itself. They also make it easier to change the report since
there is one specific place to look for each part of the report. the
writeHeader method writes the title or header at the top of the report
consisting of the course name and work name. The lines containing the
student number and mark (called detail lines in a report) are written by
the method writeDetail and the average (often called the summary)
is written by writeSummary.
The standard writeType methods of ASCIIDisplayer write the
value to the display using a minimal number of characters appropriate
for the value being written. However, if control is desired over the layout
of the output, including the number of characters used for output of a
value and/or the number of decimal places written for a double value,
there are alternative versions. For writeInt, a second parameter
indicates the field length (i.e. number of characters to be used). If the
value requires fewer characters, the value is right-justified by filling with
leading spaces. For writeDouble, both a field length and a number of
decimal places are specified. The value is displayed with the specified
number of decimal places (if zero, no decimal places and no decimal
point) and, if the total length of the result (including decimal places and
decimal point) is smaller than the specified length, the result is also
right-justified. Using the same formats for each detail line (as is easily
achieved by using a method to write the line), the result will have a
tabular format with each field in a column.
READING TO END-OF-FILE
The program in Figure 9.7 still suffers from an inconvenience when
dealing with large classes. The program requires that the user, in
preparing the data file, count the number of tests that were written. If an
error is made in this count, either some of the marks will not be
processed or the program might try to read more data than is actually in
the file, producing erroneous results. The solution is to have the program
adapt to the amount of data provided, rather than having the user count
it. Essentially, we want the program to read as much data as there is in
the file.
As we know (see Section 9.1), a file as a source for a stream can only
contain a finite amount of data. As a program continues reading from a
file, there must come a time when, in an attempt to read the next data
item, there is no more data in the file. This situation is called End-Of-File
(EOF). Clearly, the readType method that was attempting to read
cannot successfully return a data value. In BasicIO, after any read, it is
possible to determine if the read was successful or not. In particular, it is
possible to determine if the read was unsuccessful due to reaching EOF.
The function method isEOF() of the ASCIIDataFile class returns
true if the last readType method executed failed due to EOF. We can
use this method to control a loop that reads from a file until it reaches
EOF. The loop will have to be an in-test loop (see Section 6.4) since we
don’t know if we want to stop reading until we have tried to read and
failed. The general algorithm for reading until end-of-file is:
Each time through the loop, an attempt is made to read a data value.
Upon successfully reading a value, isEOF returns false and the code
continues to process the data item read. After exhausting the data in the
file, the last read is unsuccessful and isEOF returns true, causing the
loop to terminate.
Figure 9.11 shows a third version of the class average program that
incorporates reading to EOF. The data file is modified from that shown in
Figure 9.8, eliminating the third line—the count of the number of
students.
Figure 9.11 Example—Computing Class Average—third version
The program differs from that in Figure 9.7 only with lines 29–38
replacing lines 29–36 in the previous version. Since the number of
students is not recorded in the data file, we cannot read it and must
instead count how many pieces of data we read. Thus we initialize
numStd to zero instead of reading it (line 29) and, each time we
successfully read a student number, we increase the count by one (line
34). Within the loop, we now attempt to read a student number (line 32)
and, if we were successful (line 33), we read a mark (line 35). The
program assumes that the data will always be available in pairs (student
#,mark) and does not check if the read of the mark was successful. A
“bullet-proof” version should validate that the data file is well-formed,
but we omit that check for simplicity.
9.3
GENERATING PRINTED REPORTS
Computer applications often generate output to other devices than just a
monitor. Much of the information maintained in computers is presented
on the web. While a web browser presents the information on the
monitor, the application that processes the data transmits the pages as
html files across the network. Applications on smart phones typically
display data on the screen using a graphical user interface (see Chapter
10). Much information is printed on paper as printed reports.
Printed reports generally are presented in a columnar format with
related information in each column. At the top of the report and/or the
top of each page, there is typically a title that indicates what the report
represents. At the top of each column on the page, there is usually a label
indicating what information the column contains. Finally, at the bottom
of each page and/or the report, there is typically some summary
information such as totals and averages. Figure 9.12 shows the general
layout of a report.
Figure 9.12 Layout of a Report
The fields are the individual pieces of information presented in the
report. Usually, all of the fields on the same line are related. For example,
in a budget report the fields on one line could all relate to the same
department. The individual fields might be the department name, the
budget amount, the expenditures, the revenues and the current balance.
In the summary, there might be a entry for each column giving the total
budget, total expenditures, total revenues and total balance. On a student
grade report, each line would be for a single student and the fields might
be the student name, number, marks in various pieces of work and final
mark. Because the lines present details about individual departments,
students or whatever, they are typically called detail lines.
The BasicIO package includes a class for generating printed reports
called ReportPrinter. The class automatically handles pagination
and layout. To generate a report, it is first necessary to specify the layout
of the report by specifying the title lines and defining the columns
(fields). After the ReportPrinter object has been created, the method
setTitle is used to specify the title lines as a sequence of Strings,
printed one per line. These are automatically centered at the top of each
page of the report. Then the fields (columns) are added using the method
addField. Each call to addField adds a new column, left-to-right
across the page. A field has a name (String) by which it is referenced
within the program code and a label (String) that is centered above the
column on the printed report. Associated with each field can be a width
(number of characters that the column occupies) and a default format
(way that the data is displayed when a field is written).
Data is written into the fields of the detail lines using methods
writeType (similar to BasicIO output streams). When writing a data
value, the field into which it is to be written (String, the field’s name) is
passed as a first parameter to the write. The second parameter is the
value to write. An optional third parameter is a format specific to this
one write operation (i.e. replacing the default specified in the
addField).
The class also provides a number of other methods which, with the
options on the addField and write methods, provide a simple but
flexible report generation tool.
GENERATING A PRINTED MARKS REPORT
Let us make one final improvement to the class average report program
of Figure 9.11. In a large class, the number of lines (one per student)
displayed to the displayer will be such that it won’t fit on the screen and
the scroll bars will have to be used to view the entire report. Although it
is possible to print the display, only that part which is visible on the
screen will be printed. If our goal is to produce a useful report that can be
archived, it is better to generate a printed report. The report for the midterm test results might look like Figure 9.13.
Figure 9.13 A Marks Report
The report has two title lines—the course name and the piece of work
for which the report is being generated. There are two fields (columns),
one for the student number and one for the mark on the test with the
labels: Student # and Mark. The final line (the one labeled Average)
is a summary line with the average of the marks in the mark field. If the
number of students is large enough, the report will span multiple pages
and on the top of each page the titles and the header line (the column
labels) will be repeated.
Figure 9.14 Example—Computing Class Average—ultimate version
Figure 9.14 shows a program that generates the report as a modification
of the program in Figure 9.11. Instead of using an ASCIIDisplayer for
output, the program uses a ReportPrinter (lines 11 & 25). The report
setup is done by the helper method setUpReport. The detail lines
written by the helper method writeDetail and the summary by the
method writeSummary (as before).
Figure 9.15 shows the helper methods of ClassAve4. SetUpReport
replaces writeHeader from the previous version. It sets the two title
lines (line 51) as the course name and work name. It then (line 52) adds
the student number field with name stNum, label (for the column)
Student # and column width of 10 characters. Lastly (line 53) it adds
the mark field with name mark, label Mark and a field width of 5
characters. If you check the API for ReportPrinter, you will see that
the third parameter for addField is a Format. Format objects in Java
are objects that specify how a data value is to be presented as a piece of
text including things like controlling the number of decimal places and
including a currency mark ($), The BasicIO library defines a class
Formats, imported on line 3, that provides methods to obtain a variety
of different Format objects. The method getDecimalInstance
obtains a format for a double value that presents the value with the
specified number of decimal places (in this case 1).
Figure 9.15 Example—Computing Class Average—helper methods
The writeDetail and writeSummary methods are quite similar to
the methods in the previous version except that they use the write
methods of the ReportPrinter class. The methods take an additional
parameter—the name of the field into which the value is to be written.
Line 61 writes the student number into the field named stNum defined
in line 52. It is printed left-justified within a field width of 10 characters.
Line 62 writes the mark into the field named mark, defined in line 53,
formatted with one decimal place and prints it right-justified within a
field width of 5 characters. writeSummary generates the summary line
by writing into the fields of the report. Line 69 writes the string
Average into the stNum field which prints Average left-justified in a
field of 10 characters. Line 70 writes the average into the mark field
which prints it with one decimal place right-justified in a field of 5
characters. This is one technique for writing a summary line.
Alternatively a free-form summary line can be written by the method
writeLine.
9.4 UPDATING FILES
Data processing applications revolve around collections of information
stored in files. For example, a credit card company would maintain a file
of information which would include the card number, customer name
and address and the current balance owing for each card holder. The
information regarding a single card holder—called a record—would be
stored as consecutive fields of the file and the records for each card
holder would be stored consecutively.
Typically the information in a file is updated periodically. For example,
for the credit card file when a purchase or payment is made the
customer’s balance owing would change, requiring the file to be updated.
When a file is processed as a stream, since a stream can only be read or
written, but not both simultaneously, updating the file requires making a
new copy of the data in the file with the appropriate pieces of
information (e.g. customer’s current balance) changed. Since copying a
file is a relatively expensive operation, changes are made to many or all
of the records in a single execution of the update program. The basic
algorithm would be:
When the update program has been executed there are two files: the
original file with data unchanged—called the old file—and the newly
written copy with changed data—called the new file. The old file is now
obsolete and could be deleted. The new file becomes the “old” file the
next time the program is run.
A SIMPLE PAYROLL APPLICATION
Let’s consider a simple program to do weekly payroll for a small
company. When the program is run the payroll clerk enters the hours
worked for each employee. The program computes the pay owed each
employee and produces a report which is used to issue paycheques.
For the program to perform its task, it requires a file of information for
the employees including employee number and hourly pay rate. To be
able to report the total amount paid to each employee to the
government for tax purposes, the year-to-date pay is also needed. A
sample data file is shown in Figure 9.16. The file consists of one record
per employee with the employee number, name, hourly pay rate and
year-to-date pay. For convenience of the human reader, the records are
written one per line.
Figure 9.16 Data File for Payroll Program
Since the year-to-date pay changes when the employee is paid, the
program would have to produce a new employee data file with the
updated amount, which would be used as the old file the next week when
the program is run again. The report produced might look like that in
Figure 9.17.
Figure 9.17 Payroll Report
Figure 9.18 shows a program to perform the payroll application. The
ASCIIDataFile empData (lines 10 & 26) is the employee data (old)
file. The ASCIIOutputFile newEmpData (lines 12 & 28) is the
updated (new) employee data file. The program obtains the hours
worked from the payroll clerk via the ASCIIPrompter prompt (lines
11 & 27) and produces the payroll report to the ReportPrinter
report (line 13). To produce the report in landscape orientation, the
optional boolean parameter portrait on the constructor is passed
the value false (line 29). So that the report can list the date in the title,
the program creates a Date object and passes it to the method
setUpReport to establish the title and fields of the report (line 30).
Date is imported from java.util (line2). It then initializes the
summary statistics (lines 31 & 32).
The main processing loop is the for loop in lines 33–47. Since in a stream
only one field can be read at a time, it reads (line 34) the employee
number (first field of the employee record). If it cannot read an employee
number, the file has been fully read, and the loop terminates (line 35).
Under the simplifying assumption that the data file is valid, it then reads
the remaining fields of the employee record (lines 36–38), To make it
clear to the payroll clerk which employee’s hours worked is requested, it
sets the label for the prompt to the employee number from the record
and prompts for the hours worked (lines 39 & 40). It then calculates the
pay for the week, updates the year-to-date pay, writes a report detail
line, writes the updated employee record to the new file and updates the
summary statistics (lines 41-46). When all the employee records have
been processed, it writes the report summary and closes the files, report
and prompter (lines 48-52).
Figure 9.18 Example—Payroll Application
The helper methods are mostly self explanatory. setUpReport takes a
Date object as a parameter (line 68). Date objects represent an
instance in time. A new Date object represents the instant that it was
created (line 30). The Format object created in line 69
(getDateInstance) has a method format that formats a Date object
as month day, year , which is used to set the second title line to the
current date (line 69). Note also that writeEmpData writes a newline
after the fields for each employee so that each record is on a separate
line. This is unnecessary for the program to function, however it makes
the file more readable for the human user.
SUMMARY
Streams abstract the details of input, output and storage devices as series
of bytes or characters that can be read or written. The BasicIO library
provides three input streams and three output streams for input and
output to text and binary files and the keyboard and monitor. Processing
a stream involves opening it by creating a stream object, performing I/O
(reading or writing) and then closing the stream.
The details of the stream classes and their methods are described (as for
any library) by the API (Application Programming Interface) for the
BasicIO package. API documentation can be generated by the
javadoc program from the JavaDoc comments in the class program text
and is presented as web pages.
A file is a collection of data stored on a storage medium such as disk. A
file consists of a number of records, each containing information about a
single entity (such as an employee or a credit card holder). Within each
record are a number of individual data values called fields (such as
employee number or pay rate). Each read (or write for output files)
reads the next field. In a text file, the fields are sequences of ASCII
characters separated by tabs or newlines (called tab-delimited format).
Since a file contains a finite number of fields, if a program continuously
reads fields there will come a time when there are no more fields
available to be read. This situation is called EOF (end-of-file) and the
read operation fails. In BasicIO it is possible to test if the operation
failed and use this to control a processing loop to process as much data
as is available in a file.
BasicIO also provides a class for producing printed reports called
ReportPrinter. A report is specified by setting the title line(s) and
adding fields to the report. Each field represents a column in the report
and has a label which is printed at the top of the column. The format of
the data displayed in each field can also be specified. The report is
produced by writing values to the individual fields on the report in
succession, producing detail lines. ReportPrinter automatically
handles pagination and summary line(s) may also be written.
In typical data-processing applications, the program reads data from a
file, updates the data, and writes it to a new file as well as producing a
report from the data. When using streams, the fields of a record are read,
some of the fields are updated and then the fields are written as new
record to a new file, continuing until there are no more records.
REVIEW QUESTIONS
1.
T
F
A stream is a standardized view of I/O.
2.
T
F
A stream must be opened before data can be accessed.
2.
T
F A binary stream is a sequence of ASCII characters encoded
as bytes.
4.
T
F EOL is the situation that occurs when there is no more data
on a line.
5.
T
F The method setLabel is used to display a heading on a
report.
6.
T
F It is not possible to have more than one output stream open
at a time.
7.
T
F Binary files should only be used if a human user is able to
interpret them.
8.
Obtaining data from a stream is called:
a)
b)
c)
d)
9.
fetching
reading
accessing
none of the above
In the piece of code:
a)
b)
c)
d)
x is a field value and 5 is a number of decimal places
out is a stream and 2 is a field width
x is a field value and 5 is a field width
out is a stream and x is a field width
10. The following code will produce which output (spaces are
represented by _).
a)
b)
c)
d)
123456.789
123,456.789
123,456.7
_ _123,456.7
11. The following code will produce which output (spaces are
represented by _).
a)
b)
c)
d)
123.456
_ _ _123.
_ _123.0
_ _ _ _123
12. The following code will produce which output (spaces are
represented by _).
a)
b)
c)
d)
10,000
_10000
10000_
******
13. The following code will produce which output (spaces are
represented by _).
a)
b)
c)
d)
1.5
_ _1
1.50
1.500
14. What is wrong with the following program segment?
a)
b)
c)
d)
nothing is wrong
ASCIIDataFile should be used for output, not input
in should be declared as ASCIIPrompter
in should be public, not private
EXERCISES
1.
Write a program to generate a multiplication table for the integers
from 1 to 10 (as shown below) to an ASCIIDisplayer using
formatted output. Each line of the table would be generated by a
nested for loop, preceded by the factor (integer) for the row.
2.
Write a program to generate a standings report for the Niagara
Hockey League to a ReportPrinter. For each team, one record of
data is stored in an ASCIIDataFile with the following information:
team number (String), games won (int), games lost (int) and
games tied (int). The report should display, for each team, the team
number, games played, games won, games lost, games tied and total
points. Points are awarded based on 2 for a win, 0 for a loss and 1 for
a tie. The data file begins with an int representing the number of
teams in the league. The input file might begin:
and the report might begin:
3.
Widgets-R-Us has just completed an inventory of the items in its
warehouse. Each kind of item is identified by an item number. They
stock a quantity of each kind of item. Each kind of item also has a unit
value (i.e. value of 1 of the item). A file (ASCIIDataFile) has been
created giving, for each kind of item: the item number (String), the
quantity on hand (int) and the unit value (double). A program is
needed to produce a report (ReportPrinter) summarizing this
information in a form similar to the following:
In the report, the item number, quantity and unit value are the
values from the file. The gross value is the product of the quantity on
hand and the unit value. The total value is the sum of the gross values
over all items (i.e. the total value of the inventory in the warehouse).
Write a Java program to produce this report from the data file.
4.
National Widgets desires to automate their payroll system. Each
week, a timesheet is prepared for each employee giving the employee
number (String), number of hours worked (double) and rate of
pay (double). The timesheets are entered into a data file, one
timesheet per line. A program is needed to produce a payroll report
giving the week’s pay information for each employee in a form similar
to the following:
In the report, the employee number, hours and rate are the values
from the timesheet file. The gross pay is the product of the hours and
the rate. The tax is the 35% of the gross and the net pay is the gross
pay minus the tax. The summary totals are the totals of the gross, tax
and net, respectively.
Write a Java program to produce this report. The program should
read the timesheet information from an ASCIIDataFile and
produce the report to a ReportPrinter.
5.
Every month, Sharkey's Loans produces a report that specifies the
details of each loan. Sharkey has hired you to automate the production
of this report. For each loan, the information concerning each month's
activities is stored in a data file. Each line contains information about
a different loan, and includes the following information: loan number
(String), monthly interest rate (double), previous balance
(double), amount borrowed by the customer this month ("debits",
double) and amount paid by the customer this month ("credits",
double). You are to write a program to produce a report in a form
similar to the following:
In the report, the loan number, interest rate, previous balance, debits
and credits are the values from the monthly data file. The new balance
is calculated as the previous balance, plus debits, minus credits plus
interest where the interest is computed on the previous balance plus
debits, minus credits. The minimum payment is 25% of the new
balance. The summary totals are the totals of the previous balance,
debits, credits, new balance and minimum payments, respectively.
Write a Java program to produce this report. The program should
read the monthly data from an ASCIIDataFile and produce the
report to a ReportPrinter.
10 GRAPHICAL USER INTERFACES
CHAPTER OBJECTIVES
·
·
·
·
Explain the modal dialog model of user-interaction
Describe the interaction pattern for various types of widgets
Apply BasicForm in providing a user-interface for a program
Apply a GUI to control the action of a program
Since the introduction of the Macintosh™ computer by Apple in 1984,
graphical user interfaces (GUIs) have become the norm for computing.
In a operating system or program using a GUI, the screen presents a
number of objects—icons, folders, windows, buttons—often called
widgets with which the user interacts to perform a task.
Like most modern programming environments, Java provides resources
—included in the awt and swing libraries—for writing programs that
interact with a user using a GUI. While complete and flexible, these
libraries require significant programming experience to use, and a
special style of programming called event-driven programming. So that
we can make use of simple GUIs in our programs, the BasicIO library
provides a simplified GUI model.
10.1 THE BasicForm CLASS
The BasicIO library provides a class called BasicForm which can be
used by a program to present a GUI to the user. A BasicForm object
presents a window on the screen (Figure 10.1) in which interaction
objects—widgets—are presented. Technically, a BasicForm object
represents a modal dialog, a style of interaction in which the program
executes to a point where it requires user input and then it is suspended
until the user makes the input. The interaction is modal—either the
program is executing or the user is interacting with the GUI, but not both
at the same time. The windows presented by classes such as
TurtleDisplayer, SoundPlayer, ASCIIPrompter and
ASCIIDisplayer all use forms created by BasicForm.
To use a GUI, the program creates a BasicForm object, specifies the
buttons (e.g. Load) and then adds various widgets such as text boxes
and canvasses to it. In adding a widget the position of the widget on the
form (window) can be specified as a pixel position starting from the topleft corner. Once the form has been set up, the program can switch to
user-interaction mode, presenting the form on the screen. The program
is then suspended while the user interacts with the widgets on the form,
ending user-interaction mode by pressing one of the buttons. At this
point, the program resumes and is able to determine which button was
pressed and can read information (settings) from the widgets on the
form to determine what the user has specified.
Figure 10.1 BasicForm for User Interaction
10.2 LOAN CALCULATOR
As a first example of a program using a GUI, let’s write an application that
allows the user to determine the payment amount and cost of borrowing
for a loan. The payment amount for a loan is determined by the
compound interest formula:
where p is the principal (amount borrowed), r is the interest rate (per
year), n is the number of years and np is the number of payments per
year (e.g. 12 for monthly payment). The result (pa) is the payment
amount per payment (e.g. per month for 12 payments).
The program should allow the user to enter the four variables (amount,
rate, years and payments/year) and display the payment amount, total
amount paid over the term of the load and the cost of borrowing (paidborrowed). This could be provided by a form such as seen in Figure 10.2.
The boxes labeled Principle etc. are widgets called text fields to which
the program can fill in values (write) or access the value currently in the
field (read). The program fills in some (or none) of the fields and then
presents the form to the user. At that point, the form appears on the
screen, the program is suspended, and the user interacts with the form
by entering/changing values in the fields. When satisfied with the
interaction, the user presses the OK button. The form then disappears
and the program continues execution, reading values from some of the
fields and continuing.
Figure 10.2 Loan Calculator Form
The program in Figure 10.3 is a program to provide a basic loan
calculator presenting a window (form) as in Figure 10.2. The program
declares and creates a new BasicForm (lines 14 & 25) from the
BasicIO library (line 3). When created, the form is empty (i.e. has no
widgets) and has one button labeled OK. The form is hidden (i.e. not
visible to the user). The helper method setUpForm (lines 44–60) is
called to add the widgets to the form.
Forms are similar to reports in that they consist of a number of entries
(widgets for forms, fields for reports) that are added to the form/report
with an associated name that is used to refer to the field/widget in
subsequent operations. For forms, there is an addType method for each
type of widget that can be added to the form, such as addTextField
for text fields. These methods include a number of parameters, some of
which are optional. All widgets include a name (String, used to
reference the widget in subsequent code), label (String, displayed with
widget on the form to identify it to the user) and position (pair of int
(x,y) giving the pixel position of the top-left of the widget on the form
with (0,0) being the top left corner of the form, x being the column and
y the row). Details of BasicForm methods are found in the BasicIO
API.
setUpForm first sets the title for the form (displayed in the title bar of
the window) to Loan Calculator (line 45). It then adds six text fields
for the principle, rate, amortization period, number of payments,
payment amount, amount paid and cost of borrowing (lines 46–59). Like
a field in a report, a text field on a form can have a format associated with
it. Data written to the field will be formatted according to this format. For
dollar amounts getCurrencyInstance (imported from
BasicIO.Formats, line 5) is used. For percentages,
getPercentInstance is used. For integral amounts
getIntegerInstance is used. A text field also has a width—the
number of characters that should fit in the associated box. If the amount
of text entered into a text field exceeds this, the text will scroll within the
box. For example, on lines 46 & 47 a text field named p is added to the
form with a label Principle. The data in the box will be formatted as
currency. The box will be 10 characters wide positioned at pixel position
(20,10).
The constructor then calls initForm to pre-load some widgets in the
form with data. Although this is not necessary (the user could enter all
the data), it is often advisable to provide default values for widgets in
case the user does not enter values. On line 64, initForm writes 1000
to the widget with name p (the widget added to the form in line 46),
causing $1,000.00 to be displayed in the Principle box. Lines 65–
67 initialize other widgets. The methods writeType are similar to
those in ReportWriter.
Figure 10.3 Example—Loan Calculator Application
Once the form is prepared, it is displayed to the user and the program
suspended at line 28 through the call the BasicForm method accept.
The user is then free to enter and/or change values in the text fields,
eventually pressing the OK button. At this point, the form is hidden and
the program resumes execution at line 29. It then uses the readType
methods to read the values that are present in the text fields (lines 29–
32). These may be the values pre-loaded by the program in intiForm
or values entered/changed by the user prior to pressing OK. The
payment amount, amount paid and cost of borrowing are computed
(lines 33–35) and written to the form (lines 36–38) and the form is
redisplayed, suspending the program (line 39). Once the user is finished
and presses OK, the form is hidden, the program resumes and terminates.
The program in Figure 10.3 is adequate, but it lacks refinement. If a user
is using a loan calculator, they are probably wanting to consider a
number of scenarios, for example the difference between paying monthly
or bi-weekly. In such a situation, it would be undesirable to have to rerun
the program for each different scenario. It would be much better if the
user could enter some values, compute the results and then change some
values, computing the result again until all scenarios have been tried.
Figure 10.6 is a refinement of the loan calculator program to support this
option. It presents a form as in Figure 10.4. In this version, the form has 2
buttons: Calculate and Quit. The user, after filling data into some
text fields, presses Calculate to compute the payment amount, etc. If
the user wants to try another scenario, s/he can revise the information in
some fields and then press Calculate again, and so on until satisfied
and pressing Quit, which terminates the application.
Figure 10.4 Revised Loan Calculator Form
When the form is created (line 26), the names of the buttons are supplied
as String parameters. A form can have any number of buttons. The
constructor with no parameters (as used in Figure 10.3) creates a form
with one button OK. An alternative constructor takes one or more
String parameters that are the buttons for the form (here two:
Calculate and Quit).
The accept method is actually a function method that returns the
button number (numbered left to right from 0) of the button that was
pressed by the user. When there is only one button, this is irrelevant and
the result of accept can be ignored (line 28 in Figure 10.3). However,
when there is more than one button, the program does different things
depending on which button is pressed, so the value returned by accept
is relevant. Here is it stored in the variable button.
Typically the program does repeated processing each time the user
presses a button. One button (here Quit) is a signal to the program to
terminate processing. This leads to a common algorithm for form
processing as shown in Figure 10.5 where n is the number of the button
indicating termination.
Figure 10.5 Form Processing Algorithm
This leads to the processing loop (lines 29–42) in Figure 10.6 which
repeatedly reads the values entered (lines 32–35), computes and writes
the results (lines 36–38 & 39–41) until the user presses Quit (button 1).
There is one other subtle change in Figure 10.6. In the setUpForm
method, there are calls to the method setEditable on lines 59, 62 and
65. Widgets can either be editable, meaning the user can interact with
them or not editable, meaning the user shouldn’t change the value (it is
for display only). Since the user should not change the payment amount,
amount paid or cost of borrowing fields, they are set not editable. The
corresponding box is grayed out (see Figure 10.4) to indicate this and the
user is unable to edit the value displayed.
Figure 10.6 Example—Loan Calculator, revised
10.3 REDEYE CORRECTION REVISITED
Let us reconsider a the program we wrote to do redeye correction on a
picture in Section 5.4 (Figure 5.8). While effective, the original program
suffers from two significant faults. Firstly, the tolerance measure —
distance from red—was built in to the program requiring the program to
be modified and recompiled to try different tolerance measures.
Secondly, any pixel in the image that was “close” to red was changed to
black, even if it wasn’t part of the eyes in the image.
A preferred solution would be to allow the user to supply the tolerance
and try different tolerance values on the same run of the program.
Secondly, the user should be able to specify a region of the picture that
contains the eyes and the program would apply the correction algorithm
only on that region. These can be accomplished by presenting the user
with a form like that in Figure 10.1. The user presses Load to select and
load a picture for redeye correction. Once the picture is displayed, the
user selects a region of the picture to process (From X Y and To X Y),
provides a Tolerance and presses Correct. The program applies the
correction and displays the result. The user presses Save to save the
result, Quit to quit (without saving), enter new values to change the
correction region and/or tolerance and press Correct or load a new
image pressing Load.
Figure 10.8 is a modified version of Figure 5.8 that uses a GUI interface to
control the program. A BasicForm with four buttons is declared and
created (lines 11 & 24). setUpForm (lines 76–86) sets the title and adds
a canvas for displaying the picture and text fields for entering the (x,y)
coordinates for the top-left and bottom-right pixel positions for
correction and the tolerance value. An initial picture consisting of one
white pixel is created and loaded into the form by calling initForm
(line 27). initForm (lines 91–98), puts the picture on the canvas,
initializes the region for correction to include the entire picture and sets
the tolerance to 150.
The main processing loop is lines 28–51 as an instance of the algorithm
in Figure 10.5. It displays the form and awaits user interaction (line 30).
The loop terminates if the user presses Quit (button 3). Otherwise it
handles either a load (button 0), correction (button 1) or save (button 2)
using a switch statement (see Section 6.4).
When Load is pressed, it loads a new picture using an open dialog and
re-initializes the form with the new picture (lines 33 & 34). When
Correct is pressed, it reads the co-ordinates (x,y) of the region for
correction and tolerance (lines 38–42) from the form and calls the
method correct to apply redeye correction to this portion of the
picture (line 43). If Save is pressed, it saves the modified picture using a
save dialog. In any event, after performing the requested operation, it
redisplays the form so the user can make another selection (line 29).
The process of handling multiple buttons is commonplace in working
with forms and the general algorithm (as an extension of Figure 10.5) is
shown in Figure 10.7 for n buttons numbered 0–n-1 with n-1 being
quit.
Figure 10.7 Processing Algorithm with Multiple Buttons
Figure 10.8 Redeye Correction, Revised Version
The method correct is a modified version of that in Figure 5.8. In
addition to the picture (pic) as a parameter, it takes the bounds of the
area upon which to apply the correction as ints: x1, y1, x2 and y2 and
the closeness measure: tolerance, a double. So that it can process a
subarea of the picture, it uses iterative for loops (lines 64-71) and
indexing (line 66) to access the pixels in the range (x1,y1) to
(x2,y2).
10.4 WIDGETS
Table 10-1 lists the widgets supported by BasicForm. BasicForm has
methods of the form addWidget for each of the different types of
widgets. When a widget is added to the form, it is given a name (String
literal) by which it is referenced in other BasicForm methods. The label
is a piece of text that identifies the widget on the screen. The (x,y)
coordinates in the method call specify its position on the form with
(0,0) being the top-left corner. Other parameters vary by widget type
and most parameters are optional. The complete description of the
addWidget methods is found in the BasicIO API.
Table 10.1 BasicForm Widgets
Figure 10.9 shows a BasicForm containing one of each kind of widget
on a form with three buttons. The user cannot interact with a label; it is
simply there for information or annotation. Clicking in the checkbox
changes the setting from checked to unchecked or vice versa. Clicking on
one of the radio buttons selects that button and unselects all others.
Clicking and dragging the cursor on the slider right or left increases or
decreases the slider value within the range. The value selected is shown
to the right. Sliders may also be displayed vertically rather than
horizontally. Pressing the sound play button plays the sound. Text can be
typed in the text field either replacing the existing text or adding to it. If
there is too much text for the box it scrolls left. A canvas can display a
picture. If the picture is too large, scroll bars allow the user to move the
view of the picture. A Turtle can also draw on the canvas or the picture
on the canvas. Text can be typed in a text area either replacing or adding
to the existing text. Pressing the enter/return key on the keyboard
moves to the next line. If the text is too big, the text are will scroll leftright or up-down or both. Pressing on any of the buttons hides the form
and returns control to the program.
Figure 10.9 BasicForm Widgets
[14]
Figure 10.10 is the program that displays the form in Figure 10.9. The
program doesn’t do anything useful other than demonstrate adding
widgets of various kinds to a form and having a main processing loop.
The constructor creates a form with three buttons (lines 11 & 16) and
calls setUpForm to add the widgets to the form. It then calls initForm
to set the widgets to initial values. The main processing loop (lines 19–
32) displays the form (line 20) and checks the button pressed by the
user, terminating on Quit (button 2). It then writes a line to the text
area (whose name is msg) an indicating which button was pressed.
The setUpForm method (lines 37–47) sets the title for the form (line
38) and then adds the widgets to the form. The label is added at position
(10,10) with name lbl and label Label. The checkbox is added at
position (100,10). The radio buttons are added at (10,40), in the
horizontal (false) direction, with four buttons labeled 0, 1, 2 and 3.
The slider is added at (10,100) with size 200 pixels and range 0-100.
The sound play button is added at (10,150). The text field is at
(120,150) and is 10 characters wide. The canvas is at (10,185) and
is 308×266 pixels. Finally, the text area is at (325,185) and is 15 lines
high and 40 characters wide.
initForm demonstrates writing information to widgets on a form. This
is done both to initialize settings (e.g. radio buttons) and to display
information to the user (e.g. canvas). BasicForm supports methods of
form readType and writeType in some meaningful way for each
widget type, in roughly the same way that ASCIIDataFile and
ReportPrinter do.
The writeType methods write text to or change the setting of the
widget specified by the name as the first parameter. Checkboxes have
two states checked/unchecked represented by the boolean values
true/false. Line 55 sets the checkbox to checked. Radio buttons are
numbered 0–n-1 left to right. Line 56 writes the value one to the radio
buttons selecting the second button (1). Sliders have a value in the range
specified. Line 57 writes the value 50 to the slider setting it to the midpoint. Text areas can contain any text. Line 59 writes a string to the text
box. Similarly, a text area contains text which can include line breaks
(\n) dividing it into lines. Line 61 writes a line of text (with a newline at
the end) to the text area. Sound fields and canvases can only be used to
present special kinds of data: Sounds and Pictures. Lines 58 and 60
place a sound and a picture onto the respective widgets. In most cases,
BasicForm will interpret writing of different kinds of data to a widget
in a reasonable way. For example, writing zero to a checkbox will set it to
the unchecked state. A text field can have a format associated with it
which will control the formatting of values written to it. Some write
methods allow a format and/or field width to be specified which also
provides formatting when written to a text field or text area. Other
options are listed in the API.
Figure 10.10 Example—Display Widgets
The readType methods read the current selection/setting/text of the
widget specified by the name as the first parameter. Typically these are
called after the form has been presented to the user (accept) to
determine what the user has selected. BasicForm will interpret the
setting/value in the widget in an appropriate way depending on the read
method used. For example, readBoolean on a checkbox will return
true for a checked box while readInt will return 1. The text in a text
field or text area can be read by readString. However methods such
as readInt will interpret the text as a value (e.g. int for readInt) and
can include a format if desired. Again the API contains complete details.
SUMMARY
Programs interact with users via graphical user interfaces—a collection
of user-interface widgets such as buttons, text boxes, folders and
windows. Java provides a comprehensive collection of classes for
building GUIs using a programming model called event-driven
programming. BasicIO provides a simplified GUI programming model
called modal dialogs (BasicForm).
Using BasicForm, widgets are added to a form and the form is then
presented to the user, suspending the program. When the user has
finished interacting with the form, s/he presses one of the buttons, the
form is hidden, and the program resumes execution. The program can
determine which button was pressed and can access the information
represented by the widgets with which the user has interacted.
REVIEW QUESTIONS
1.
T
F In a modal dialog, either the program is waiting for the user
or the user is waiting for the program.
2.
T
F Using the BasicForm class, a program can execute while
the user is updating the form.
3.
T
F A sequence of nested if statements is the “standard” way to
handle user button presses on a BasicForm.
4.
T
F The value displayed in a label can be formatted by supplying
a Format object when the label widget is added to the form.
5.
T
F The effect of a writeType method depends on the kind of
widget referenced by the name supplied as the first parameter.
6.
Which is true of widgets in BasicForm?
a)
b)
c)
have a name by which they are referenced by methods
are positioned at an (x,y) pixel position on the form
may be editable or not editable on the form
d)
7.
all of the above
The accept method in BasicForm
a)
b)
c)
displays the form on the screen
suspends the execution of the program
returns the button number of the button pressed by the
user
d)
8.
Which widget would be used to allow the user to select between a
number of mutually exclusive options such as choosing from
red/green/blue or unleaded/premium/diesel?
a)
b)
c)
d)
9.
all of the above
check box
radio buttons
slider
text field
Which widget would be used to allow the user to select between two
opposite values such as on/off, yes/no or show/hide?
a)
b)
c)
d)
check box
radio buttons
slider
text field
10. Which widget would be used to allow the user to enter a value within
a specified range such as 1–10 or 0–255?
a)
b)
c)
d)
check box
radio buttons
slider
text field
EXERCISES
1.
Write a program to brighten/darken an image. The program should
present a GUI with two canvasses, one for the original image and one
for the result of brightening/darkening the image. A slider should be
used to allow the user to select brightening factor between 0 and 1000
(being a percentage such that 0% would be darkening to black, 50%
would be half as bright, 100% would be leaving the image unchanged
and 1000% would be 10 times the brightness). There should be 4
buttons. The form might look like:
Pressing Load would present an open dialog to select a picture to be
enhanced, displaying it on the first canvas. Pressing Apply would
enhance the picture, creating a new picture in which the color
channels for each pixel in the original are multiplied by the
brightening factor, displaying the result on the second canvas.
Pressing Save would present a save dialog allowing the user to select
a location and name to which the enhanced picture would be saved.
Pressing Quit would terminate the program.
2.
Rewrite Exercise 7.3 to use a GUI for user interaction instead of
relying on constants. The program should display a form into which
the user can enter the duration (in samples), frequency (in hertz) and
amplitude (between 0 and 32767). The form might look like:
When the user presses Generate, the program will generate the sine
wave and display the form with a play (sound) widget to allow the
user to hear the generated sound. When the user presses Quit, the
program will give the user the opportunity to save the sound and then
terminate.
3.
Rewrite Exercise 8.2 to allow a user to build up a sound by splicing a
number of sounds together. The form should have 5 buttons: Load,
Splice, Save, Clear and Quit. There should be two play (sound)
widgets, one for the spliced sound and one for the most recent clip
that has been loaded. In addition there should be two text fields that
show the duration of the spliced sound and the duration of the clip,
respectively (in seconds and starting from zero). The form might look
like:
When the user presses Load, an open dialog should be presented to
load a sound clip for splicing. This clip should be played if the clip play
widget is pressed. When the user presses Splice, the currently
loaded clip should be spliced to the end of the spliced sound. If
nothing has been spliced yet, the spliced sound would be of zero
length and the result would just be the clip. Pressing the play widget
for the spliced sound would play the result. When the user presses
Save, a save dialog should be displayed allowing the user to select a
name and location to which the spliced sound will be saved. Pressing
the Clear button should reset the spliced sound to empty and clear
the clip. Finally, pressing Quit terminates the program.
4.
Every 5 years Statistics Canada completes a population census of the
country. There are plans to have census takers go door-to-door to
collect the census data. To facilitate this, the census takers will be
supplied with a computer tablet to assist them in collecting and
recording the data. You are to write the application program that will
run on the tablet.
The program will be form based. The user (census taker) will launch
the program and select a file of addresses that they will visit. The
program will then present a form for each address in turn as the
census taker walks from house to house. The Household form will
look like:
The Address field is the address that the census taker should visit
next. When she is at that address, if no one is home, she presses the
Skip button and will be presented with the next address. If someone
is home, she fills in the number of people that reside in the home and
presses OK.
When OK has been pressed, the program will present an
Individual form for each of the people residing at the household
such as:
The Person field is the number of the person residing at the
household (increasing by one for each form presented at the
household). The census taker fills in the name and age and selects the
sex and language (first learned). Upon pressing OK the data is
recorded and the form for the next person residing in the household is
presented. When there are no more members in the household, the
next Household form is presented. When there are no more
addresses in the address file, the program will terminate.
The program will produce a file (ASCIIOutputFile) of census data.
This file will include one record (line) per individual at each
household where someone was home. The record will include:
address (String), person number (int, from 1 for household), name
(String), age (int), sex (int: 0 for male, 1 for female) and language
(int: 0 for English, 1 for French, 2 for Other). The census data file
might look like:
The program will also produce a report such as:
The report will include one detail line per household where someone
was home. The detail line will include the address, number of
residents, number of female residents, number of male residents,
number of English residents, number of French residents and number
of non-English, non-French residents. At the end of the report, a
summary line should include the total number of residents in the
households visited and the breakdown by sex and language.
11 CLASSES
CHAPTER OBJECTIVES
Explain the relationship between class state and behavior.
Apply data abstraction in the design of a multi-class program.
Employ the principles of information hiding in the design of a
class.
·
Implement a program using multiple classes.
·
Explain the process by which objects are made persistent.
·
Apply persistence in the implementation of a program.
·
·
·
So far all of our examples have involved writing one class (the main
class) and making use of library classes such as Turtle and
PictureDisplayer. Real-world projects usually involve hundreds or
thousands of classes, some written explicitly for the project and some
from libraries that have been custom written or purchased. In this
chapter, we will look at how programs with multiple classes are written
and how the classes—actually, objects as instances of those classes—
interact.
Classes represent entities within the computer system. Many of these
entities correspond to real-world entities that are involved in the process
the program is automating. For example, in a payroll system, employees
are part of the real-world system. A payroll program may thus involve a
class representing employees. Entities in real-world systems are present
for long periods of time, certainly longer than the execution of one
computer program. Java supports persistent objects—objects whose
lifetime extends beyond the execution of a single computer program—
allowing us to model this situation.
11.1
CLASSES REVISITED
We first encountered classes in Chapter 2. There we saw that the class is
the major building block of Java programs. Everything that we write is a
part of a class and a program is a collection of classes. When a program
executes, we create instances (objects) of one or more classes and these
objects interact to produce the desired results. In Section 2.1, we wrote
one class: Square. The one instance of the class Square, created in the
main method, interacted with the one instance of the class Turtle (a
library class) created in the constructor of Square to draw a square on
the screen. Later programs used more classes, for example, in Section 9.4
we used a number of input and output streams and a report class to
implement a simple payroll system.. However, we still wrote only one of
the classes ourselves.
CLASSES AND OBJECTS
The class declaration syntax in Figure 2.6 shows that a class is a
collection of declarations that includes constructors, fields, and methods.
Constructors are “methods” that are executed when an instance of the
class is created. Fields, also called instance variables, allow information
to be remembered by the object through its lifetime. Methods allow the
object to perform some actions.
Up to now the class we wrote—the main class—had a single constructor
which performed the actions of our program. It made use of objects
created from imported libraries, methods of the class as helper methods
and stored information about the system as instance variables. The main
method created an instance of our class, causing the constructor to
execute and produce our desired result. When the constructor finished,
the program terminated.
A constructor’s purpose is to put an object into a valid initial state so that
it can interact with other objects in a meaningful way. A class may define
more than one constructor. For example, PictureDisplayer had a
constructor with no parameters that created a display with a standard
size canvas for picture display. An alternative constructor took a
Picture as a parameter and created a custom size canvas upon which
the picture was placed.
Instance variables serve as an object’s memory. For example, in early
programs we used an instance variable to remember which Turtle
object we were using. Clearly, the Turtle object itself must remember
where it is on the page and in which direction it is traveling, so the
Turtle class defines some instance variables as well.
We used methods in two different ways. When we were using methods of
the object itself—those defined in the class—we used the simple method
call syntax:
When we were using methods of another object—those defined in a
different class such as Turtle—we used the method call syntax:
where objectName is a reference to the object we are asking to do the
task—the target object. In the first case, we consider that the object
itself is doing the task, so it is not named. In fact, there is a reserved word
this that always represents the object itself, and so the first case is
really shorthand for:
Thus a method is always performed by some object. It may be this object
itself or some other referenced by a reference variable.
An object, which is an instance of a class, has a memory and can perform
actions. Its memory is represented by the instance variables defined in
the class. The methods defined in the class represent the actions it can
perform. We can think of the objects as sentient entities having an
intelligence of their own. When we write large-scale programs, we
consider the execution as a result achieved by the cooperation of a
number of such sentient entities, much like any human endeavor. By
analogy, in the real world we interact with other people to achieve some
goal: we might interact with a teller in a bank to withdraw some money.
Writing large programs involves writing a number of classes and, in the
main class, creating instances of a number of these classes, which
cooperate to produce the result. In this chapter we will look at object
interaction.
STATE AND BEHAVIOR
As we have seen, a class declaration consists of constructor declarations,
field (instance variable) declarations, and method declarations. When a
new object is created, the constructor is first executed. While the object
exists, it has memory represented by its instance variables and can
perform actions, which are its methods.
Since the methods of the class can refer to the instance variables of the
class, what they do can depend on the current values of the instance
variables. The method could do different things at different times. For
example, the line drawn by a Turtle using the method forward goes
in a different direction depending on the current direction the Turtle is
facing. After the forward operation, the Turtle is at a different position.
We say that objects have a state which is represented by the values of its
instance variables. And the objects have a behavior, which is what the
methods of the object do. Its behavior depends on the state and its
behavior can change the state. This is like any sentient entity. How we
react to a situation (behavior) depends on out life experiences (state).
Performing an action also changes our life experience.
Since behavior depends on state, an object must be in a well-defined
state when it begins its life if it is to have well-defined behavior. This is
the role of a constructor: to put the object into a well-defined initial state
consisting of appropriate initial values for the instance variables. For
example, the constructor for a Turtle object sets the initial direction to
east and the initial position to the center of the drawing page.
11.2
DATA ABSTRACTION
As we saw when we discussed procedural abstraction (Chapter 3), as a
system gets more complex, it is necessary to abstract details to allow an
understanding and make it possible to write a program. Unfortunately,
procedural abstraction is not powerful enough, on its own, to allow us to
handle large programs. A second kind of abstraction, data abstraction,
is also needed.
Classes allow us to abstract details concerning a certain kind of entity,
perhaps the employees in a company or the students in a university.
Within the class, we can use information hiding to hide the details of
what an object will remember via instance variables and how it achieves
its behavior via method bodies. In a program with many classes, only the
class representing the particular entity needs to concern itself with what
is remembered and how the behavior is achieved; all other classes
(objects) need worry only about what the entity can do. This is exactly
how we have used Turtle objects since Chapter 2. We used Turtle
objects, knowing only what they could do for us, for example, move
forward, or turn right. We didn’t have to know how they knew where
they were on the page or how a line was drawn from one place to
another on the screen.
Data abstraction is abstracting details about state and behavior within a
class. Objects from other classes do not (and, using information hiding,
cannot) need to know about the way the state is represented and how
the behaviors are achieved. They are also prevented from changing the
state (i.e. instance variables) of another object except indirectly by
executing a method of the object. This makes writing the code for a class
much easier.
11.3
PAYROLL SYSTEM REVISITED
Let’s consider a program that uses data abstraction via multiple classes.
It is a variation of the payroll example in Section 9.4. Each employee is
paid according to a particular rate of pay in dollars per hour. The payroll
system inquires of the payroll clerk how many hours each employee
worked in the past week and produces a report indicating the net pay for
each employee, such as seen in Figure 1.1.
Figure 11.1 Payroll Report
The program is to read a data file of employee information (Figure 11.2)
and produce an updated version of the file as a result.
Figure 11.2 Payroll Data File
Although we could probably use procedural abstraction in this small
problem, let’s consider using data abstraction. Employees are clearly
entities involved in the system. Within a class called Employee, we can
encapsulate all the details about an employee, including the rate of pay
and the process used to calculate the employee’s net pay. If we have a
variable anEmp that is a reference to an Employee object, we could do
something like the following:
The class Employee defines methods including: payForHours, write
and getYTDPay . The method payForHours “pays” the employee—
calculates the amount to pay, updates the year-to-date pay and returns
the pay amount as its result. The method write writes the employee
data to the designated stream. The method getYTDPay returns the
current year-to-date pay for the employee. Note how we can write this
code without worrying about details such as how the employee knows
his or her rate of pay or how the pay is calculated. We can neglect
concerns such as overtime hours. This is the power and beauty of
abstraction.
THE Payroll2 CLASS
The main class, Payroll2, is shown in Figure 11.4. It is essentially the
code in Figure 9.18 with the details about employees abstracted out and
using a form instead of a prompter. It declares a reference to an
Employee object (anEmp) on line 20. The main processing loop (lines
33-44) corresponds to the loop in the original.
Line 34 creates a new Employee object using a constructor that takes a
stream as a parameter. This constructor reads employee data from the
stream (notice we don’t need to know what data is read nor how it is
read), creating an Employee object representing the employee whose
data was read. Since the data is being read from a stream, it must be
finite and at some point end-of-file must be reached. This event is used
(line 35) to terminate the loop.
The program uses a BasicForm to provide a GUI as shown in Figure
11.3. Line 36 calls a helper method to fill the form with the information
about the current employee (anEmp). Note that only the object reference
need be passed. The object encapsulates all of the information about the
employee it represents including the number, name, pay rate and yearto-date pay.
Figure 11.3 GUI for Payroll Program
As described above, on line 39 the payForHours method of the
Employee class takes the hours worked as a parameter, computes the
gross pay and updates the year-to-date pay, returning the gross pay as its
result. On line 40, the helper method writeDetail is passed the
employee reference, hours worked and pay. It can obtain the employee
information for the detail line from the object. The Employee class
provides a method writeEmp that takes a stream as a parameter and
writes the updated employee information to the stream (line 41). Finally,
the year-to-date pay is obtained via the Employee method getYTDPay
(line 43) to update the total year-to-date pay.
Figure 11.4 Example—Payroll System Main Class
Figure 11.5 show the helper methods of the Payroll2 class. Most are
self-explanatory. Note how, in fillForm and writeDetail, the
employee information is passed via an Employee object. The methods
access the particular pieces of employee information via methods
provided by the Employee class. Compare also the version of
writeDetail in this example with that in Figure 9.18. Previously each
piece of employee information was passed separately. Now the
Employee object encapsulates all this data as a single object, simplifying
the method call.
Figure 11.5 Payroll System Helper Methods
THE Employee CLASS
The Employee class is not a main class, but just another class within a
program. The syntax for a class is the same (see Figure 2.15) whether or
not it is a main class. The difference is whether the class has the special
method main. Another difference between a main class and any other
class is that execution begins within the main class, specifically in the
method main. Execution occurs in other classes only when an object is
created (when the constructor is executed) or the object is called upon to
perform some action (when a method is executed).
Figure 11.6 shows the Employee class. There are four instance variable
declarations (lines 10–13) representing the attributes of an employee
relevant to this application. They serve as the memory of the employee.
These are the things that make one employee different from another and
allow the Employee object to do its job within the program. Following
these is a constructor and a number of methods: the actions that an
Employee object can perform.
Consider the method payForHours. This method is called by the main
class in line 39 of Figure 11.4. At this point, the Payroll2 object is
suspended and the Employee object executes its payForHours
method using its instance variables and the parameter hours whose
value is provided by the Payroll2 object. Thus the gross pay is
calculated from this employee’s rate of pay and the specified hours
worked. At a different time, with a different Employee object, the
computation would result in a different result. The method changes the
object’s state when it updates the YTDPay instance variable. This shows
how behavior (value of gross pay) depends on state (rate) and how
behavior can change state (YTDPay).
The methods getEmpNum, getEmpName, getEmpRate and
getYTDPay are used by the main program to obtain information about
the current employee (for example in lines 94, 95, 96 and 99 in Figure
11.5).
Figure 11.6 Example—The Employee Class
Figure 11.7 shows the memory model at the point of the call to
payForHours within the main class. Only the relevant methods and
variables have been shown. In the constructor for Payroll2, anEmp is a
reference to the employee object that was just read. It has obtained the
number of hours worked (hours) from the form (display). It also has
storage for the computed gross pay (pay). The Employee object has
instance variables empNum, empName, rate and YTDPay. Within the
called method (payForHours), the formal parameter hours has been
passed the value of the actual parameter (hours in Payroll2). When
execution begins within payForHours, the code references the rate of
pay for the employee (instance variable rate) and the hours worked
(formal parameter hours) to compute the gross pay (local variable
pay). Note that, although the values of local variables become undefined
each time a method is called, the values of the instance variables are
retained as long as the object exists, providing the object’s long-term
memory.
Figure 11.7 Memory model for Payroll program
Let’s turn our attention to the constructor for the Employee class. The
purpose of a constructor is to place the object into its initial state. When
an Employee object is created, it must come into existence knowing
about itself—its employee number, name, rate of pay, year-to-date pay.
Since this information is present in a file, the constructor must read from
the file. Since all the employee information is in one file, each time an
Employee object is created, the same file must be read, This means that
the Employee constructor cannot open the data file (or different files
would be used each time). The solution is to pass the file object as a
parameter to the constructor. Remember, just like methods, constructors
may have parameters.
We know that a file is not infinite—at some point there will be no further
data to read. In this program, the data file should be read, one employee
(line) at a time, until there is no more data. The data is read by calls to
the Employee constructor. This means the constructor must be able to
handle the possibility that there is no data left in the file. Basically, the
constructor implements the body of the read to end of file algorithm
(Section 9.2) without the loop. It attempts to read the employee number
(line 19) and only if not EOF (line 20) does it read the rest of the fields
(lines 21–23). After the constructor returns, the calling method can
check the status of the input stream object and if EOF, terminate the loop
(lines 34 & 35 in Figure 11.4). Note that the call to the constructor is
being used as if it were a read operation.
This processing loop in Payroll2 demonstrates an important concept.
Each time through the loop, a new Employee object is created, one for
each record of input data. Over the complete execution, a potentially
large number of objects will be created. Since each object uses up some
computer memory, it is possible that the program could run out of
memory!
Note, however, there is only one Employee variable in the code. Since a
variable can store only one value because storing another replaces the
first, only one Employee object is referenced at any time. All of the
previously created Employee objects are not referenced by any
variable. When an object is not referenced by a variable, it can never be
used. That’s because the only way to use an object is via a reference
variable. An unreferenced and unusable object is called garbage. The
Java runtime—the program code that supports the execution of every
Java program—contains a process called the garbage collector. This
code periodically looks through memory for objects that cannot be
accessed (garbage) and recovers the memory previously allocated to
them in a process called garbage collection. Because of this process,
this program does not run out of memory.
As a final consideration for the Employee class, we must remember that
one of the responsibilities of the program was to write to a new
ASCIIOutputFile an updated version of the input data file. This to be
the same format as the input file since it will be input the next time the
program is run. Since this information is stored as instance variables of
the object, it makes sense that the object itself should write out the
information. There is also symmetry here, if the class reads the data it
should also write it.
The method write of the Employee class serves this purpose. It is
passed an ASCIIOutputFile object and uses this object to write out
the data values from its instance variables, being careful to write them in
the same order that the constructor reads them. The write method also
writes an EOL marker, so each employee record is on a different line.
11.4
INFORMATION HIDING
To achieve the reduction of complexity afforded by the use of classes for
data abstraction, care is needed in the design of a class. First, a class
should be cohesive. This means that the instance variables represent
information logically associated with the entity that the class represents
and that the methods represent operations the entity might logically
perform.
Second, a class should use selective disclosure: it should present to
other classes in the system only those things that other classes need to
know about. The class should not expose its inner workings. If other
classes cannot see the inner workings they cannot make use of them and
this makes the class easier to use. For example, when we used the
Turtle class, we didn’t have to know how the position of the turtle was
stored or how a line was actually drawn on the screen. Additionally, it
afforded the designer of the Turtle class a wide choice of
representations and even the possibility to change the class, without
affecting the users of the class. Information hiding is the choice to hide
the details of the representation of information and the implementation
of the methods within a class, selectively exposing only those details
necessary for the use of the class.
ACCESSOR AND UPDATER METHODS
The first concern in information hiding is visibility of the attributes of the
object as represented by the instance variables. Clearly, some of the
attributes are of concern to outside classes. For example, the employee
number is needed by the Payroll2 class for the detail line in the report.
Some of the attributes should be modifiable from outside the class. For
instance, giving the employee a raise involves changing the rate of pay.
Others should not be changed at all—an employee number is permanent.
Still others should change, but only because of an operation performed
by the object. Thus year-to-date pay should change only when the
employee is paid via a call to payForHours.
The best way to control the access to attributes is to declare all instance
variables private. That way they are visible only within the class itself
(see Section 3.5). Methods can be used to permit controlled access. A
method such as getEmpNum that simply returns the value of an attribute
is called an accessor method. Accessor methods are declared public
so other objects may use them. This allows other objects to access the
information without running the risk that they may change it. In the
Employee class, getEmpNum, getEmpName, getRate and
getYTDPay are all accessor methods. Although it is not necessary to
make all attributes accessible, in this case it makes sense. The Java
convention is that accessor methods are named get followed by the
attribute (instance variable) name.
Methods that allow other objects to modify the value of an attribute are
called updater or setter methods. These methods take the new value for
the attribute as a parameter and update the instance variable
accordingly. Again, updater methods are declared public. Only those
attributes that should be updatable have updater methods. In this case,
only setRate is provided to update the rate instance variable, to give
the employee a raise. This means the other attributes cannot be updated,
by outside objects. In addition to selective updating, updater methods
can check that the update is appropriate. This check can prevent
inappropriate updates and allows the object to ensure that its state (as
represented by its instance variables) remains valid. The Java convention
is that updater methods are named set followed by the attribute
(instance variable) name.
Remember that behavior can change state. This means that some
methods of a class (as opposed to updater methods) modify instance
variables as a by-product of doing something else. For example,
payForHours computes gross pay and also updates the instance
variable YTDPay, changing the state of the object. These methods are
called mutator methods.
Of course, a class does not have to expose all of its methods. Methods that
are intended to be used only by other methods of the class (helper or
local methods) are declared private. They are only intended to be used
as by the object itself as procedural abstraction of more complex tasks.
*11.5 DESIGNING FOR REUSE
One of the advantages of object-oriented programming is the possibility
for code reuse. You will note that there are a number of methods
provided in the Employee class that are not used in the payroll
application. Why are they included?
In the development of a system, it is advantageous to reuse code that was
developed for a system that was previously written and is likely to be
still in use. Since this code has already been written and tested—both
during testing and during continuous use in the existing system—it is
likely to be reliable. We have already seen reuse in one form, using prewritten classes that are stored in a library. However individual classes
(such as Employee) or small groups of classes, even if not considered to
be useful as a library, can also be reused.
The unit for code reuse in an object-oriented language is the class.
Classes can be placed in a library, or simply included is the set of classes
for an application. When a class is first written, it is a good idea to think
ahead and consider how the same class might fit into other systems. For
example, the company might need to do weekly payroll now, but it also
has to provide income statements to the government for tax purposes. It
may also want to keep track of pension and benefit information. In all
these systems, there is the presence of an employee, so some kind of
Employee class would likely be used. It makes good sense to develop
the Employee class once for the first system, and then reuse it in
subsequent systems.
There is a downside to reusing code. If a class has to be modified for one
system, there are two possibilities: either make a copy of the class,
modify the copy, and use the copy in the new system, or change the
original class, necessitating recompilation of all existing systems that use
the class. The first approach has the problem that there are now really
two different classes and maintenance has to be done on both of them.
The problem with the second is that a change for one system may break
the class for another. A technique using inheritance addresses these
problems. However, the topic of inheritance is beyond the scope of this
book.
SUMMARY
Classes are the basic building block of programs in object-oriented
languages, including Java. Most real-world programs consist of tens,
hundreds, or even thousands of classes, some written for the project,
some reused from libraries. A class consists of a set of declarations
including instance variables (fields), constructors, and methods.
Instances of a class (objects) are created and interact to produce the
effect of the program. Each object has its own instance variables (as longterm memory) and each shares the same method code with other objects
of the same class. A method is always executed by some object.
Classes provide a powerful abstraction mechanism: data abstraction by
which large, complex systems may be built. Information hiding within
classes allows reduction of complexity by allowing the client
programmer to concentrate on what an object can do rather than on
what data it stores and how it performs its operations.
A class can control visibility by using the visibility modifiers public and
private, for instance variables and methods. To provide the most
control, instance variables are declared private and accessor or
updater methods are made available as required. An accessor method
allows access to the value of an instance variable and an updater method
allows controlled update of the value of an instance variable. Methods
that change the state of the object as represented by the instance
variables are called mutator methods. Methods are declared public if
they are intended to be used by a client class or declared private if
they are intended to be used only by methods within the class (local
helper methods).
REVIEW QUESTIONS
1.
T
F
2.
T
F An accessor method is a method of a class whose sole
purpose is to return the value of one of the class’ instance
variables.
3.
T
F The constructor should ensure well-defined behavior by
putting the object into a well-defined state.
4.
T
F
5.
T
F Accessor and updater methods should be written for every
variable.
6.
T
F
7.
T
F Information hiding is hiding the representation (instance
variables) of a class while exposing its implementation
(methods).
8.
A constructor:
a)
b)
c)
d)
9.
An object’s behavior depends on its state.
A constructor must have at least one parameter.
A method call always has a target object.
is called when an object is used
must not have parameters
puts the object into valid state
all of the above
The reuse of memory previously allocated to an object that is no
longer being referenced is called:
a)
b)
c)
d)
storage deallocation
memory reclamation
object destruction
garbage collection
10. Which of the following is false?
a)
b)
c)
d)
Java provides automatic garbage collection.
Objects become garbage when they are no longer
referenced by a variable.
If no garbage collection is performed, then a program may
eventually use all of main memory.
Garbage collection occurs immediately after an object
becomes inaccessible.
11. Data abstraction is:
a)
b)
c)
d)
using classes to represent data objects
using information hiding to hide the details of an object
using methods in a class to implement the operations on an
object
all of the above
12. Accessor methods:
a)
b)
c)
d)
are function methods
return the value of an instance variable
may return a value computed from instance variables
all of the above
13. A class is cohesive if:
a)
b)
c)
d)
the instance variables are private
the methods are public
the methods represent operations logically associated with
the class
all of the above
14. An updater method:
a)
b)
c)
d)
assigns a new value to an instance variable
may validate the value to be stored in an instance variable
should be declared private
all of the above
15. A local method:
a)
b)
c)
d)
may only reference parameter values
is declared private
must not return a value
a and b
EXERCISES
1.
Rewrite Exercise 3 from Chapter 9 using two classes, one describing
inventory items and one, the main class, to generate the report. The
gross value should be computed from the quantity and unit value
attributes by the Inventory class.
2.
The Registrar’s Office at Broccoli University keeps track of students’
registration in courses. For each registration of a student in a course, a
record (line) is entered in an ASCIIDataFile recording: student
number (String), department number (String), course number
(String), and date of registration (String as yymmdd). Write a Java
class called Registration that encapsulates this information.
Periodically, the Registrar’s Office must produce class lists for
faculty. Write a main class that uses the Registration class and the
data file to produce a class list for a course. The program should read,
from an BasicForm, the department number and course number and
then print a report to an ReportPrinter that displays, the student
number and date of registration for all students registered in the
course. As a report summary, it should print the number of students
currently registered in the course.
3.
Peach Computers Inc. requires a program to process its payroll.
Employees in the company are paid each week and are either hourly
employees whose gross pay is determined by the number of hours
worked and the pay rate, or they are salaried employees whose pay
for the week is a fixed amount. Hourly employees are paid straighttime for the first 40 hours of work in the week and time-an-a-half for
overtime (any hours worked in excess of 40). Salaried employees are
not paid overtime so that the number of hours they have worked is
irrelevant. The federal and state governments require that the
company withhold tax, each at a particular taxation rate that may be
subject to change.
An ASCIIDataFile of timesheet information is created each week
containing information for each employee that is to be paid. The first
two values in the file are the federal taxation rate (double) and the
provincial taxation rate (double). Following that is information for
each employee consisting of (1) employee number (String), (2) pay
class (int, 0 for hourly, and 1 for salaried), (3) pay rate (double, the
hourly rate for hourly employees and the weekly rate for salaried
employees), and (4) hours worked (double, irrelevant for salaried
employees).
The program is to input the employee information and compute and
display the employees’ gross pay, federal tax withheld, state tax
withheld, and net pay. Since the company must remit the federal and
state taxes withheld to the respective governments, the program must
also display the total taxes withheld. In addition, so that the auditors
may audit the payroll records, the total gross and total net pay paid
out must be computed and displayed.
If the timesheet file contained the following information:
the report generated by the program should look similar to the
following:
12 FILES & PERSISTENCE
CHAPTER OBJECTIVES
Explain how information is stored on auxiliary storage as files
Differentiate between stream and record I/O.
Describe the data hierarchy.
Understand the significance of a persistent object.
Explain the process by which objects are made persistent.
Apply persistence in the implementation of a program.
Apply binary object I/O to read and write objects.
Differentiate between sequential and random processing of data
in a file.
·
Explain the standard file processing algorithms: file merge and
file update.
·
·
·
·
·
·
·
·
Auxiliary storage is one of the main hardware components of computer
systems. It can be used for storing large amounts of information such as
movies and pictures for which there would not be enough room in
memory. Since main memory is volatile, auxiliary storage is also used for
long-term storage of information—for example, letters, essays,
inventories—that are repeatedly accessed and updated. On a personal
computer, auxiliary storage typically consists of a hard disk and DVD
drive. External flash drives are also used especially for transporting data
or backup.
Regardless of the physical medium, information on auxiliary storage is
organized into collections of related information called files. Files are the
basic unit of information storage recognized by the operating system.
When a file is processed by reading/writing individual characters/bytes,
the processing is called stream I/O. When a file is processed by reading
blocks of related data, the processing is called record I/O.
Entities in a real-world system are present for long periods of time,
certainly longer than the execution of one computer program. Java
defines persistent objects—objects whose lifetime extends beyond the
execution of a single computer program—allowing us to model this
situation. Persistent objects are supported via record I/O where the
entire object is read/written as a unit.
12.1
FILES
One of the operating system’s main functions is file management, which
is keeping track of files and unused space on auxiliary storage and
handling programs’ requests for file access. To make it easier for users to
organize their files, the operating system associates with each file a name
called the external file name. This name is usually chosen by the user
according to certain rules of the operating system. It is the name you
enter response to a “create” or “save as” dialog.
A program might have to access many different files at different times.
For example, a word processor may be used to edit a letter at one time
and your term paper at another. The program will not want to be tied to
a particular external file name, but rather use a generic name, such as
theDocument, to refer to the file. This file name is called the internal
file name and typically is the name of a variable within a program. When
a program wishes to use a file, it must make an association between the
internal file name and the external file name of the actual file being
accessed. The process—called opening the file—is accomplished via a
request to the operating system.
When a program makes an “open” request to the operating system, the
operating system verifies that the operation is appropriate. This involves
either finding space for the file on auxiliary storage for a new file or
verifying the existence of the file for an existing file. It must also verify
that the user running the program is allowed to access or create the file
and that no other program is currently using the file. It then allocates the
file to the program, locking out access by other programs, and provides
the program with a file reference—a means to access the file.
The program then processes the file, reading or writing information
from or to the file, until it has finished its task. The I/O is done via
requests to the operating system. The program then informs the
operating system that it has finished using the file. This is called closing
the file, and the operating system ensures that the file is in a valid state
and unlocks the file so it can be accessed by other programs.
When the file being read or written is considered to be just a sequence of
bytes or characters, the I/O is called stream I/O. That is, the data is
considered a stream of bytes or characters. This is the kind of I/O
supported by ASCIIDataFile and ASCIIOutputFile. There is
another way to consider the organization of files, however. In record
I/O, the information in a file is organized into a number of records, and
an entire record is read or written at a time. Java supports
reading/writing of entire objects (object I/O) as a form of record I/O.
Record I/O is the basis for most data processing systems such as
inventory control systems, student records systems and banking
systems. Records and record-oriented files also form the basis of
database systems. In this chapter we will concentrate on record I/O.
RECORD I/O
In most data processing systems, the data is organized in some way, it is
not just a sequence of bytes or characters. For example, in a student
records system the information—the name, marks, etc.—about a single
student is grouped together. Individual pieces of information such as the
student’s name or mark in a particular course are called fields. A field is
the basic unit of processing. Although a field is considered to be
indivisible for processing, it actually consists of a number of bytes or
characters. All of the fields concerning a single entity, such as a student,
are grouped together into a record. The record is the basic unit of I/O.
The records of related entities, such as all the students at the university,
are grouped together into a file. The file is the basic unit of storage
management in the operating system. Finally, related files, for example,
student academic information and student financial information, may be
grouped together into a database. This hierarchy (byte, field, record, file,
database) is called the data hierarchy.
Some field of the record, called the key, typically identifies an individual
record within the file or database. Usually unique identification is
desired so the key must be unique. Naturally occurring fields are not
often unique. For example, it would be natural to use a student’s name as
a key; however, it is not likely to be unique if the number of students is
large. To solve this problem an unique artificial key is often created and
assigned to an entity. This artificial key is called an assigned key.
Student numbers, bank account numbers, etc., are all assigned keys.
Complete records are read or written at one time, so such files are called
record-oriented and the form of I/O called record I/O. When a record is
read, it must be stored in main memory. In many languages, there is a
special construct, called a record or structure, to represent this kind of
data—collection of fields of diverse types. In object-oriented languages,
an object corresponds to a record since it is a representation, within the
program, of some entity and has attributes that correspond to the
individual pieces of information, or fields, of the record. Thus, in objectoriented languages, record I/O is sometimes called object I/O.
Most data processing environments, such as a student records system at
a university or an inventory system at a large wholesale supplier, involve
very large collections of data—too large to be stored in memory at one
time. The data is thus stored in files and the records are read and
processed as needed. The records can be processed in two ways:
sequentially or randomly. In sequential processing, the records are
read and processed, from first to last, in the order they occur in the file.
In random processing, records are selected by key to be read and
processed. Processing can be in any order. Sequential processing is done
when all or most of the records must be processed or when they must be
processed in order. An example would be a monthly billing system.
Random processing is used when only a small proportion of the records
are to be accessed or when access is to be done in real-time. A real-time
system is one where the response to an input must occur within a short
period of time. An example of random processing would be seat selection
in an airline reservations system.
Random processing can only occur when the file is stored on a direct-
access device. A direct access device is one in which each record is
individually addressable and the time to access a record is independent
of the record’s position on the device. With sequential access devices, the
records can only be accessed in the order they occur. Disk , DVD and
flash drives are direct access devices. Although most devices are direct
access devices, sequential processing still makes sense in many cases. In
this book we will only consider sequential processing.
12.2
PERSISTENCE
As we saw in Chapter 11, when we program in an object-oriented
language such as Java, we typically build a model of a real-world system
(e.g. payroll system in a company) that includes classes that represent
the entities (e.g. employees) of the real-world system. In the real-world
system, some of these entities typically have a life-time that transcends a
single execution of the system. For example, the employees of the
company are part of the company for an extended period of time, not just
at the instant they are being paid. These entities have a memory that
lives on between executions of the system.
It is also common that the same entities participate in more than one
system. For example, the employees are hired, given raises and retire, if
they work shifts, the shifts are scheduled. If these processes are
automated, the Employee objects would be involved in an HR system
and a scheduling system as well as the payroll system.
Our previous version of the payroll system (Example_11_1), does not
capture this situation. It achieves the “memory” (in particular of the
year-to-date pay) of the employees by writing the employee information
to a text file and then re-creating the employee from this file the next
time the program is run. To model the system more accurately, it would
make sense that Employee objects live on beyond the single execution
of the program rather that recreating them each time the program is run.
Maintaining a text representation of the employee information also
creates issues. Since the data file can be read and written by a text editor,
there is the temptation to make necessary changes by editing the text
file. This could introduce errors and inconsistencies in the data and
prevent the system from producing correct results.
Java supports persistent objects—objects whose life-time is not tied to
the single execution of a program. Once created, these objects may
participate in the execution(s) of program(s), maintaining their state
(memory) from execution to execution. They need not cease to exist at
the end of the execution of a program and be recreated for another
execution.
Of course, when we write a class as a model of a real-world entity, we do
not capture everything the entity does but rather only that that is
relevant to the system(s) we are developing. For example, we do not
model what an employee does after work nor represent his/her hair
color. Thus, within our programs, objects have periods of activity (such
as getting paid) and periods of inactivity when they are not participating
in any system.
In Java after a persistent object completes an active period in a program,
it is placed into suspended animation by writing the object (as a single
unit) to disk. When the object enters another active period (in the same
or different program), it is reanimated by reading the object from disk.
Since what is written/read is the run-time representation of the object,
binary I/O is used—record I/O.
As mentioned in Chapter 9, the BasicIO library provides two classes for
binary I/O to/from disk: BinaryDataFile and BinaryOutputFile.
When a value is written to a binary file, a copy of the memory
representation (see Chapter 4) is written. For example, when the int
value 10 is written, 4 bytes are written being the binary representation
of 10. Objects have a more complex memory representation than simple
values and a direct copy of that representation is not sufficient. When an
object is written, a process called serialization is performed to produce
an unique binary representation of the object. When the object is
subsequently read, the memory representation is recreated from this
binary representation such that the object continues its life as if it had
never been suspended.
PAYROLL WITH PERSISTENCE
Figure 12.1 shows a version of the payroll system modified from Figure
11.4. Instead of text files for storing the employee information, it uses
binary files (lines 11, 13, 24 & 26). Instead of creating new Employee
objects (using the constructor and getting data from the text file),
Employee objects are reanimated by reading from the binary file (line
33). Finally, instead of writing the employee info to a text file using the
Employee method write, the Employee objects are serialized and
written to a binary file (line 40). The remainder of the Payroll class is
unchanged.
The reading and writing of the objects requires further examination. If
you consult the API for BinaryDataFile, you will see that the
readObject method returns a value of type Object. Object is a
predefined type in Java that represents objects in general. All objects,
regardless of their class, are instances of Object. Thus an Employee is
an Object and a Turtle is an Object. Object is said to be a
supertype of all classes. Since the author of the BasicIO library had no
way of knowing what object types (e.g. Employee) may be used by a
program, by defining that readObject returns type Object the
method can return an object of any type.
Figure 12.1 Example—Payroll with Persistence
However, this leads to an issue. At line 33, all that the compiler knows is
that readObject returns a value of type Object, which could be any
object type. The assignment statement attempts to assign this to a
variable of type Employee. Assignment compatibility (see Section 4.4)
requires that the type of the right-hand-side (Object) be a subtype of
the type of the left-hand-side (Employee). This is not the case (in fact
Employee is a subtype of Object). Of course, we expect that the object
read will actually be an Employee object—it must be of some object
type. This leads to the use of the cast ((Employee)) prior to the
assignment. Unlike a cast for a primitive type such as double, the cast of
an object type doesn’t cause a conversion. Rather the cast tells the
compiler to treat the object as being of the specified type (Employee),
making the assignment legal. However, the compiler doesn’t blindly trust
the programmer (otherwise you could defeat the type-safety of the
language). The compiler arranges that the actual type of the object read
is checked at execution time to verify that it really is Employee and not
some other object type such as Turtle. If it isn’t, the program will crash
on a ClassCastException. This form of a cast—from supertype to
subtype—is called a downcast.
A related issue arises on line 40 where the Employee object is written
to the binary file. The parameter for writeObject is of type Object as
well. Parameter compatibility is defined in terms of assignment
compatibility of the argument type (Employee) to the parameter type
(Object). However, there is no problem here since Employee is a
subtype of Object and thus compatible. The cast—from a subtype to a
supertype—that occurs here is called an upcast. Downcasting must be
explicit while upcasting may be implicit.
Figure 12.2 shows a version of the Employee class as modified from
Figure 11.6. There are minimal changes. For security reasons, Java
requires that all classes that are to be written to disk as binary objects
(i.e. are serializable) must be declared as such. This is done by declaring
the class implements the Serializable interface (line 9), which is
declared in the java.io library (line 3). This ensures that some objects
(such as security objects) cannot be copied from one program to another
breaking the security system. A further discussion of interfaces is beyond
the scope of this book.
Since different programmers might write classes with the same name,
the class name alone isn’t unique enough to ensure that the correct type
of object is read (line 33 in Figure 12.1). Associated with each unique
class (i.e. the compiled version of the .java file) is a special number
called the serialVersionUID. This number (a long constant) can be
automatically generated by the compiler (resulting in a different value
each time the file is compiled) or it can be specified by the programmer.
We have done the latter in Figure 12.2 at line 10. When checking the type
during the downcast, the compiler actually checks that the
serialVersionUID of the Employee object (written when the object
was serialized) matches the serialVersionUID of the Employee
class in the program.
Note that the Employee constructor and the write method are never
used in this version of the program. This is because the program doesn’t
create any Employee objects (they already exist) and stores the
Employee objects using writeObject.
Figure 12.2 Example—Serializable Employee Class
CREATING AND LISTING THE Employee BINARY FILES
As noted above, the payroll program assumes the Employee objects
already exist. How and when were they created? At some point in time,
the company must have decided to use a payroll system that involved
persistent objects, either initially when they decided to automate the
payroll process or after they had been using a legacy system (such as the
version of payroll in Figure 11.4). In preparation for use of the new
system, files (either paper when no automated system was used or text
in a legacy system) need to be transferred over to the new format
(binary). In other words, the first version of the binary employee object
file would have to be created. It would then be used in the first execution
of the new system and things would proceed with only the binary files.
Figure 12.3 is a simple program to create a binary file of Employee
objects from the text file already in existence from the legacy system. If
there was no legacy system, it would be necessary to type this text file
first. Input is from a text file of employee information (lines 9 & 18) and
output is of a binary file of Employee objects (lines 10 & 20). The
program uses the constructor of the Employee class to read the
employee information and create the corresponding new Employee
object (line 23). It then uses the writeObject method to serialize the
now created Employee object. Since the program would otherwise not
demonstrate any evidence of having executed (i.e. it only does file I/O), it
uses an ASCIIDiplayer to display a happiness message (the number
of objects written) to the user. A happiness message is a message of
confirmation that something (and the right thing) is happening. Often a
progress bar or similar is used for this purpose.
Figure 12.3 Example—Create Employee File
A downside of using binary files for storing the employee information is
that the file cannot be viewed using standard applications such as a text
processor since it isn’t text. If it is desirable to examine the current state
of the Employee objects, say for debugging or maintenance of the
system, we would need a program to read the objects and display the
employee information.
Figure 12.4 Example—List the Employee File
Figure 12.4 is such a program. It uses a binary data file to read the
Employee objects (lines 11 & 18) and a displayer to display the
employee information (lines 12 & 19). It reads the Employee objects
(line 22) and then uses its writeDetail method to write the employee
information to the display using the Employee accessor methods.
12.3
FILE PROCESSING
As was mentioned earlier, a key is a field of a record that uniquely
identifies the record within the file or database. When no naturally
occurring field is unique, an assigned key is used to achieve uniqueness.
The unique key is often called the primary key. If the records of the file
are stored in primary key order, it is possible to perform processing in a
single sequential pass over the file, reading each record once. This is the
basis for most sequential file processing algorithms.
In this section, we will discuss the two most common sequential file
processing algorithms: file merge and file update. A file merge is an
algorithm to combine two files of like records into a single file. A file
update is a process to modify the information stored in the records of a
file by applying transactions to it.
FILE MERGE
The file merge algorithm combines files of like but unique records into a
single file (see Figure 12.5). For example, consider a situation in which
each marker for a large course maintained a separate student file for the
students in their laboratory. At the end of the year there would be a
number of files containing records for different students that would have
to be merged into a single file to perform final mark reporting for the
course. We will consider a two-file merge, however the algorithm can be
generalized for an n-file merge.
Figure 12.5 File merge
If the order of the records were irrelevant, we could simply copy all the
records from the first file followed by all the records from the second file.
However, as was mentioned earlier it is usually desirable to have the files
in key order. If file 1 and file 2 are in key order, we can produce a
new file in key order in a single pass through the original files.
Consider how we would do the problem by hand. Say we are merging
two piles of assignments already in student number order. Call them the
left hand and right hand piles respectively. We could pick up one
assignment from each pile. Considering the student numbers on these
two assignments, we place the assignment with the lowest student
number into the final pile and pick up a new assignment from the
appropriate pile. That is, if the assignment placed down was from the left
hand pile we would pick up from the left hand pile. This continues until
one of the two piles is exhausted and we simply transfer the remaining
assignments from the other pile to the final pile.
Why does this algorithm work? Since the original piles are in order we
have, at any time, the assignment with the lowest key from its pile in
each hand. The one that is lower of these two is clearly the lowest of all
remaining in the piles. It is lower than the others in its pile and, since it is
lower than the one in the other hand, it is lower than all others in the
other pile as well. At the same time, it is greater than any assignment
previously taken from its pile and, since it is still in hand, it must have
been greater than the one previously in the other hand, and thus greater
than any assignment previously taken from the other pile. Thus it is next
in succession for the final pile.
If we apply this algorithm for computer solution, we replace the
assignments with records and the piles with files. The algorithm is given
in Figure 12.6. After reading a record from each file, and until one file is
empty, the record with the lowest key is written out and replenished
from its file.
Figure 12.6 Basic File Merge Algorithm
USING A SENTINEL RECORD Although valid, this algorithm leaves a
bit to be desired, specifically the need for the special cases when one of
the two files is exhausted. The code there must read and write records
from the non-exhausted file. However, code to read and write records
already occurs in the loop. How could this code be used? The answer lies
in a technique that is often employed in this kind of situation—a sentinel.
A sentinel is an entity that marks the end of something. In this case a
record that marks the end of a file. Since the files are sorted, the sentinel
record would have to have a key greater than any other key in the file. At
the same time, to serve as a marker, the sentinel must be recognizable—
it must have a specific key value. What is required is a value known to be
greater than any other possible key value. This is not always possible,
especially if the key is a natural key however, with assigned keys it can
be ensured that such a value is reserved when keys are assigned.
With the use of a sentinel, when one file is exhausted the sentinel record
is the current record. Since its key is greater than any other possible key,
all of the records from the other file will be written to the new file, until
that file is also exhausted. The algorithm terminates when both files are
exhausted, that is, when both records are sentinel records. The revised
algorithm is presented as the algorithm in Figure 12.7.
Figure 12.7 File Merge Algorithm
USING A FILTER METHOD
Sentinel records do not actually occur in
the files, and we don’t want to write one to the new file. How do we
arrange that they be there for this algorithm? The answer lies in a second
common technique: a filter. A filter is a method or class that transforms
data into (from) another form desired by the program. The filter is used
by the program in place of the data access method method or class.
Filters are the basis of the Java I/O library, each filter class transforms
the data in some way. In fact, the BasicIO library is actually a set of
additional filters for the Java I/O libraries to make them, easier to use. In
our case, we will use a filter method that will replace the file read
(readObject) and return a sentinel record (object) at the end of file.
The Example in Figure 12.8 is a file merge application based on the file
merge algorithm and using a sentinel and a filter. It merges two files of
Employee records, which are sorted by student number, into a single
file sorted by employee number. Three files are used, one each for the
input employee files (lines 13 & 14) and one for the new file (line 15). A
displayer (line 23) is used for happiness messages.
The constant HIGH_KEY is the key value for the sentinel record. It
consists of a single character string containing the highest character in
the Unicode character set. The constant: Character.MAX_VALUE is
this highest character. The method String.valueOf converts the
char into a String.
Since the loop is to stop when both files are at the sentinel, we wish to
continue in the opposite case This is implemented by negating the
expression for the termination condition using ! (lines 32 & 33).
Alternatively, deMorgan’s law could be used to rewrite the expression. A
sentinel record is one that has HIGH_KEY as its employee number. Since
the keys are String objects, the methods equals and compareTo are
used to compare them (lines 32, 33 and 34), and the appropriate record
is written and replaced. After the loop, the streams are closed.
Figure 12.8 Example—Employee File Merge
The method getRec (lines 59-66) is the input filter. It attempts to read a
record from the specified file. If the read is not successful because it has
reached EOF, the method creates a new sentinel record. A second
Employee constructor is available that takes the employee number as a
parameter and creates an empty student record with that key. getRec
uses this constructor passing HIGH_KEY as the employee number to
create the sentinel record. The sentinel is a dummy object so the rest of
the fields are irrelevant and not filled. getRec then returns the record
read or the sentinel record. Where the program is to read the records
(employee objects), the filter method is used instead of the actual read
method of the file (lines 30, 31, 36, 40).
As a final note, since the two input files are considered to contain unique
records, the case of equal keys of the two records is not considered. It is
possible to merge files with non-unique keys, as long as it is correct for
the new file to have two records with the same key. This algorithm will
handle this with the records from file2 preceding the records of equal
key from file1. If this case is considered to be invalid, an extra test
could be included to reject records with equal keys or combine them into
a single record.
STYLE TIP
Filters can be a useful device and can make programs much easier to
read. They can be used in both stream- and record-oriented I/O. A filter
method can be used whenever the input doesn’t conform exactly to what
the program requires. It can modify the input text, rearranging fields,
deleting fields and adding fields. It can insert special characters as
necessary, such as placing a space at the end of a text line so the program
can always assume a word is followed by a space. By placing the code
that transforms the input into a method, the algorithm that does the
actual processing of the information becomes clearer, using the filter
method as if it were an input method of the I/O class. Of course, filter
methods can also be used on output to make the program output
conform to a specific output file format.
FILE UPDATE
In a file update there is a file, called the master file that contains
information about a group of entities. For example, it could contain
student records at a university or credit card account records at a credit
card company. It is the master set of data about the entities and must be
updated to reflect the current situation. For example, markers submit
marks for students and customers purchase items with their credit
cards. These updates to the master file are called transactions.
The master file can be updated in one of two ways. If the contents of the
master file must be always be up-to-date, the master file can be updated
directly using random processing. This is how booking a seat on the
airplane is done in an airline reservation system. If the master file
doesn’t have to be absolutely up-to-date, the updates can be done
periodically—every night, once a week or once a month, depending on
the currency of the data. When the master file is updated periodically,
the transactions are collected together over time. This collection is called
a batch and becomes a transaction file which is used to update the
master file. This kind of processing is called batch processing.
The process of sequentially updating a master file using batched
transactions is shown in Figure 12.9. The master and transaction files are
stored in key order and the updating can be done in a single pass over
the files, producing a new master file. The process usually involves the
generation of a report, for example the credit card bills, and a log of the
modifications to the master file used for auditing and backup purposes.
Typically, in this kind of system, the old master file and transaction file
are retained as backups, in case the new master file is lost or damaged.
Figure 12.9 File Update
The algorithm is similar to the file merge algorithm. The first difference
is the files themselves. In file merge they are equal status while in the file
update, there is a master file and the transactions are simply updating it.
Secondly, in file merge the records are of the same type while in a file
update, transaction records are typically different from master records,
although they share the same keys.
The second difference is in the tests for the keys and processing involved.
When the keys of the master record and the transaction record are
compared, there are three possibilities:
Consider case 2 first. Clearly, since the keys match, the transaction is
intended for the master so it is applied to the master. That is, the update
should be performed. The update is done to the memory version of the
record (the object). Since there could be more than one transaction for
the master, all that is done subsequently is to read another transaction.
When the master key is less than the transaction key (case 1), the
transaction must apply to a master record later in the master file. Since
the transactions are in order, no other transaction later in the
transaction file could apply to this master and this master has been
completely updated. The updated master should be written to the new
master file and the next master record read.
In the final case (case 3), the transaction must apply to a master record
earlier in the master file. However, this is not possible since the only way
that master could have been passed is if a higher transaction was read,
yet the transactions are in order. Thus no master matches the
transaction. Here there are two possibilities. In some applications, one of
the goals of a transaction could be to add a new master record to the
master file. This would cause this situation, and the previously read
master would have to be saved, a new master record created from the
transaction and a new transaction read. When this master is written and
a new one read (case 1), instead the saved one would be recalled. If
transactions are not intended to create new master records, this case
indicates an error that there is no matching master for the transaction.
Figure 12.10 is the algorithm for master file update. It has the same loop
structure as the file update (Figure 12.7) and uses the sentinel method
for termination. The decision structure has three branches handling the
three cases above. Report generation and logging can be inserted at the
appropriate places, generally in the second case as a transaction is
applied.
Figure 12.10 Master File Update Algorithm
SUMMARY
When the data that a program requires is either too large to fit in main
memory or needs to be used repeatedly over a long period of time, it is
stored on auxiliary storage as a file. In most large data processing
systems, the data is organized into a data hierarchy of bytes composing
fields that are the data items of records, which are collected into a file.
Files may also be collected into a database. In such systems, the unit of
I/O is a record. In an object-oriented language, such as Java, objects
represent the records and the I/O can be called object I/O. These objects
are called persistent objects. In Java, objects that are to be read or
written to auxiliary must be serializable.
File processing can occur in one of two forms: sequential and random. In
sequential processing the records (objects) are read and processed in the
order they occur in the file. In random processing the records are
accessed by a key and processed in any order. Random processing can
only be done on direct-access devices such as disk.
Two common sequential file-processing algorithms are file merge and
file update. File merge combines two files of like records into a single file
while file update updates the contents of the records of the master file
based on a file of transactions. In both cases, the records in the files are
sorted in key order and processing can be done in a single pass, reading
the records only once. Both of these algorithms are simplified by the use
of a sentinel—a dummy record marking the end of the file. Since it is
undesirable to actually store the dummy record in the file, a filter
method is used to supply the sentinel record at EOF. In the file update
algorithm, changes to the file are collected together over some period of
time—a process called batching. These changes are called transactions,
and the collection of records that is to be updated is called the master
file. The batch of transactions is applied to the master file in a single pass,
updating all records required and typically producing a report.
Objects whose lifetime transcends the execution of a single program are
called persistent objects. Java supports persistent objects via
serialization in which an object is converted to a unique binary
representation and written to a binary file. Reanimation of the object
occurs via reading the binary representation and recreating the object.
For a class to support serialization, it must be declared as implementing
the Serializable interface.
REVIEW QUESTIONS
1.
T
F
In stream I/O, a stream is a sequence of bytes or characters.
2.
T
F “Downcasting” occurs when we use a sub-type in place of its
super-type.
3.
T
F Sequential processing would be used to update the seat
bookings in an airline reservations system.
4.
T
F Random processing can only be performed on a direct
access device.
5.
T
F
6.
T
F Bank account balances might be stored on magnetic tape for
an automated teller system.
Batch processing is used when a file must be kept current.
7.
T
F
8.
T
F A batch is a collection of master records that is kept
separate from the main master file.
9.
T
F
10. T
A sentinel record must have a uniquely identifiable key.
In file merge, two like files are combined together.
F Random processing is carried out only when sequential
processing fails.
11. In response to a request to open a file, the operating system:
a)
b)
c)
d)
verifies user’s access rights
finds the internal file name
creates a copy of the file
a and b
12. The data hierarchy is, in order from smallest to largest:
a)
b)
c)
d)
field, record, file, byte
file, record, field, byte
byte, record, field, file
byte, field, record, file
13. A primary key is:
a)
b)
a)
b)
a natural key
an assigned key
an unique key
always required
14. In a file update, which operation must be performed when the
master key < transaction key?
a)
b)
c)
d)
read a new master
read a new transaction
write the old master
a and c
15. A persistent object:
a)
b)
c)
d)
has a lifetime longer than the execution of a program
is written to a file using binary I/O
must be of a Serializable class
all of the above
16. A student number is:
a)
b)
c)
d)
an assigned key
a natural key
an integer
none of the above
17. In a file update, which operation might be performed when the
master key > transaction key?
a)
b)
c)
d)
add a new master
read a new transaction
generate an error
all of the above
18. In record I/O:
a)
b)
c)
d)
the record is the basic unit of I/O
information is organized into a number of records
a file is collection of records stored on auxiliary storage
all of the above
19. A sentinel can be used to:
a)
b)
c)
d)
mark end of a file
print error message
read a new master
none of the above
20. Updates to the master file are called:
a)
b)
c)
d)
records
transactions
a batch
a database
EXERCISES
1.
Pages Bookstore, has decided to automate its warehouse and
distribution facility. As a first step, they wish to transfer the paper
records of their inventory to a computer file. Using a text editor, a
complete inventory has been created as a text file. They now wish to
create a binary (object) file for the inventory.
The inventory file will contain one record per book that the
bookstore stocks. For each book the following information has been
recorded in the text file, one line per book in tab-delimited format: the
ISBN (International Standard Book Number, string), title (string),
quantity in stock (integer), quantity on order (integer), reorder point
(integer) and selling price (double). The text file is in order by
inventory number.
Write a program that reads the inventory text file
(ASCIIDataFile) and produces a binary object file
(BinaryOutputFile) of Book records. The program should display
to an ASCIIDisplayer, the total number of book records processed
as a happiness message.
2.
Pages Bookstore needs a program to produce a report on the
inventory in its warehouse. A binary inventory file (see Exercise 1)
records the current state of the inventory. The program is to produce
a report on this inventory giving, for each book stocked, the ISBN,
quantity on hand, and value of the stock (quantity times selling price).
The report summary should indicate the total value of the books in the
warehouse. The report might look like the following:
Use the data file created from Exercise 1 as the inventory file.
Remember, to read this binary file (BinaryDataFile), the program
must use the same file describing the Book class.
3.
News Flash: Pages merges with rival Nobel Barns! The new company
moves its entire inventory to a single warehouse. A program is needed
to combine the inventory files of the two companies into a single
inventory file. Luckily both inventory systems were developed in Java
and used the same Book class (see Exercise 1). Write a program to
merge the two inventory files. Since both stores are likely to stock
some of the same books, if the keys match on the merge, the two
records should be combined into one record by summing the
quantities on hand and the quantities on order, and selecting the
minimum of the reorder points and the selling prices. The new file will
contain only one record for each book, and will be sorted by key. The
two input inventory files are each a BinaryDataFile and the new
inventory file is a BinaryOutputFile. The program should display
the number of records read from each file, the total number of
duplicate records (i.e. records common to both files) and the total
number of records written to the new file, as happiness messages to
an ASCIIDisplayer.
4.
Big Profits Inc. requires a payroll application. Every two weeks, the
payroll department processes timesheets for each employee that
worked during the pay period, and enters the information into a
transaction file. This file is then used to generate pay stubs and update
the master file of employee information.
The master file contains one record per employee containing the
employee number (string, the key), employee name (string), hourly
pay rate (double, in dollars), year-to-date (YTD) hours worked
(double), YTD gross pay (double), YTD federal tax withheld (double)
and YTD state tax withheld (double). The YTD values are the totals of
these values from all pay periods so far in the year. The master file is
sorted by employee number.
The transaction file contains one record per employee that worked
during the pay period. Each transaction includes the employee
number (string) and the number of hours worked (double). The
transaction file is also sorted by employee number.
The program should apply the transactions to the master file,
updating the master file by updating the YTD values. A pay stub is to
be produced, for each employee that worked, to a report file. No stub
is produced for an employee that didn’t work, since there is no record
in the transaction file. The stub should indicate the employee name
and number and the hours worked, rate of pay, gross pay, taxes
withheld and net pay as well as a summary of the YTD gross, YTD
taxes withheld and YTD net pay. The federal tax is 17% of the gross
and the provincial tax 40% of the federal tax. The pay stub should
have a format similar to the following:
The employee (master) file should be read, using record-oriented
I/O, from a BinaryDataFile and the timesheet (transaction) file
from an ASCIIDataFile using stream-oriented I/O. The pay stubs
should be written to an ReportPrinter and the new master file to a
BinaryOutputFile. Your program should generate happiness
messages to an ACSIIDisplayer and detect as an error a
transaction for a non-existent employee number, generating an error
message to the message stream.
5.
Usury Inc. is a loan company that issues credit cards. Each month,
records of transactions (payments and purchases) are received by the
central office and entered into a transaction file. At the end of each
month, the company generates statements for each of its cardholders
(accounts), listing the transactions for the month and the final
monthly balance. It also uses the transactions to update the master file
of account information.
The master file contains one record per account that records: the
account number (string), account holder’s name (string), address
(string) and the final balance (double) of the account at the end of the
previous month. The master file is sorted by account number.
The transaction file contains one record per transaction which
includes: the transaction type (a character: C (credit) for a payment or
D (debit) for a purchase), the account number (string), the date
(string: yy/mm/dd), a description (string) and an amount (double).
The transaction file is sorted by date within account number.
A statement is to be produced for each account to a report file, each
statement on a new page. At the beginning of each statement should
be the company name, statement date, account number, card holder’s
name and address. Then, for each transaction to the account, there is a
line giving: the date, description, amount and new balance after
applying the transaction to the account. When all transactions for the
account have been processed, a line should be printed giving the
original balance, total payments, total purchases, interest charged and
new balance. The statement for an account might look like the
following:
Interest (at a rate of 23% per month) is charged on any outstanding
balance from the previous month. The outstanding balance is the
difference between the original balance and the total payments. The
updated master record should be written to a new master file when
the processing of the account is complete.
The billing date should be read from an ASCIIPrompter,
happiness and error messages written to an ASCIIDisplayer, the
master (account) file read from a BinaryDataFile, the transaction
file read from an ASCIIDataFile, the statements written to an
ReportPrinter and the new master file written to a
BinaryOutputFile. Your program should detect, as an error, a
transaction on a non-existent account number and generate an error
message to the message file. You may assume that transactions are
otherwise valid.
6.
The Registrar’s Office at Broccoli University must produce
statements of standing for students at the end of each academic year.
The office maintains a file of student information (master file).
Throughout the year, course marks are submitted by instructors and
are batched into a transaction file. At the end of the year, the
transaction file is sorted by student number and used to update the
master file and generate statements of standing.
The master (student) file contains one record per student
containing: the student number (string), student’s name (string),
address (as three strings: street, city and postal code), the student’s
major department (string), the number of major credits completed
(integer), the total of grades on major courses (integer), the number
of minor (other) credits (integer) and the total of grades on the minor
courses (integer). The master file is a binary (object-oriented) file
sorted by student number.
The transaction file contains one record per course completed by a
student. Each record includes: the department (string), course
number (string), session (string), course title (string), student number
(string) and grade awarded (integer). The transaction file is a text file
sorted by student number.
The updated master record should be written to a new master file
when the processing of the student’s courses is complete.
A statement of standing is to be produced to a report file for each
student that completed a course during the academic year. That is, for
each student for whom there is at least one transaction record. At the
beginning of each statement should be the university name, statement
date, student number and name. Then, for each course, there is a line
giving: the session, department, course number, title and grade
awarded. When all courses have been processed for the student, a
summary including the previous (i.e. before this year) major, minor
and total credits and averages and the current (i.e. after applying the
new courses) major, minor and total credits and averages. Each
statement should begin on a new page. Since students can only take a
limited number of courses in a year, you may assume that the
statement for each student will be no more than one page in length. A
statement of standing might look like the following:
The statement date should be read from an ASCIIPrompter,
happiness and error messages written to an ASCIIDisplayer, the
master (student) file read from a BinaryDataFile, the transaction
(credit) file read from an ASCIIDataFile, the statements written to
an ReportPrinter and the new master file written to a
BinaryOutputFile. Your program should detect, as an error, a
transaction on a non-existent student number and generate an error
message to the message file. You may assume that transactions are
otherwise valid.
7.
Pages bookstore requires a program to perform inventory control.
Throughout the day shipping orders are received for books to be
shipped to various Pages stores, shipments of books are received from
the publishers and restocking orders are sent to publishers. The
transactions are batched during the day and applied to the master
inventory file during the night so that the inventory records are
correct when the warehouse reopens in the morning. The inventory
file is the same as described in Exercise 1, and is sorted by key.
The transaction (update) file contains one record per transaction
giving the transaction code (character, s=ship, r=receive, o=order,
n=new), inventory number (string) and quantity (integer). For the
new (n) transaction, there are three additional fields: title (string),
reorder point (integer) and selling price (double). The transaction file
is a text file sorted in ascending order by inventory number.
A ship (s) transaction must verify that there is adequate quantity on
hand to satisfy the order. If not it should ship only what is on hand
and generate a message to the report indicating the number of items
ordered and the number actually shipped. In any event, the quantity
on hand should be decreased appropriately. A receive (r) transaction
should decrement the quantity on order and increase the quantity on
hand. An order (o) transaction should increase the quantity on order.
Finally, a new (n) transaction should verify that there is not already a
record for this book and then create a new record in the inventory file
with the data from the transaction record. The quantity on the
transaction record becomes the quantity on order for the new record.
Essentially the new transaction is an order transaction for a new
book.
When all transactions for a master record are processed, the updated
master record should be written to the new master file (binary,
object-oriented file). If the quantity on hand plus the quantity on
order is below the reorder point, a message requesting a restocking
order should be generated to the report for enough items to reach the
reorder point.
Happiness messages are written to an ASCIIDisplayer, the
master (inventory) file is read from a BinaryDataFile, the
transaction file is read from an ASCIIDataFile, the report is
written to an ASCIIOutputFile and the new master file is written
to a BinaryOutputFile. Your program should detect, as an error, a
transaction (other than n) on a non-existent ISBN or a new
transaction on an existent ISBN, and generate an error message to the
report file. You may assume that transactions are otherwise valid.
[1]
Actually, like everything else on computers, a megabyte is defined
in base two not ten. A megabyte is actually 220 or 1,048,576 bytes.
We commonly use the approximation of one million for convenience.
[2]
Abelson, H. & diSessa, A.A.; Turtle Geometry; MIT Press;
Cambridge, Mass; 1980
[3]
A radian is a unit of measure of an angle. There are 2π radians
around a complete circle.
[4]
http://docs.oracle.com/javase/specs/jls/se7/html/index.html
[5]
Ronan, Philip; used under Creative Commons CC by SA 2.5 license
[6]
from Wikimedia Commons, public domain
[7]
Judd, Deane B.; Wyszecki, Günter (1975). Color in Business, Science
and Industry. Wiley Series in Pure and Applied Optics (3rd ed.). New
York: Wiley-Interscience. p. 388. ISBN 0-471-45212-2.
[8]
Floryan, Marcin, public domain
[9]
image used under Creative Commons CC by 3.0 license; see URL:
http://freeaussiestock.com/free/Queensland/slides/mission_beach.htm
[10]
from Wikimedia Commons, public domain
[11]
C. E. Shannon, "Communication in the presence of noise",
Proc. Institute of Radio Engineers, vol. 37, no. 1, pp. 10-21, Jan. 1949.
[12]
Image of Charles Babbage (public domain)
[13]
The term student number is a misnomer. Although it is made up of
digits, it isn’t really a number. You don’t perform arithmetic on it. It
really serves as an identifier, uniquely identifying each student in the
University.
[14]
Image copyright Alexandre Normand, used under Creative
Commons License CC by 2.0.
Download