Uploaded by Maria Sheryl Laurio

13249036-Principles-in-JAVA

advertisement
Programming Principles
in Java:
Architectures and Interfaces
David Schmidt
Computing and Information Science Department
Kansas State University
January 2003 Edition
This text is Copyright 2003 by David Schmidt. Reproduction is allowed by permission
of the author, who can be contacted at schmidt@cis.ksu.edu
Preface
My nine-year-old niece is a computer programmer: Using her father’s PC and
Disney’s “Print Studio” software, she constructs programs that print greeting cards
for her friends. My niece has no training in art, graphic design, or computer programming, yet she programs greeting cards to her satisfaction—she practices “naive
programming.”
In similar and ever increasing fashion, naive programmers use visual, “drag and
drop” languages to program applications for home, school, and office use. Naive
programming will play a crucial role in satisfying the exploding demand for software,
but there will always be limits—just as no hospital patient would submit to a surgery
conducted by a naive surgeon, no customer of a complex or safety-critical system will
settle for software written by anyone but a properly trained, professional programmer.
This textbook is meant for a first course for future professional programmers and
computing scientists.
What makes “professional programming” different from naive programming? The
answer lies in use of structures—control structures (sequencing, conditionals, iteration, parallelism), data structures (arrays, stacks, trees), and component structures
(modules, classes, objects, packages). Professional programmers understand how to
employ properly all three forms of structure; amateurs do not. Further, patterns
of structures-within-structures define architectures that are learned and regularly imitated by professionals. As indicated by its title, this text presents standard architectures for component construction and patterns of control construction and data
construction.
The text takes a “modern” approach by emphasizing component structures over
the other two forms. Computing has matured into a distributed, component-based
activity, where both computer hardware and software are assembled from standardized components and connected together by means of standardized interfaces. By no
means does this text ignore the classic developments of control and data structures—
they appear in due course at an appropriate level of detail. But component-level
issues drive the software design process, and the text emphasizes this fact.
Java is used as the programming language in this text because it supplies solid
support for component-structure-driven design; its control and data structuring mechanisms are adequate as well. Because Java and its support library are huge, only a
carefully selected subset that promotes sound programming techniques is presented.
To the Student
As the previous narrative indicates, learning to program requires more than merely
learning to write in a particular computer language—you must understand the structures within programs and how these structures behave. To do this, you must pore
ii
over the book’s programming examples, copy them to your computer, test them, try
to “break” or “trick” them, modify them in small ways, and try them again. In many
ways, computer programs are like toys or appliances that can be examined, played
with, disassembled, and reassembled in different ways. Experiences like these help
you develop programming intuitions.
Most sections in the text end with a short exercises section that suggests simple
ways to apply and modify the programs in the section. Work at least one or two of
the exercises before you proceed to the next section, and if you have difficulty with an
exercise, do not hesitate to reread the section. It is rare for anyone to understand a
new concept after just one reading, and a technical topic like programming requires
careful, thoughtful effort for deep understanding. Remember that progress is measured by the number of concepts and structures that you can use well and not by the
number of pages you have read or number of programs you have typed.
Each Chapter concludes with a collection of projects that test your abilities to
design and build complete programs. The projects are roughly ordered in terms of
difficulty, and many are written so that you can “customize” them into a product
that you or others would enjoy using—take time to make your final result one you
would be proud to demonstrate to others.
Like video recorders and microwave ovens, programs come with instructions.
When you study the example programs in the text, pay attention to the “instructions”
(documentation) that are included. Because programs and program components are
useless without documentation, you must develop the habit of documenting the programs you write. This activity pays huge dividends when you start building large,
complex programs.
The Java programming language is used as the working language in this text.
Java is by no means perfect, but it supports the crucial structures for programming,
and it provides mechanisms for “fun” activities like graphics and animation. Chapter
2 states partial instructions for installing Java on your computer, but if you are
inexperienced at installing software, you should seek help.
The programming examples from the text and other supporting materials can be
found at the URL http://www.cis.ksu.edu/santos/schmidt/ppj.
To the Instructor
My experiences, plus the extensive feedback I have received from the Scott/Jones
reviewers and my colleagues, have caused the text to evolve into an implementation
of the following algorithm:
1. Convince the students that programs have architectures, like houses do. Tell
them programming is a learned discipline, like house design and construction.
2. Start students sending messages to objects immediately. Amuse and motivate
them with a bit of graphics, but don’t overwhelm them with Java trivia.
iii
3. Teach the students class design and component assembly via interfaces before
the students get lost in loops.
4. Use control structures and array data structures to build “smarter” objects.
5. Finish with interesting applications—GUI-driven programs, database systems,
interactive games, animations, and applets.
The ordering of Step 3 before Step 4 is crucial, because it encourages the componentdriven approach to programming.
Here are some pragmatic issues and how they are handled by the text:
• Design: Beginners learn by imitation. For this reason, the text uses simplified
versions of the Smalltalk Model-View-Controller (MVC) software architecture
for its programs. (The MVC architecture structures a program so that its
model component handles the computational duties of a program, the view
components(s) handle input-output transmission, and the controller controls
the transfer of data from view to model and back.) This architecture helps a
beginner see how a program is assembled from components and how components
are designed so that they can be easily unconnected, reconnected, and replaced.
The beginner can readily imitate this architecture in her own projects.
As part of the design process, components are first specified with UML/Javastyle interfaces before any code is written. (An interface lists the names of the
public methods and private attributes a class needs to do its job.) For complex
applications, UML class diagrams are drawn.
• Documentation: All programs are documented in Sun’s “javadoc” style, and
the reader is shown how to use Sun’s javadoc tool to automatically generate Web-based documentation pages (the so-called “Application Programming
Interface”—“API”) for her programs. UML class diagrams document the program’s overall architecture.
• Pedagogy: Chapters are organized into Essentials-Projects-Supplement components. The Essentials sections present the central concepts of that chapter;
included with these sections are exercise sets that guide the student through
basic applications of the topic. The chapter concludes with a section of programming projects and multiple Supplement sections, which provide technical
details that a student or instructor can skip the first time through the text.
• Applications: When using Java for programming examples, it is tempting to
emphasize graphics, animations, applets, and networking, for which Java provides ample support. But not all programming fundamentals are best taught
with these applications, so a middle ground is taken: The text presents a mixture of information processing examples and graphics examples. Animations
iv
and applets appear in due course. Networking is not a beginner’s topic and is
not covered.
The text does not use any specially written classes or packages supplied by the
author—only the standard Java packages are used. This prevents a beginner from
becoming dependent on nonstandard variants of Java and relieves the instructor of
the headache of installing custom packages on classroom computers and students’
personal computers.
Although the choice of Java as the text’s programming language is basically sound,
the language possesses several annoying features. One that simply cannot be avoided
is using the static method, main, to start an application’s execution. To avoid tedious
explanations of static methods and classes from which no objects are ever created,
the text naively claims in Chapter 2 that an application’s start-up class (that is, the
one that contains main) generates a “start-up” object. Technically, this is incorrect,
but it allows a beginner to stick with the axiom, “Classes Generate Objects,” which
is promoted from Chapter 1 onwards. The remainder of the text presents the syntax
and semantics of Java in technically correct detail.
The programming examples from the text and other supporting materials can be
found at the URL http://www.cis.ksu.edu/santos/schmidt/ppj.
Acknowledgements
First and foremost, I thank Gudmund Skovbjerg and his students at Aarhus University, Denmark, who used several earlier drafts of this text. Their comments led to
huge improvements in the book’s organization and pedagogy. Vladimiro Sassone and
his students at Catania University, Italy, and Peter Thiemann and his students at
Freiburg University, Germany, also used early drafts of the text, and they thanked
as well. I’ve also received useful comments from Thore Husfeldt and his students
at the University of Lund University, Sweden, Aake Wikstro”m and his students at
the University of Gothenburg, Sweden, and Sebastian Hunt at City University, London. I also thank my co-instructor, William Shea, my graduate teaching assistants,
and my students at Kansas State University for tolerating numerous revisions over
a multi-year period. Bonnie Braendgaard of Aarhus University, Carolyn Schauble of
Colorado State University, and Bernhard Steffen and Volker Braun of Dortmund University are thanked for their insightful suggestions. I also appreciate the comments
and criticisms of my departmental colleagues, Michael Huth and Stefan Sokolowski.
Richard Jones and Robert Horan of Scott/Jones Press deserve special thanks for
their initial interest in the text, their tolerance of my rewritings, and their recruitment
of the following review team, whose commentaries led the text into its final form:
REVIEWERS’ NAMES HERE.
The book’s first draft was written while I spent part of a sabbatical year at Aarhus
University, Denmark; I thank Olivier Danvy for hosting my visit. Subsequent drafts
v
were written during periods when I was supported by the National Science Foundation, the Defense Advanced Research Projects Agency, and the National Aeronautics
and Space Administration; I thank my project managers, Frank Anger (NSF), Helen
Gill (DARPA), and Mike Lowry (NASA) for tolerating what I hope will be judged a
substantial contribution to programming.
Finally, during the period of time this book was written, my mother, Frances
Louise Walters Schmidt, died; I dedicate the text to her.
vi
Table of Contents
Preface
i
Chapter 1: Computers and Programming 1
1.1 What is a Computer? 1
1.2 Computer Programming 2
1.3 Programs Are Objects 5
1.4 Operating Systems and Windows 6
1.5 Software Architecture 8
1.5.1 Class Diagrams 9
1.6 Summary 11
1.7 Beyond the Basics 13
1.7.1 Stepwise Refinement 14
1.7.2 Object-Oriented Design 15
1.7.3 Classes Generate Objects 17
1.7.4 Frameworks and Inheritance 18
Chapter 2: Simple Java Applications 20
2.1 An Application and its Architecture 20
2.2 How to Build and Execute an Application 23
2.2.1 Using an IDE 23
2.2.2 Using the JDK 28
2.3 How the Application Works 30
2.3.1 An Execution Trace of the Application 33
2.4 How One Object Constructs Another 35
2.5 Repairing Compiler Error Messages 41
2.6 Summary 43
2.7 Programming Exercises 46
2.8 Beyond the Basics 47
2.8.1 Syntax 47
2.8.2 Semantics 49
2.8.3 Java Packages 51
2.8.4 Java API 51
Chapter 3: Arithmetic and Variables 56
3.1 Integer Arithmetic 57
3.2 Named Quantities: Variables 59
3.2.1 Variables Can Vary: Assignments 63
3.3 Arithmetic with Fractions: Doubles 68
3.4 Booleans 72
3.5 Operator Precedences 74
3.6 Strings, Characters, and their Operations 75
3.7 Data-Type Checking 80
3.8 Input via Program Arguments 83
3.8.1 Converting between Strings and Numbers and Formatting 86
3.8.2 Temperature Conversion with Input 88
3.9 Diagnosing Errors in Expressions and Variables 92
3.10 Java Keywords and Identifiers 95
3.11 Summary 95
3.12 Programming Projects 97
3.13 Beyond the Basics 102
3.13.1 Longs, Bytes, and Floats 102
3.13.2 Helper Methods for Mathematics 103
3.13.3 Syntax and Semantics of Expressions and Variables 105
Chapter 4: Input, Output, and State 107
4.1 Interactive Input 108
4.1.1 Dialog Output 112
4.2 Graphical Output 113
4.2.1 Panels and their Frames 113
4.2.2 Customizing Panels with Inheritance 115
4.3 Format and Methods for Painting 122
4.3.1 Constructor Methods and this Object 124
4.4 Objects with State: Field Variables 130
4.4.1 Using Fields to Remember Inputs and Answers 135
4.4.2 Scope of Variables and Fields 140
4.5 Testing a Program that Uses Input 142
4.6 Summary 144
4.7 Programming Projects 147
4.8 Beyond the Basics 151
4.8.1 Scope of Variables and Fields 151
4.8.2 Partial API for JFrame 152
4.8.3 Methods for GregorianCalendar 152
4.8.4 Colors for Graphics 152
5.8.5 Applets 153
Chapter 5: Component Structure: Method and Class Building 157
5.1 Methods 158
5.2 Public Methods 159
5.2.1 Basic Public Methods 160
5.2.2 Constructor Methods 164
5.3 Parameters to Methods 168
5.3.1 Forms of Parameters 173
5.4 Case Study: General Purpose Output Frame 179
5.5 Results from Methods: Functions 186
5.6 Private Methods 193
5.7 Summary 200
5.8 Programming Projects 203
5.9 Beyond the Basics 212
5.9.1 Naming Variables, Methods, and Classes 212
5.9.2 Generating Web Documentation with javadoc 213
5.9.3 Static Methods 217
5.9.4 How the Java Compiler Checks Typing of Methods
5.9.5 Formal Description of Methods 221
5.9.6 Revised Syntax and Semantics of Classes 227
Chapter 6: Control Structure: Conditional Statements 229
220
6.1 Control Flow and Control Structure 230
6.2 Condtional Control Structure 231
6.2.1 Nested Conditional Statements 235
6.2.2 Syntax Problems with Conditionals 240
6.3 Relational Operations 241
6.4 Uses of Conditionals 245
6.5 Altering Control Flow 249
6.5.1 Exceptions 250
6.5.2 System Exit 251
6.5.3 Premature Method Returns 252
6.6 The Switch Statement 252
6.7 Model and Controller Components 255
6.7.1 Designing an Application with a Model-View-Controller Architecture 257
6.8 Case Study: Bank Accounts Manager 259
6.8.1 Collecting Use-Case Behaviors 259
6.8.2 Selecting a Software Architecture 261
6.8.3 Specifying the Model 261
6.8.4 Writing and Testing the Model 261
6.8.5 Specifying the View Components 265
6.8.6 A Second Look at the Software Architecture 265
6.8.7 Writing the View Classes 265
6.8.8 Controller Construction and Testing 272
6.8.9 Testing the Assembled Application 276
6.8.10 Multiple Objects from the Same Class 277
6.9 More about Testing Methods and Classes 283
6.9.1 Testing Individual Methods 283
6.9.2 Testing Methods and Attributes Together 283
6.9.3 Testing a Suite of Methods 284
6.9.4 Execution Traces 285
6.10 Summary 286
6.11 Programming Projects 290
6.12 Beyond the Basics 295
6.12.1 The Logic of the Conditional Statement 295
6.12.2 Interface Specifications and Integration 300
Chapter 7: Patterns of Repetition: Iteration and Recursion 304
7.1 Repetition 305
7.2 While Loops 306
7.3 Definite Iteration 308
7.3.1 Definite-Iteration Example: Painting a Bulls-Eye 153
7.4 Nontermination 319
7.5 Indefinite Iteration: Input Processing 321
7.5.1 Indefinite Iteration: Searching 324
7.6 For-Statements 328
7.7 Nested Loops 329
7.8 Writing and Testing Loops 335
7.9 Case Study: Bouncing Ball Animation 338
7.10 Recursion 347
7.10.1 An Execution Trace of Recursion 352
7.11 Counting with Recursion 356
7.11.1 Loops and Recursions 359
7.11.2 Counting with Multiple Recursions 360
7.12 Drawing Recursive Pictures 363
7.13 Summary 366
7.14 Programming Projects 370
7.15 Beyond the Basics 378
7.15.1 Loop Termination with break 379
7.15.2 The do-while Loop 380
7.15.3 Loop Invariants 381
7.15.4 Loop Termination 386
7.12.5 More Applets 387
Chapter 8: Data Structure: Arrays 391
8.1 Why We Need Arrays 392
8.2 Collecting Input Data within Arrays 396
8.3 Translation Tables 399
8.4 Internal Structure of One-Dimensional Arrays 402
8.5 Arrays of Objects 406
8.6 Case Study: Databases 3409
8.6.1 Behaviors 412
8.6.2 Architecture 413
8.6.3 Specifications 413
8.6.4 Implementation 415
8.6.5 Forms of Records and Keys 420
8.7 Case Study: Playing Pieces for Card Games 424
8.8 Two-Dimensional Arrays 431
8.9 Internal Structure of Two-Dimensional Arrays 434
8.10 Case Study: Slide-Puzzle Game 437
8.11 Testing Programs with Arrays 446
8.12 Summary 448
8.13 Programming Projects 450
8.14 Beyond the Basics 458
8.14.1 Sorting 458
8.14.2 Searching 462
8.14.3 Time-Complexity Measures 465
8.14.4 Divide-and-Conquer Algorithms 469
8.14.5 Formal Description of Arrays 477
Chapter 9: Programming to Interfaces 483 ¡/a¿
9.1 Why We Need Specifications 484
9.2 Java Interfaces 485
9.2.1 Case Study: Databases 493
9.3 Inheritance 497
9.4 Reference Types, Subtypes, and instanceof 500
9.5 Abstract Classes 508
9.5.1 Case Study: Card Players 509
9.5.2 Class Hierarchies 516
9.5.3 Frameworks and Abstract Classes 519
9.6 Subtypes versus Subclasses 519
9.7 class Object and Wrappers 520
9.8 Packages 522
9.8.1 Generating Package APIs with javadoc 524
9.9 Case Study: An Adventure Game 526
9.9.1 Interfaces and Inheritance Together 532
9.9.2 Inheritance of Interfaces 539
9.10 Summary 539
9.11 Programming Projects 543
9.12 Beyond the Basics 544
9.12.1 Subclasses and Method Overriding 545
9.12.2 Semantics of Overriding 550
9.12.3 final components 555
9.12.4 Method Overloading 556
9.12.5 Semantics of Overloading 561
Chapter 10: Graphical User Interfaces and Event-Driven Programming 564
10.1 Model-View-Controller Revisited 565
10.2 Events 567
10.3 The AWT/Swing Class Hierarchy 568
10.4 Simple Windows: Labels and Buttons 571
10.5 Handling an Event 579
10.5.1 A View as Action Listener 579
10.5.2 A Separate Controller 583
10.5.3 A Button-Controller 583
10.6 Richer Layout: Panels and Borders 590
10.6.1 An Animation in a Panel 594
10.7 Grid Layout 600
10.8 Scrolling Lists 604
10.9 Text Fields 610
10.10 Error Reporting with Dialogs 617
10.11 TextAreas and Menus 620
10.11.1 Case Study: Text Editor 623
10.12 Event-Driven Programming with Observers 632
10.12.1 Observers and the MVC-Architecture 635
10.13 Summary 636
10.14 Programming Projects 641
10.15 Beyond the Basics 645
10.15.1 Applets 646
10.15.2 Tables and Spreadsheets 649
10.15.3 Handling Mouse Clicks and Drags 655
10.15.4 Threads of Execution 664
10.15.5 GUI Design and Use-Cases 669
10.15.6 Summary of Methods for Graphical Components 671
Chapter 11: Text and File Processing 680
11.1 Strings are Immutable Objects 681
11.1.1 String Tokenizers 683
11.2 Sequential Files 686
11.2.1 Output to Sequential Files 688
11.2.2 Input from Sequential Files 690
11.3 Sequential Input from the Command Window 692
11.4 Case Study: Payroll Processing 695
11.5 Exceptions and Exception Handlers 700
11.5.1 Restarting a Method with an Exception Handler 702
11.5.2 Interactive Input with Exception Handlers 705
11.6 Exceptions Are Objects 706
11.6.1 Programmer-Generated Exceptions 713
11.7 Summary 714
11.8 Programming Projects 716
11.9 Beyond the Basics 719
11.9.1 Character-by-Character File Processing 719
11.9.2 Binary Files and Files of Objects 720
11.9.3 A Taxonomy of File Classes 721
11.9.4 A GUI for File Selection 730
Appendix I:Java Language Definition 724
Appendix II:Types and Subtypes 741
Appendix III:Class Diagrams 744
Index 748
Chapter 1
Computers and Programming
1.1 What is a Computer?
1.2 Computer Programming
1.3 Programs Are Objects
1.4 Operating Systems and Windows
1.5 Software Architecture
1.5.1 Class Diagrams
1.6 Summary
1.7 Beyond the Basics
We begin this text by describing the fundamental aspects of computers and programs,
and we present computer programming as a three-step process:
1. designing an architecture
2. defining the classes that comprise the architecture
3. writing the Java instructions that realize each class.
1.1
What is a Computer?
Electronic computers can be found almost everywhere, but in general terms, a computer is any entity that can follow orders, more specifically, that can execute instructions. This classification includes humans (who are imperfect computers) as well as
pocket calculators, programmable disc players, and conventional “PC”s.
This text is concerned with conventional computers, which must possess (at least)
these two components:
• one (or more) processors. A processor executes instructions (e.g., arithmetic
calculations or instructions to draw colors and shapes).
• primary storage (also known as “memory,” “random access memory,” or “RAM”).
Primary storage acts as an electronic “scratch pad” that holds the instructions that the processor reads and executes and holds numbers and information
(“data”) that the processor calculates upon—primary storage is like the scratch
pad that an accountant (a human “processor”) uses.
2
Most computers have also secondary storage devices—internal disk drive (“hard drive”),
compact disk drive, and diskette drive (“floppy drive”)—whose data must be copied
into the primary storage component before it can be read by the processor. Secondary
storage is designed for portability (e.g., diskettes that can be carried from one computer to another) or for holding massive amounts of data, much like an accountant’s
filing cabinet can hold more information than the accountant’s scratch pad.
The filing cabinet analogy just mentioned has generated some standard computer
terminology: A file is a collection of instructions or data; files are themselves grouped
into folders (also known as directories). Directories are normally kept in secondary
storage, their contents copied into primary storage when needed by the processor.
Finally, a computer usually has several input-output devices (such as the display,
keyboard, mouse, printer), which let a human supply information to the computer
(say, by typing at the keyboard or clicking a mouse) and receive answers (by reading
it on the display or from the printer).
If a computer can execute instructions, what kind of instructions can it execute?
This depends—a conventional notebook computer of course cannot execute the instructions written in a cookbook, nor can it follow the instructions for driving a
car from Chicago to Manhattan. (But there are special-purpose computers that can
attempt the latter.)
The processor inside a typical computer performs a limited range of arithmeticlike instructions—numeric addition and subtraction and text copying. But to do
even these simple tasks, numbers and text must be coded in sequences of 1’s and 0’s,
called bits. This technique of writing numbers, text, and instructions to a computer
is called binary coding. The collection of binary-coded instructions that a computer’s
processor can read and execute is called the computer’s machine language. By using
binary codings, a computer can compute with letters as well as numbers. Images
(with colors, brightness, and shapes) can also be coded with binary codings.
Exercises
1. If a computer is indeed any entity that can follow orders, then give examples
from real life of “computers.”
2. List all the input-output devices that are attached to the computer you will use
for your programming exercises.
3. Hand-held calculators are computers. What are the input-output devices for a
calculator?
1.2
Computer Programming
Programming is the activity of writing instructions that a computer can execute. For
1.2. COMPUTER PROGRAMMING
3
Figure 1.1: a program for baking “lemon cake”
1. Squeeze the juice from one lemon into a large bowl.
2. Cut the lemon’s rinds into small pieces and add them to the bowl.
3. Mix three eggs, one cup of sugar, two tablespoons of flour,
and one cup of milk into the bowl.
4. Pour the mixture into a square cake pan.
5. Heat an oven to 350 degrees Fahrenheit.
6. Insert the pan into the oven and wait 40 minutes.
the moment, forget about conventional computers—if your spouse can read and can
operate an oven, then when you write instructions for baking a cake, you are “programming” your spouse—the instructions are a program, and you are a programmer.
The point here is that a program (in this case, for baking a cake) is a list of
instructions written in a precise style where declarative verbs (“cut,” “pour,” “heat”)
state computational actions, and nouns (“egg,” “flour,” “bowl”) state data with which
computation is performed; see Figure 1 for an example.
A sequence of declarative instructions for accomplishing a goal, like that in Figure
1, is called an algorithm. The term computer program, is used to describe an algorithm
that is written precisely enough so that a computer processor can read and execute the
instructions. For historical reasons, the writing of computer programs is sometimes
called coding, and the program itself is sometimes called code.
A computer program is almost always saved as a file on secondary storage. When
someone wants the processor to execute the program’s instructions, the programmer
starts the program—this causes the program to be copied into primary storage, where
the processor reads the instructions and executes them. Starting a program might be
done with a mouse click on a program’s icon (its picture on the display) or by typing
some text with the keyboard.
Unfortunately, computer processors execute instructions written in machine language—
the language of binary codings—and humans find this language almost impossible to
write. For this reason, other languages have been designed for programming that
are easier for humans to use. Examples of such high-level programming languages are
Fortran, Cobol, Lisp, Basic, Algol, Prolog, ML, C++, and Java. Many of these languages look like the language of secondary-school algebra augmented with a carefully
defined set of verbs, nouns, and punctuation symbols.
In this text, we use Java as our programming language.
When one writes a program in Java, there remains the problem of making the
computer’s processor understand the program. This problem is solved by the Java
designers in two stages:
1. First, a translator program, called a compiler, is used to translate Java programs
4
into another language, called Java byte code, which is almost machine language.
The programmer starts the compiler and tells it to translate her Java program
to byte code. The resulting byte code is deposited, as a file, on secondary
storage.
2. Next, when the programmer wishes the byte code executed, she starts a second
program, the Java interpreter (also known as the Java virtual machine or JVM).
The interpreter copies the byte code to primary storage and works with the
processor to execute the machine-code versions of the byte-code instructions.
Programming languages like Java are useful for programming these kinds of activities:
• scientific and mathematical calculations, such as calculating the roots of a
quadratic equation, drawing the curve for a polynomial, or printing a table
of monthly payments for varying interest rates on a mortgage.
• information processing, such as editing and typesetting a letter, printing a file
of paychecks, or drawing pictures on the console.
• simulation, such as imitating the cockpit of an airplane, simulating the next
five days’ weather, or playing a card game.
This text intends to show you how to use the Java language to program these activities. Along the way, you will learn practical and formal aspects of writing programs
in good style.
Exercises
1. Locate a cookbook and study one of its recipes. Mark the declarative verbs,
nouns, and precise quantities. Also, circle any instructions in the recipe that
appear to you to be imprecise.
2. Arithmetic is often called the “first programming language.” Pretend that this
expression is a program
(3 + 2) - 1 + (6 + 5)
and pretend that you are a computer. List the steps you take with your pencil
to execute the instructions in this program. (That is, compute the answer to
the expression, one step at a time.)
3. Algebra is a programming language. List the steps you take to solve the value
of x in this “program”:
3y - x = 3 + 2x
5
1.3. PROGRAMS ARE OBJECTS
Figure 1.2: the computing environment
DISPLAY
USER
DISKS
EXECUTING PROGRAM (in primary storage)
KEYBOARD
OTHER COMPUTERS
(Hint: the first step is to add x to both sides of the equation, producing the
new equation, 3y - x + x = 3 + 2x + x.)
4. Here is a small fragment of a Java program:
int x = 3 + 2;
int y = x + 1;
displayOnTheConsole(y);
Which parts of this program appear to be verbs? nouns? adjectives? algebraic
expressions?
5. Propose a programming language for drawing colored bubbles and squares on
a sheet of paper. What verbs will you include (e.g., “draw,” “trace”)? nouns
(“circle,” “red”)? adjectives (“large,” “dark”)?
1.3
Programs Are Objects
When a computer program is executing (that is, the program has been copied into
primary storage, and the processor is executing its instructions), the program does
not “live” in isolation; it is surrounded by an environment consisting of a keyboard,
display, disks, and even other computers. Figure 2 pictures such an environment. We
use the term, object, as a generic term for each of the components in a computing environment. The EXECUTING PROGRAM is itself an “object,” as is the computer’s
USER (a human being), who interacts with the machinery. Indeed, a program can
itself consists of multiple objects (smaller programs).
An “object” is meant to be a generalization of a “person”—an object has an
“identity,” it knows some things, and it is able to do some things.
We use the term, method, to describe an object’s skill or ability or activity—a
thing that an object can do. For example, an executing word-processing program is
an object that has methods for inserting words, moving words, correcting spelling
errors, and typesetting documents. An executing program that calculates retirement
savings is an object with methods for calculating rates of savings, interest rates,
6
payment schedules, etc. And a graphics program is an object that has methods for
drawing lines and geometric figures, painting the figures, moving them, and so on.
Similarly, a keyboard is an object that has methods for typing letters and numbers,
and a display is an object that has methods for displaying text, colors, and shapes. A
user has methods for typing text at a keyboard and reading answers on the display.
(Users also have methods for eating, sleeping, etc., but these are not important to
computer programming.)
Objects “communicate” with each other—one object can ask another to perform
one of its methods. This is called sending a message. The arrows in Figure 3 indicate
the directions in which messages are sent. For example, a USER might wish to
know the square root of a number, so she types the number on the KEYBOARD,
in effect sending a message to the KEYBOARD object. The KEYBOARD is the
receiver of the message. (The USER is called the client.) The KEYBOARD reacts
to the message by using one of its methods to convert key taps into a number, and
it sends a message containing the number to the EXECUTING PROGRAM. The
EXECUTING PROGRAM receives the message and uses its methods to compute
the number’s square root. The EXECUTING PROGRAM then communicates the
square root to the DISPLAY, which shows number, as symbols, on the screen so that
it reaches the USER’s eyes.
In this way, computation is performed by a series of communications between
objects. A complex executing program might itself be a collection of interconnected
objects—perhaps computing the square root of a number is completed only after
several internal communications between the program’s own executing subobjects.
Here is some commonly used terminology: When a human supplies information to
a computer program, say, by typing on a keyboard or moving a mouse, the information
is called input to the program. (Another term is input events.) Input can also be
supplied to a program from information on a disk. When the program calculates a
result or answer and this answer is displayed or saved on a disk, this is called the
program’s output. Programs are often called software, which is a pun on the term,
hardware, which of course describes physical devices such as processors, displays, and
disk drives.
1.4
Operating Systems and Windows
When a computer is first started, its processor looks on the computer’s disk drive
for a program to place first into primary storage to execute; the program it finds
is called the operating system. An operating system is the computer’s “controller
program”; by displaying information on the display and by receiving messages from
mouse and keyboard, the operating system helps the computer’s user execute other
programs. Often, a user’s request to execute a program is little more than a mouse
movement and a click, or it might be the typing of text within a command window
1.4. OPERATING SYSTEMS AND WINDOWS
7
Figure 1.3: a multi-window display
(or command-prompt window).
Prior to the 1980s, an operating system used the computer’s display as one large
command window. Programs were started by typing within the window, and the
program would read its input data from the window and would display its output
within the same window. All input and output were text—words and numbers.
Of course, modern operating systems create multiple windows on the display,
where the windows might be command windows or windows created by executing
programs. Figure 3 shows a display that holds three distinct windows: a command
window, a window created by a word-processing program, and a window created by a
Web-browsing program. These windows were created with the behind-the-scenes help
of the operating system. The user interacts with the window by moving the mouse
into it, typing, clicking, or reading. Icons appear along the left side of the display.
The point of Figure 3 is that the multiple windows that appear on the display
are themselves distinct objects. The crude picture in Figure 2 suggested that the
DISPLAY was one object, but modern operating systems make it possible for an
8
executing program to communicate with multiple window objects. In a similar way,
the multiple folders and files that reside in secondary storage are also distinct objects.
Exercise
Use a computer to start a program, like a game or a word processor. List all the
windows that are created by the program, list the ways you give input information
to the program, and list the ways the program displays output.
1.5
Software Architecture
The previous section stated that a typical computing environment contains many objects: keyboard, windows, executing program, files on secondary storage, etc. When
we do some computing, we do not build objects like keyboards from scratch—we use
the one we bought. And we buy programs and use them as well. But this text is
about building programs rather than buying them—how do we do this?
Becoming a good programmer is not unlike becoming a good novelist, bridgebuilding engineer, or architect—time and effort must be invested in studying standard
examples of the genre, disassembling and modifying them, learning basic techniques
of composition/construction, and working plenty of exercises.
In novel writing, bridge construction, and house building, a beginner is tempted
to start on the final product without first investing time in a design. A professional
knows better. Consider house building: A modern house’s design is specified by a
blueprint. A blueprint possesses a “style” or architecture. A house can have a simple,
one-room architecture, or it can have a multi-room, split-level, architecture, or it
might even be multiple housing units connected by passageways and stairways.
The house builder uses the blueprint to construct a physical structure with walls,
floors, electrical wiring, and plumbing. Almost no one would buy a house that was
not constructed from a blueprint—the house would not be trustworthy.
Builders read blueprints; most do not design them. The highly trained individual
who designs and draws the blueprint is an architect. A well trained architect knows
about mathematics, physics, and art as well as house construction.
Machines, such as cars, automated bank tellers, and computers, also have architectures (see Figure 2), and so do computer programs. Writing a computer program involves more than merely writing instructions in Java—one must design the program’s
architecture, specify the architecture’s components, and write Java instructions for
each component. Here are the crucial steps:
• First, a program’s architecture is specified by drawing a picture called a class
diagram. The class diagram indicates the components, called classes, that form
the entire program, much like a blueprint shows the rooms that form a house.
1.5. SOFTWARE ARCHITECTURE
9
• Next, each class is designed as a collection of methods.
• Finally, each method is written as a sequence of Java instructions, and the coded
methods for each class are saved as a file in secondary storage.
When the computer program is started, objects in primary storage are created from
the classes in secondary storage, forming the executing program.
This text relies heavily on one particular architecture, called the Model-ViewController (MVC) architecture, so we must acquire some intuition about it. To do
so, we consider an automated bank teller machine (ATM), which is a machine built
in MVC style.
When you use an ATM, you stand in front of a video screen and buttons. This
is the view of the machine. When you insert your bank card and press buttons,
you activate the ATM’s controller, which receives your bank card and button presses
and relays them into the bank, where your account is kept. The accounts inside
the bank are the model, where information about your bank account is held. The
model computes the transaction you requested and relays it back to the view—money
appears.
In summary, the ATM has these components in its architecture:
• A view object—it presents the appearance of the ATM to its human user, and it
possesses methods that receive requests (“messages”) from the user and display
results.
• A controller object—it has methods that control the transfer of information
within the ATM by sending messages to the other components.
• A model object—its methods compute answers in response to messages sent to
it by the controller.
Many appliances are built with MVC architectures (e.g., pocket calculators, video
games, radios), and the MVC architecture adapts well to computer programs as
well—have you ever played a computer game, where the game’s view was projected
on the console screen, the mouse movements activated the game’s controller, and the
computer computed the mouse movements and showed them on the screen? The
computer game is a program with MVC architecture.
1.5.1
Class Diagrams
Real programs consist of thousands of instructions and are too huge to be written
as one long file. As noted in the previous section, a program must be divided into
manageably-sized files called classes. Each class’s instructions are themselves grouped
into meaningful subcollections, called methods—we use the term, “method,” as in the
10
section, “Programs Are Objects”: A method is a specific computing activity that can
be activated when a message is sent to it.
Begin footnote: More precisely, when the classes are copied into primary storage,
they become objects, and the objects send messages that request the other objects to
execute the methods. End footnote
The classes, methods and the manner in which they communicate are drawn as a
class diagram.
A class diagram specifies an architecture from which a program can be constructed.
For example, if we design a program with an MVC architecture, the class diagram
displays a class that serves as the view, a class that serves as the controller, and a
class that serves as the model.
Figure 4 displays a class diagram for a simple word-processing program in MVC
architecture. The components—classes—are annotated with the methods that each
possesses; for example, class MODEL owns an insertText method, so insertTextmessages can be sent to the MODEL. When MODEL receives an insertText-message,
the instructions within the insertText method execute.
Arrows show the direction in which messages are sent. (Connections without
arrow heads suggest that messages might be sent in both directions; compare this to
Figure 2.)
Here is an informal explanation of the behavior of the architecture: When a human
user interacts with the executing word-processing program, her inputs are transferred
from the mouse and keyboard to the INPUT VIEW—perhaps mouse clicks generate
getRequest messages to the INPUT VIEW, and text typed into the edited document
generate getText messages.
As in the ATM example, messages received by the INPUT VIEW are forwarded to
the CONTROLLER component, which sends an appropriate message to the MODEL
component. The MODEL is responsible for “modelling” the document the user is
editing, so the MODEL has methods for editing and altering the document. (For
example, a getText message to the INPUT VIEW causes the CONTROLLER to
be contacted, which sends an insertText message to the MODEL. In this way, the
representation of the document within primary storage gets updated.)
The CONTROLLER also sends messages to the OUTPUT VIEW, telling it to
redisplay its presentation of the edited document on the display or to print the document when requested. (To display or print the document, the OUTPUT VIEW must
send the MODEL a contentsOf message, asking for the current contents of the edited
document.)
Of course, this explanation of the word processor’s software architecture is informal, and additional design work is needed before Java instructions can be written for
the methods and classes.
The programming examples we encounter in the next chapter have simple oneand two-class architectures, like one-room or two-room houses, but programs in subsequent chapters rise or fall on their complex designs, and we must rely on architectures
11
1.6. SUMMARY
Figure 1.4: a class diagram of a word processor
Controller
main
Input View
getRequest
getText
Model
insertText
deleteText
reformatLines
...
contentsOf
Output View
redisplayDocument
printDocument
and class diagrams to guide our way.
1.6
Summary
Each chapter will conclude with a summary of the terms, concepts, and programming
examples that will be needed for later work. Here is the relevant material for Chapter
1:
New Terminology
• computer: an entity that executes instructions
• processor: the part of an electronic computer that executes instructions
• primary storage: the part of an electronic computer that holds the instructions
and information that the processor reads to do its work. Also known as memory,
random access memory, and RAM.
• secondary storage: the parts of an electronic computer that archives additional
instructions and information. Examples are the internal “hard” disk, diskettes,
and compact disks. For the processor to read the information on secondary
storage, it must be copied into primary storage.
• input device: a mouse or keyboard, which supplies information to a computer
• output device: a display screen or printer, which presents information to a computer’s user
• file: a collection of related information, typically saved on secondary storage
• folder: a collection of files, typically saved on secondary storage
12
• bit: the “atomic” unit of information within a computer—a “1” or a “0”
• binary code: a sequence of bits, read by the processor as instructions or information.
• machine language: the specific format of binary code read by a specific processor
• algorithm: a sequence of declarative instructions (“orders”) for accomplishing a
task.
• computer program: an algorithm written specifically for a processor to execute.
• code: traditional name for a computer program
• programming language: a language designed specifically for writing computer
programs
• compiler: a computer program, that when executed, translates computer programs in one programming language into programs in another language
• interpreter: a computer program, that when executed, helps a processor read
and execute computer programs that are not written in machine language
• Java byte code: the compiler for the Java programming language translates
programs written in Java into programs in Java byte code
• Java virtual machine (JVM): the interpreter for Java byte code
• object: a basic unit of an executing computer program
• method: an ability that an object possesses
• message: a communication that one object sends to another
• client object: an object that sends a message to a receiver
• receiver object: an object that receives a message sent by a client
• input: information given to a computer program for computation
• output: the answers computed by a program
• hardware: the physical components of a computer, e.g., processor and primary
storage
• software: computer programs
• operating system: the controller program that starts when a computer is first
switched on
1.7. BEYOND THE BASICS
13
• command window: a position on the computer display where a human can type
instructions to a computer
• software architecture: the overall design of a computer program; analogous to a
house’s blueprint
• class diagram: a graphical presentation of a program’s architecture
• class: a file containing a component of a computer program
• Model-View-Controller (MVC) architecture: a standard software architecture
Points to Remember
• Computer hardware and software are constructed from communicating objects.
Examples of objects are the keyboard, the display, the windows on the display,
files, and the executing program itself.
• Objects have methods, which are abilities that can be performed on request.
Objects communicate requests to other objects to perform their methods by
sending messages to them.
• A computer program is saved in a file in secondary storage. When the program
is started, it is copied into primary storage, and the processor executes the
instructions. We say that the program is executing.
• Programs written in the Java language are saved in files called classes; each
class is a collection of methods. When a Java program is started, the program’s
classes are copied into primary storage; when a class is copied into primary
storage, it becomes an executing object. In this way, an executing program is
a collection of objects that send messages to one another.
1.7
Beyond the Basics
1.7.1 Stepwise Refinement
1.7.2 Object-Oriented Design and Refinement
1.7.3 Classes Generate Objects
1.7.4 Frameworks and Inheritance
In this section and in the similarly named sections in subsequent chapters, we present
material that will enhance your programming skills. This material is optional and can
be skipped on first reading.
14
1.7.1
Stepwise Refinement
In the cooking world, Figure 1 is a “method” for baking lemon cake, and an Italian
cookbook of is a collection, or “class,” of such methods.
How do we write a method from scratch? A classic methodology for method
writing is stepwise refinement (also known as top-down design), which is the process
of writing an outline of a method and then refining the outline with more and more
precise details until the completed method is the final result.
For example, perhaps we must write a method for making lasagna. We begin with
an outline of the basic steps:
1. prepare the sauce
2. cook the pasta
3. place the pasta and sauce in a dish; cover with topping
4. bake the filled dish
Although many details are missing, we have a solid, “top level” design—an algorithm—
that we can refine.
Next, we insert details. Consider Step 3; its refinement might read as follows:
• 3.1 Take a medium-sized dish.
• 3.2 Cover the bottom of the dish with sauce.
• 3.3 Next, place a layer of pasta noodles on top of the sauce.
• 3.4 Repeat steps 3.2 and 3.3 until the dish is filled.
• 3.5 If the cook desires, then sprinkle grated cheese on the top.
The refinement introduces specifics about filling the dish with pasta and sauce. In
particular, notice the use of the words “Repeat ... until” in Step 3.4; this is a clever
and standard way of saying that the step of layering pasta noodles can be repeated
until a specific stopping point is reached. Similarly, Step 3.5 uses the words “if ...
then” to indicate a statement that may or may not be performed, based upon the
situation at the time the recipe is executed.
The other steps in the recipe are similarly refined, until nothing is left to chance.
If we were writing a method in the Java language, we would begin with a toplevel design, like the one just seen, and apply stepwise refinement until all instructions
were spelled out as statements in the Java language. We apply this technique to many
examples in the chapters than follow.
1.7. BEYOND THE BASICS
15
Exercises
Use stepwise refinement to write algorithms for the following:
1. how to journey from your home to the airport
2. how to make your favorite pizza
3. how to change a flat tire on an automobile
4. how to add two fractions
5. how to perform the long division of one integer by another
6. how to calculate the sales tax (value-added tax) for a purchase in your community
1.7.2
Object-Oriented Design
A good programmer will rely on standard architectural patterns, like the MVC architecture seen in this chapter, as much as possible. But occasionally a part of an
architecture (e.g., the MODEL portion of Figure 4) or even a complete new architecture must be designed specially. We encounter such situations in Chapter 7 onwards.
Object-oriented design can be used to design an architecture. Object-oriented
design solves a problem by treating it as a simulation—a re-creation of reality—
inside the computer. For example, games are simulations: When a child pretends
to be a doctor or a fireman, she is creating a simulation. War games like chess and
“battleship” are also simulations, and it is no accident that such games are available
as computer programs.
Indeed, computers are excellent devices for conducting simulations. Executing
programs can create representations of people, televisions, airplanes, and even the
weather. Computer simulations have proven valuable for studying traffic circulation
in cities, predicting climate changes, and simulating the behavior of airplanes in flight.
Some people argue that all computing activities are simulations, because a computer
itself is a “simulation” of how a human being behaves when she calculates.
What is the relationship of simulation to program design? A simulation has
actors or “objects” (e.g., people or pawns or battleships) who play roles. Each actor
has abilities or “methods,” and the actors communicate and interact to enact the
simulation.
To gain some intuition about simulations, here an example. Say that you are
hired as a consultant to help some entrepreneurs start a new Italian restaurant, and
you must design an operating plan for the the restaurant and its personnel.
To solve this nontrivial problem, you simulate the restaurant and its personnel.
The simulation is formed by answering several basic questions:
16
Figure 1.5: architecture of a restaurant
Customer
giveOrder
Waiter
calculateCheck
Cashier
receiveMoney
Cook
cookLasagna
cookTortellini
• Components: What are the forms of object one needs to solve the problem (in
this case, to operate the restaurant)?
• Collaborators: What will be the patterns of communication between the objects? (Who talks with whom in the restaurant?)
• Responsibilities: What methods must each object have so that appropriate actions can be taken when there is communication? (What will the staff do when
asked?)
The answers you give to these questions should lead to an appropriate operations
plan—an architecture—for the Italian restaurant.
For the restaurant, one imagines the need for cooks, waiters, cashiers, and presumably, customers. (We will not worry about tables, chairs, pots, and pans, etc.,
for the moment.) A diagram of the components, responsibilities, and collaborations
appears in Figure 5.
Based on the diagram, we speculate that a WAITER initiates activity by sending a
giveOrder message to the CUSTOMER (that is, the WAITER asks the CUSTOMER
what she wants to eat). The CUSTOMER’s reply causes the the WAITER to send a
message to the COOK to cook the appropriate dish, e.g., cookLasagna. In response,
the COOK cooks the order, which is returned to the WAITER, who gives the food
to the CUSTOMER.
Begin Footnote: An arrow, such as WAITER --> COOK, states that the WAITER
can send a message to the COOK. The COOK is allowed to “reply” to the message
by answering with food. The point is, the COOK does not initiate messages to the
WAITER; it merely replies to the messages the WAITER sends it. End Footnote
When the CUSTOMER is finished, she sends a calculateCheck message to the
WAITER to receive the check, and a receiveMoney message is sent to the CASHIER
to pay the bill.
Computer programs can be designed this way as well, and it is easy to imagine
how a computer game might be designed—consider an “Italian-restaurant computer
game”—but even mundane information processing programs can be designed with
17
1.7. BEYOND THE BASICS
Figure 1.6: model part of a more realistic word processor
Document
getText
deleteText
reformatLines
...
getHighlightedText
contentsOf
Clipboard
saveClippedText
retrieveClippedText
Formatter
setTypeFont
getTypeFont
setLineLength
getLineLength
object-oriented design. For example, the MODEL portion of the word-processor
architecture in Figure 4 is simplistic and can be replaced by one developed with
object-oriented design; see Figure 6.
The Figure presents the model as consisting of a DOCUMENT component, which
has the responsibility of holding the typed text and managing insertions and deletions. To help it with its responsibilities, the DOCUMENT sends messages to a
FORMATTER, which manages font and line-length information. A third component,
the CLIPBOARD, handles cut-and-paste actions. It takes only a little imagination
to see that the DOCUMENT, FORMATTER, and CLIPBOARD are behaving much
like the CUSTOMER, WAITER, and COOK seen earlier—actors communicating and
interacting to accomplish a task.
In this manner, we can employ object-oriented design to creatively organize a
program into distinct components with distinct responsibilities.
Exercises
Use object-oriented design to model the following:
1. a hamburger stand
2. a grocery store
3. an airport
4. a computer program that calculates spreadsheets
1.7.3
Classes Generate Objects
The class diagram in Figure 5 presented an architecture for an Italian restaurant.
If we constructed a real restaurant from the diagram, however, we would certainly
employ more than one waiter and one cook and we would certainly hope for more
than one customer. From the designer’s perspective, multiple waiter “objects” can
18
be “constructed” from just the one class of waiter. If we review Figure 5, we note
that the four classes (WAITER, COOK, CASHIER, and CUSTOMER) can be used
to create a real restaurant with many customer objects, waiter objects, cook objects,
and cashier objects. But regardless of the number of objects built, the responsibilities
and collaboration patterns remain as indicated in the class diagram.
In addition, once we define a class, we can reuse it. For example, if yesterday we
designed a vegetable market, and the vegetable market used cashiers, then the cashier
class that we designed for the vegetable market can be reused to “build” cashiers for
the Italian restaurant project.
We use the above analogy to emphasize that classes generate objects: When we
design a Java program, we draw a class diagram that indicates the classes and their
collaborations. Next, for each class in the diagram, we write a file of Java instructions
that code the methods of the class. When the program is started, one or more objects
are created from each of the classes, and the objects are placed in primary storage.
The processor executes the instructions within the objects.
1.7.4
Frameworks and Inheritance
If we find ourselves designing lots of markets and restaurants, we might develop a set
of classes and a basic architecture that can be used, with minor variations, over and
over again. Such a collection is called a framework.
Frameworks often depend on inheritance to do their job. Inheritance lets us add
minor customizations to a class. For example, our design of a basic CASHIER might
need some customization to fit perfectly into the Italian restaurant, say, perhaps
the cashier must speak Italian. We customize the CASHIER class by writing the
new method, speakItalian, and inventing new name, say, ITALIAN CASHIER, for
the customized class that possesses all the methods of the CASHIER plus the new
method. The situation we have in mind is drawn like this in class-diagram style:
Italian Cashier
speakItalian
Cashier
receiveMoney
The ITALIAN CASHIER inherits (or “extends”) the CASHIER class, because it
has all the methods of the original plus the additional customizations. The large
arrowhead in the diagram denotes this inheritance.
The attractive feature of inheritance is that the customizations are not inserted
into the original class but are attached as extensions. Thus, several people can use
the one and only original class but extend it in distinct ways. (For example, the basic
CASHIER class might be extended into an Italian restaurant cashier and extended
also into a bank cashier. For that matter, a restaurant might be built to have one
CASHIER object (who does not speak Italian) and one ITALIAN CASHIER object
(who does)).
1.7. BEYOND THE BASICS
19
We employ inheritance in Chapter 4 to generate graphics windows.
Another standard way of explaining inheritance is in terms of the “is-a” relationship, e.g., “An Italian-restaurant cashier is a cashier who can speak Italian.” Such
examples of inheritance abound in zoology, and here is one informal example:
• A mammal is an animal that is warm-blooded.
• A feline is a mammal that is cat-like.
• A tiger is a feline that has stripes.
• A lion is a feline that has a mane.
• A giraffe is a mammal that has an extremely long neck.
If we must write a simulation of a jungle, it makes sense to design one basic animal
class, design a mammal class that inherits it, design a feline class that inherits it, etc.
This gives us a coherent and economical design of the jungle animals. Also, if we
wish to add birds, zebras, and other new animals later, we can easily integrate the
new classes into the design.
Chapter 2
Simple Java Applications
2.1 An Application and its Architecture
2.2 How to Build and Execute an Application
2.2.1 Using an IDE
2.2.2 Using the JDK
2.3 How the Application Works
2.3.1 An Execution Trace of the Application
2.4 How One Object Constructs Another
2.5 Repairing Compiler Error Messages
2.6 Summary
2.7 Programming Exercises
2.8 Beyond the Basics
This chapter applies concepts from Chapter 1 to building simple programs in the
Java language. Its objectives are to
• present the standard format of a Java program and illustrate the instances of
class, method, and object, as they appear in Java.
• explain the steps one takes to type a program, check its spelling and grammar,
and execute it
• show how an object can send messages to other objects and even create other
objects as it executes.
2.1
An Application and its Architecture
If you were asked to built a robot, what would you do? First, you would draw a picture
of the robot; next you would write the detailed instructions for its assembly. Finally,
you would use the detailed instructions to build the physical robot. The final product,
a robot “object,” has buttons on its chest, that when pushed, cause the robot to talk,
walk, sit, and so forth. The buttons trigger the robot’s “methods”—the activities the
robot can perform.
2.1. AN APPLICATION AND ITS ARCHITECTURE
21
Figure 2.1: architecture of initial application
Hello
main
System.out
println
[Note: what we will write]
[Note: Java’s name for the command window]
As we learned in the previous chapter, program construction follows the same
methodology: We draw an initial design, the class diagram, and for each component
(class) in the diagram, we write detailed instructions in Java, which are saved in a file
in secondary storage. When we start the program, the class is copied into primary
storage and becomes an executing object.
The Java designers use the term application to describe a program that is started
by a human user. After an application’s object is constructed in primary storage, a
message is automatically sent to the application’s main method, causing its instructions to execute. Therefore, every Java application must possess a method named
main.
Begin Footnote: Not surprisingly, the precise explanation of how an application
starts execution is more complex than what is stated here, but we nonetheless stick
with our slogan that “classes are copied into primary storage and become executing
objects.” End Footnote
To illustrate these ideas, we construct a small Java application that contains just
the one method, main, which makes these two lines of text appear in the command
window:
Hello to you!
49
To make the text appear, our application sends messages to a pre-existing object,
named System.out, which is Java’s name for the command window. The application
we build is named Hello; it interacts with System.out in the pattern portrayed in the
class diagram of Figure 1.
The class diagram shows that Hello has a main method; a message from the
“outside world” to main starts the application. The other component, System.out,
has its name underlined to indicate that it is a pre-existing object—a Java object that
is already connected to the command window. The object has a method, println
(read this as “printline”), which knows how to display a line of text in the command
window. The Java designers have ensured that a System.out object is always ready
and waiting to communicate with the applications you write.
22
The arrow in the diagram indicates that Hello sends messages to System.out.
That is, it uses System.out by communicating with (or “connecting to”) its println
method.
Using terminology from Chapter 1, we say that Figure 1 presents an architecture
where Hello is the controller (it controls what the program does) and System.out is
the output view (it displays the “view” of the program to the human who uses it).
We write and place the instructions for class Hello in a file named Hello.java.
Java instructions look like “technical English,” and the contents of Hello.java will
look something like this:
public class Hello
{ public static void main(String[] args)
{
... to be supplied momentarily ...
}
}
Java is a wordy programming language, and we must tolerate distracting words like
public, static, void, which will be explained in due course. For now, keep in mind
that we are writing a class that has the name Hello and contains a method named
main. The set braces act as “punctuation,” showing exactly where a method and a
class begin and end. The ellipses indicate where the instructions will be that send
messages to System.out.
For our example, main must contain instructions that display two full lines of text.
The algorithm for this task goes:
1. Send a message to System.out to print ”Hello to you!” on a line of its own in
the command window.
2. Send a message to System.out to print the number 49 on a line of its own in
the command window.
We must convert the above algorithm into Java instructions and insert them where
the ellipses appeared above. Step 1 of the algorithm is written like this in Java:
System.out.println("Hello to you!");
This instruction sends to System.out the message to print-line (println) the text,
"Hello to you!". In similar fashion, Step 2 is written
System.out.println(49);
The technical details behind these two instructions will be presented momentarily;
for now, see Figure 2 for the completely assembled program.
Before we dissect Figure 2 word by word, we demonstrate first how one types the
program into the computer, checks the program’s spelling and grammar, and starts
it.
2.2. HOW TO BUILD AND EXECUTE AN APPLICATION
23
Figure 2.2: sample application
/** Hello prints two lines in the command window */
public class Hello
{ public static void main(String[] args)
{ System.out.println("Hello to you!");
System.out.println(49);
}
}
2.2
How to Build and Execute an Application
We take several steps to make the application in Figure 2 print its text in the command
window:
1. class Hello must be typed and saved in the file, Hello.java.
2. The program’s spelling and grammar must be checked by the Java compiler;
that is, the program must be compiled.
3. The program must be started (executed).
To perform these steps, you use either your computer’s (i) integrated development
environment (IDE) for the Java language, or (ii) text editor and the Java Development
Kit (JDK).
We briefly examine both options, and you should obtain help with selecting the
one you will use. If you are not interested in this selection at this moment in time,
you may skip either or both of the two subsections that follow.
2.2.1
Using an IDE
There are many IDEs available for Java, and we cannot consider them all. Fortunately,
IDEs operate similarly, so we present a hypothetical example. Be certain to read the
manual for your IDE before you attempt the following experiment.
When an IDE is started, it will present a window into which you can type a Java
application (or “project,” as the IDE might call it). Select the IDE’s menu item or
button named New Project to create a new project, and when the IDE asks, type
the name of the class you wish to write. For the example in Figure 2, use the name,
24
Hello, for the project:
2.2. HOW TO BUILD AND EXECUTE AN APPLICATION
25
Next, type the class into the window and save it by using the IDE’s Save button:
If done properly, class Hello will be saved in a file named Hello.java.
Next, the program must be compiled. Compile by selecting the IDE’s Compile or
26
Build button:
If there are any spelling or grammar errors, they will be listed in a small window
of their own; otherwise, a message will announce that the compile has completed
successfully. In the former case, you repair the errors and try again; in the latter case,
you will see that the compiler has created the translated version of the application
and placed it in the file, Hello.class.
Finally, start the program by selecting the button named Run or Launch (or Start,
etc.) This starts the Java interpreter, which helps the processor read and execute
2.2. HOW TO BUILD AND EXECUTE AN APPLICATION
27
the byte-code instructions in Hello.class. In the case of the Hello application, its
execution causes two full lines of text to appear in the command window. The IDE
shows the command window when needed:
(Footnote: Unfortunately, your IDE might show and remove the command window
before you can read the text that appeared! If the command window disappears
too quickly, insert these lines into your Java program immediately after the last
28
System.out.println:
try { Thread.sleep(5000); }
catch (InterruptedException e) { }
These cryptic extra lines delay the program by 5 seconds, giving you time to read the
contents of the command window. EndFootnote.)
2.2.2
Using the JDK
An alternative to an IDE is a text editor and the Java Development Kit (JDK). Your
computer should already have a text editor (e.g., Notepad or emacs or vi) that you
can use to type and save files. The JDK can be obtained from Sun Microsystems’s
Web Site at http://java.sun.com. You must download the JDK and properly install
it on your computer; installation is not covered here, but instructions come with the
JDK.
Once you have the JDK installed, the first step is to use a text editor to create
the file, Hello.java, with the contents of Figure 2:
2.2. HOW TO BUILD AND EXECUTE AN APPLICATION
29
Next, compile the application by typing in a command window, javac Hello.java:
This starts the Java compiler, which examines the application, line by line, attempting
to translate it into byte-code language. If the program is badly formed—there are
spelling or grammar or punctuation errors—then the compiler will list these errors.
If there are errors, correct them with the text editor and compile again; if not, then
you will see that the Java compiler created the translated version of the application,
a byte-code file named Hello.class.
To execute the program, type the command, java Hello. This starts the program,
more specifically, it starts the Java interpreter that helps the processor execute the
30
byte-code instructions, and the two lines of text appear in the command window:
Details about error messages and error correction appear in a section later in this
Chapter.
Exercise
Install either an IDE or the JDK on your computer and type, compile, and execute
the program in Figure 2.
2.3
How the Application Works
We now scrutinize Figure 2 line by line. The class’s first line,
/** Hello
prints two lines in the command window */
is a comment. A comment is not a Java instruction for the computer to execute—it
is a sentence inserted, as an aside, for a human to read, in the case that the human
wants to inspect the application before it is compiled and executed. The comment is
meant to explain the purpose of class Hello. A comment can extend over multiple
lines, as we will see in later examples.
You should begin every class you write with a comment that explains the class’s
purpose. Java programs are not all that easy for humans to read, and a few lines of
explanation can prove very helpful!
2.3. HOW THE APPLICATION WORKS
31
Begin Footnote: By the way, it confuses the Java compiler if you place one comment inside another, e.g.
/** A badly formed
/** comment */
looks like this. */
so do not do this! Also, the Java compiler will accept comments that begin with /* as
well as /**, but we use the latter for reasons explained in Chapter 5. End Footnote.
Following the comment is the line that gives the class’s name, and then there are
two matching brackets:
public class Hello
{
...
}
The line with the program’s name is the program’s header line. Hello is of course the
name, but the words public and class have special, specific meanings in Java.
Words with special, specific meanings are called keywords. The keyword, public,
indicates that the class can be used by the “general public,” which includes other
objects and human users, to build objects on demand. The keyword, class, indicates
of course that Hello is a Java class, the basic unit of program construction.
Following the program’s header line is the program’s body, which is enclosed by
the matching brackets, { and }. Within the body lie the program’s methods (as well
as other items that we encounter in later chapters).
In the examples in this text, we will align the brackets vertically so that they are
easy to see and match. The only exception to this rule will be when the brackets
enclose a mere one-line body; then, the closing bracket will be placed at the end of
the same line, to save space on the printed page. (See Figure 2 again.) When you
type your own Java programs, you might prefer to use this style of bracket placement:
public class Hello {
...
}
because it is easier to use with a text editor. (But this style makes it easy to forget a
bracket, so be careful.) Do as your instructor indicates; we will not discuss this issue
again.
Back to the example—Hello’s body has but one method, main, which has its own
header line and body:
public static void main(String[] args)
{
...
}
32
Method main is surrounded by a slew of keywords! The word, public, means the same
as before—the main method may receive messages from the general “public.” (This
includes the user who starts Hello and wants main to execute!) The keyword, static,
refers to a subtle, technical point, and its explanation, along with that of void and
(String[] args), must be postponed.
The body of the main method contains the instructions (statements) that will be
executed when a Hello object is sent the message to execute its main method. These
instructions are
System.out.println("Hello to you!");
System.out.println(49);
Each of the statements has the form, System.out.println(SOMETHING TO DISPLAY);
this statement form sends a message to the System.out object, and the message is
println(SOMETHING TO DISPLAY). The SOMETHING TO DISPLAY-part is the argument part
of the message. The statement must be terminated by a semicolon. (Semicolons in
Java are used much like periods in English are used.)
This message asks System.out to execute its println method. The instructions
inside the println method display the argument, SOMETHING TO DISPLAY, at the position of the cursor in the command window. And, println inserts a newline character
immediately after the displayed argument—this makes the cursor in the command
window move to the start of a fresh line.
As we learn in the next chapter, the Java language requires that textual phrases
must be enclosed within double quotes, e.g., "Hello to you!". Textual phrases are
called strings. The double quotes that surround strings prevent confusing strings with
keywords. For example, "public class" is clearly a string and not two keywords side
by side. In contrast, numbers like 49 are not strings and are not enclosed by double
quotes.
Begin Footnote: But note that "49" is a string, because of the double quotes. The
distinction between 49 and "49" becomes clear in the next chapter. End Footnote
Method println serves as System.out’s “communication” or “connection” point,
and the Hello object makes two such communications with System.out by using
println, not unlike contacting a friend twice on the telephone.
Here is a final point, about spelling: The Java language distinguishes between
upper-case and lower-case letters. Therefore, public is spelled differently from PUBLIC
and Public, and only the first is accepted as the correct Java keyword. It is traditional
to spell the names of classes beginning with a single upper-case letter, e.g., Hello, and
method names tranditionally begin with a lower-case letter, e.g., println. Additional
spelling guidelines are introduced in the next chapter.
Exercises
1. Write a Java program that displays your name on one line.
33
2.3. HOW THE APPLICATION WORKS
2. Write a Java program that prints your name, where your first (given) name is
on a line by itself, and you last (family) name is on a line by itself.
3. For both of the previous exercises, explain why your program has the architecture presented in Figure 1. (This makes clear that different programs can be
built in the same architectural style.)
2.3.1
An Execution Trace of the Application
When we demonstrated the Hello application, we saw the lines, Hello to you! and
49 appear in the command window, but we did not see how the computer executed the
application’s instructions to produce these results. To be a competent programmer,
you must develop mental images of how the computer executes programs. To help
develop this skill, we study a step-by-step pictorial re-creation—an execution trace—of
the computer’s actions.
When the Hello application is started by a user, the file, Hello.class, is copied
into primary storage and becomes a Hello object.
Begin Footnote: The preceding explanation is imprecise, because it fails to explain
that the “object” created from Hello.class is not the usual form of object described
in Chapter 1. But it is not worth the pain at this point to state the distinction
between a class that possesses an invoked static main method and one that possesses
an invoked non-static constructor method. Our situation is similar to the one in an
introductory physics course, where Newtonian physics is learned initially because it
is simple and beautiful although slightly incorrect. In subsequent chapters, these
technical issues are resolved. End Footnote
Here is a depiction of the Hello object (and System.out, which already exists) in
primary storage:
Hello
public static void main(String[] args)
{ System.out.println("Hello to you!");
System.out.println(49);
}
System.out
...
println(...)
{ instructions to print text }
Objects rest in primary storage and wait for messages. So, to start the computation, the computer (more precisely, the JVM) sends Hello a message to start its main
method.
When Hello receives the message, it indeed starts main, whose statements are
executed, one by one. We use a marker, >, to indicate which statement is executed
first:
Hello
public static void main(String[] args)
{ > System.out.println("Hello to you!");
System.out.println(49);
}
System.out
...
println(...)
{ instructions to print text }
34
The statement, System.out.println("Hello to you!"), sends the message, println("Hello
to you!"), to the System.out object. The argument, "Hello to you!", is what the
method will print. (The enclosing double quotes are not printed, but you must include them, nonetheless.) This is an example of how one object sends a message to
another object, awakening it from its rest.
The message is delivered to System.out, which puts its println method to work.
In the interim, the Hello object waits:
Hello
public static void main(String[] args)
{ > AWAIT System.out’s COMPLETION
System.out.println(49);
}
System.out
...
println(...)
{ > instructions to print text }
Eventually, System.out fulfills its request and the text, Hello to you! appears in the
command window. Once this happens, System.out signals Hello that it can proceed
to its next statement, and System.out returns to a “resting” state:
Hello
public static void main(String[] args)
{ ...
> System.out.println(49);
}
System.out
...
println(...)
{ instructions to print text }
Next, another message is sent to System.out, requesting that its println method display the number 49. (Notice that double-quote marks are not used around numbers.)
Again, Hello waits until System.out executes its message and signals completion:
Hello
public static void main(String[] args)
{ ...
> AWAIT System.out’s COMPLETION
}
System.out
...
println(...)
{ > instructions to print text }
Once println finishes, there is nothing more to execute in main’s body,
Hello
public static void main(String[] args)
{ ...
...
> }
System.out
...
println(...)
{ instructions to print text }
and Hello signals that it is finished.
Exercise
Write an execution trace for a program you wrote from the earlier Exercise set.
2.4. HOW ONE OBJECT CONSTRUCTS ANOTHER
35
Figure 2.3: class diagram of an application that displays the time
NameAndDate
main
GregorianCalendar
getTime
[Note: connects to the
computer’s internal clock]
System.out
println
print
2.4
How One Object Constructs Another
The Hello example showed how an object can send messages to the preexisting object,
System.out. But it is also possible for an object to construct its own, “helper,” objects
as needed and send them messages. We see this in an example.
Say that you want an application that prints your name and the exact date and
time, all on one line, in the command window. Of course, the date and time are dependent on when the program starts, and you cannot guess this in advance. Fortunately,
the Java designers wrote a class, named class GregorianCalendar, that knows how
to read the computer’s internal clock. So, we construct an object that itself constructs
a GregorianCalendar object and sends the GregorianCalendar object a message for
the exact date and time. Figure 3 shows the architecture of the application we plan
to build. The class we write is called NameAndDate. It will send a getTime message
to the GregorianCalendar and then it will send messages to System.out to print the
time. (The new method, print, of System.out, is explained below.)
When this program is executed, we will see in the command window something
like this:
Fred Mertz --- Fri Aug 13 19:07:42 CDT 2010
Finished
That is, the programmer’s name, Fred Mertz, is printed followed by the exact moment
that the program executes. Then an empty line and a line consisting of just Finished
appears.
Figure 3 has an interesting architecture: NameAndDate is the controller, because
it controls what will happen; System.out is the output view, because it presents the
“view” of the program to its user; and GregorianCalendar is the model, because it
“models” the computer clock—this is our first, simplistic example of a model-viewcontroller (MVC) architecture—see Chapter 1.
We must write the controller, class NameAndDate. The controller’s main method
asks System.out to print the text in the command window, but main must also ask
36
a GregorianCalendar object for the exact time. To do this, main must construct the
object first before it sends it a message. Here are the crucial steps main must do:
• Construct a new GregorianCalendar object, by saying
new GregorianCalendar()
The keyword, new, constructs an object from the class named GregorianCalendar.
The matching parentheses, ( and ), can hold extra arguments if necessary; here,
extra arguments are unneeded to construct the object.
• Give the newly created object a variable name so that we can send messages to
the object:
GregorianCalendar c = new GregorianCalendar();
Here, the name is c. (The extra word, GregorianCalendar, is explained later.)
We can use the name c like we used the name, System.out—we can send messages to the named object.
• Send object c a getTime message that asks it to consult the clock and reply
with the date and time:
c.getTime()
• Tell System.out to display this date and time:
System.out.println(c.getTime());
Recall that the statement, System.out.println(ARGUMENT), prints the ARGUMENT.
Here, we insert, c.getTime(), which asks c to reply with the time as a response—
it is the time received in response that is printed.
Figure 4 shows the program that uses the above steps. In addition to what we
have just studied, several other new techniques are illustrated in the example. We
explain them one by one.
To understand the first line,
import java.util.*;
we must provide some background: The Java language comes with many prewritten
classes for your use. The classes are grouped into packages, and the package where one
finds class GregorianCalendar is java.util. The statement, import java.util.*,
tells the Java interpreter to search in the java.util package, where it will locate
class GregorianCalendar. Once the class is located, an object may be constructed
2.4. HOW ONE OBJECT CONSTRUCTS ANOTHER
37
Figure 2.4: application that prints the date and time
import java.util.*;
/** NameAndDate prints my name and the exact date and time. */
public class NameAndDate
{ public static void main(String[] args)
{ System.out.print("Fred Mertz --- ");
// The next statement creates an object:
GregorianCalendar c = new GregorianCalendar();
System.out.println(c.getTime()); // ask c the time and print its reply
System.out.println();
System.out.println("Finished");
}
}
from it. It will always be clear in this text’s examples when a special statement like
import java.util.* is necessary.
Begin Footnote: Stated more precisely, when a class, C, is mentioned in a statement, the Java interpreter must locate the file, C.class. Normally, the interpreter
searches in two places: (1) the folder in which the application was started, and (2)
java.lang, a package of general-purpose classes. If C.class does not reside in either
of these two places, an import statement must indicate where else the interpreter
should search. End Footnote
The first statement within main says
System.out.print("Fred Mertz --- ");
Method print is another method that belongs to System.out. When executed,
print(ARGUMENT) displays the ARGUMENT at the position of the cursor in the command window, but no newline character is appended immediately after the displayed
argument—this retains the command window’s cursor on the same line as the displayed argument so that additional information can be printed on the line.
The line,
// The next statement creates an object:
is an internal comment. An internal comment is a comment inserted inside a class,
providing technical information to the person who is studying the program. An
internal comment extends only from the double slashes until the end of the line on
which it appears. In this text, we use internal comments to explain the workings of
important or subtle statements.
The statement,
GregorianCalendar c = new GregorianCalendar();
38
was explained above: a new object is constructed from class GregorianCalendar and is
named c. the reason for the apparently redundant leading word, GregorianCalendar,
is best explained in the next chapter; for the moment, we note that the leading word
states that c is a “type” of name only for GregorianCalendar objects.
Variable names are developed fully in the next chapter, and our use of one to
name for the GregorianCalendar object is merely a “preview.”
The statement,
System.out.println(c.getTime());
has been explained; the date that is computed and returned by the GregorianCalendar
object becomes the argument to the println message, and therefore it is the date (and
not the message itself) that is displayed in the command window. We see this behavior
explained in the execution trace that follows.
Finally,
System.out.println()
prints an argument that is nothing, to which is appended a newline character. The
net effect is that the cursor in the command window is moved to the beginning of a
fresh line. (The parentheses, (), hold no information to print.)
Execution Trace
Because this example is an important one, we study its execution trace. The instant
after the NameAndDate application is started, primary storage looks like this:
NameAndDate
System.out
main
{ > System.out.print("Fred Mertz --- ");
GregorianCalendar c = new GregorianCalendar();
System.out.println(c.getTime());
System.out.println();
System.out.println("Finished");
}
print(...)
{ instructions to print text }
println(...)
{ instructions to terminate text }
Once the text, Fred Mertz ---, is printed, the interpreter begins the next statement, which creates a new GregorianCalendar object—a segment of primary storage
is allocated to hold the contents of a GregorianCalendar object, as described by class
GregorianCalendar.
Begin Footnote: More specifically, the file, GregorianCalendar.class is used to
construct the object in storage. End Footnote
39
2.4. HOW ONE OBJECT CONSTRUCTS ANOTHER
We see the following:
NameAndDate
main
{ ... // the test, "FredMertz --- " has been displayed
> GregorianCalendar c = new GregorianCalendar();
System.out.println(c.getTime());
System.out.println();
System.out.println("Finished");
}
System.out
...
as before ...
a1 : a GregorianCalendar object
...
getTime()
{ a method that reads the clock and returns the time }
The new object has an internal address where it is found. Here, the address of the
newly constructed GregorianCalendar object is a1. The object at address a1 has its
own internal structure, including a method named getTime.
Next, a storage space (called a cell) is created, and the address is placed into the
cell. In this way, c is made to name the object at address a1:
NameAndDate
main
{ ...
// the test, "FredMertz --- " has been displayed
System.out
...
as before ...
GregorianCalendar c == a1
> System.out.println(c.getTime());
System.out.println();
System.out.println("Finished");
}
a1 : a GregorianCalendar object
...
getTime()
{ a method that reads the clock and returns the time }
Now, a println message to System.out must be sent. But the message’s argument
is not yet determined. For this reason, the interpreter sends a getTime() message to
c, and since c names a cell that holds address a1, the object at address a1 is sent the
40
message to execute the instructions in its getTime method:
NameAndDate
main
{ ...
// the test, "FredMertz --- " has been displayed
System.out
...
GregorianCalendar c == a1
> System.out.println( AWAIT THE REPLY FROM a1 );
System.out.println();
System.out.println("Finished");
}
a1 : a GregorianCalendar object
...
getTime()
{ > a method that reads the clock and returns the time }
The main method waits while a1’s getTime method executes.
Once getTime gets the date and time from the computer’s clock, it returns the
date and time to the exact position from which the message was sent:
NameAndDate
main
{ ...
// the test, "FredMertz --- " has been displayed
System.out
...
GregorianCalendar c == a1
> System.out.println("Fri Aug 13 19:07:42 CDT 2010" );
System.out.println();
System.out.println("Finished");
}
a1 : a GregorianCalendar object
...
getTime()
{ a method that reads the clock and returns the time }
After the time is placed in the position where it was requested, it becomes the
argument part of the println message that is sent to System.out. This is a pattern
that appears often in Java statements: When part of a statement is written within
parentheses, the parenthesized part executes first, and the result, if any, is deposited
in the parentheses.
System.out displays the date and time, and execution proceeds to the last two
statements in main.
Finally, we note that only one message is sent to the GregorianCalendar object
that is constructed by the application. In the case that an object is constructed and
just one message is sent to it immediately thereafter, we need not give the object a
name. Here is the application rewritten so that the object’s name no longer appears:
import java.util.*;
/** NameAndDate prints my name and the exact date and time. */
2.5. REPAIRING COMPILER ERROR MESSAGES
41
public class NameAndDate
{ public static void main(String[] args)
{ System.out.print("Fred Mertz --- ");
// The next statement constructs an object and sends it a getTime message:
System.out.println(new GregorianCalendar().getTime());
System.out.println();
System.out.println("Finished");
}
}
The key statement,
System.out.println(new GregorianCalendar().getTime());
embeds within it the steps of (i) constructing a new GregorianCalendar object (ii)
immediately sending the object a getTime() message, and (iii) using the message’s
reply as the argument to System.out.println. This complex arrangement executes
correctly because the Java interpreter executes phrases within parentheses first.
Exercise
Revise Figure 3 so that it prints your own name with the date and time. Compile
and execute it. Execute it again. Compare the outputs from the two executions.
2.5
Repairing Compiler Error Messages
Humans can tolerate errors in spelling and grammar, but computers cannot, and
for this reason, the Java compiler complains if a program contains any spelling or
grammar errors. The compiler will identify the line on which an error appears and
give a short explanation.
For example, here is a program that contains several errors. (Take a moment to
locate them.)
public class test
{ public static main(String[] args)
{ System.out.println(Hello!)
}
Perhaps we save this program in Test.java and compile it. The compiler replies with
several error messages, the first of which is
Test.java:2: Invalid method declaration; return type required.
{ public static main(String[] args)
^
42
This message states there is an error in Line 2 of the program, at approximately the
position marked by the caret. The explanation of the error is not so helpful, but
its position is important because we can compare the above to a correctly written
program and notice that we forgot the keyword, void.
The compiler reports an error on the next line, also:
Test.java:3: ’)’ expected.
{ System.out.println(Hello!)
^
Again, the compiler’s message is not so helpful, but the position of the error suggests
there is something wrong with the word, Hello!—we forgot to enclose the string
within double quotes. Again, we can spot this error quickly by comparing our println
statement to one in the text that was correctly written.
There is also an error in the following line:
Test.java:4: ’}’ expected.
}
^
This time, the explanation is on target—we are indeed lacking a closing bracket.
Finally, the compiler reports an error at Line 1:
Test.java:1: Public class test must be defined in a file called "test.java".
public class test
^
The compiler’s message is correct—the name of the class must be spelled exactly the
same as the name of the file in which the class is saved. Therefore, we should change
the first line to read public class Test.
Once we repair the errors, we have this program:
public class Test
{ public static void main(String[] args)
{ System.out.println("Hello!") }
}
When we compile the revised program, the compiler identifies one error we missed:
Test.java:3: ’;’ expected.
{ System.out.println("Hello!") }
^
Indeed, we have forgotten to end the statement with a semicolon. Once we repair
this last error, the compiler will successfully compile our program. (Unfortunately, a
compiler is not perfect at detecting all grammar errors on a first reading, and it is
2.6. SUMMARY
43
common that an highly erroneous program must be compiled several times before all
its grammar errors are exposed.)
Although the Java compiler will methodically locate all the grammar errors in an
application, a programmer should not rely on it as a kind of “oracle” that announces
when an application is ready to be executed. Indeed, a programmer should be familiar
with the appearance (syntax) and meanings (semantics) of the statements in the Java
language. An introduction to these notions appears in the sections at the end of this
chapter, and Appendix I provides a thorough description of the syntax and semantics
for the subset of the Java language used in this text.
Finally, remember that the compiler’s role is to enforce spelling and grammar and
not to comment on the suitability of the statements in the program. For example,
the English sentence, “Turn left into the Atlantic Ocean, and drive on the ocean floor
until you reach the east coast of France”, is a grammatically correct but procedurally
and geographically dubious instruction for someone who wishes to travel from New
York City to Paris! In a similar way, a program can contain grammatically correct
but dubious statements.
For this reason, the Java compiler can not guarantee that a program will perform
the actions that its author intends. What the author intends lives in her brain; how she
converts these intentions into written instructions cannot be checked by the compiler!
For this reason, we must study design, testing, and validation techniques that help
a programmer correctly convert her intentions into programs. We encounter these
techniques in subsequent chapters.
2.6
Summary
We conclude the chapter with a summary of the new constructions, terms, and concepts.
New Constructions
• Java class (from Figure 2):
public class Hello
{
...
}
• main method (from Figure 2):
public static void main(String[] args)
{
...
}
44
• comment (from Figure 2):
/** Hello
prints two lines in the command window */
• internal comment (from Figure 4):
// The next statement creates an object:
• message-sending statement (from Figure 2):
System.out.println("Hello to you!");
• constructing a new object (from Figure 4):
new GregorianCalendar()
• variable name (from Figure 4):
GregorianCalendar c = new GregorianCalendar();
New Terminology
• application: a Java program that is started by a human; an object is constructed
in primary storage and a message is sent to the object to execute its main
method.
• statement: a single instruction within a Java program; typically terminated by
a semicolon.
• main method: the “start up” method that executes first when an application is
started.
• argument: extra information that is appended to a message sent to an object;
enclosed within parentheses following the method name within the message.
• comment: an explanation inserted into a program for a human (and not the
computer) to read.
• string: a sequence of characters, enclosed by double quotes, e.g., "hello".
• Java Development Kit (JDK): a collection of programs, available from Sun
Microsystems, that let a user compile and start a Java application from the
command window.
2.6. SUMMARY
45
• integrated development environment (IDE): a single program from which a use
can edit, compile, start, and monitor a Java application
• execution trace: a sequence of pictures of primary storage, showing the changes
to the objects as the computer executes statements one by one.
• address (of an object): an object’s “name” within primary storage; used to send
messages to it.
• variable name: a name of a cell where a value, such as the address of an object,
is saved. (See Chapter 3 for a fuller development.)
Additional Points to Remember
• A Java application must be compiled before it can be started. When a file,
C.java, is compiled, another file, C.class is created, and objects are constructed
from C.class when the user starts C. A message is sent to object C’s main
method.
• When an application is created in primary storage, there are already other
preexisting objects, such as System.out, to which messages can be sent.
• An object can create other objects by stating the keyword, new, followed by the
name of the class from which the object is to be constructed.
• When an object is created from a class that is located in a Java package, the
package’s name should be explicitly imported so that the Java interpreter can locate the class. For example, class GregorianCalendar is located in the package,
java.util, so we import the package to use the class:
import java.util.*;
public class ExampleOfImportation
{
... new GregorianCalendar() ...
}
New Constructions for Later Use
• System.out: the object that communicates with the command window. Methods:
– print(ARGUMENT): displays ARGUMENT in the command window at the position of the cursor
– println(ARGUMENT): displays ARGUMENT, appended to a newline character,
in the command window at the position of the cursor.
46
• class GregorianCalendar (a class from which one can construct objects that
read the computer’s clock). Found in the package, java.util. Method:
– getTime(): returns the current time
2.7
Programming Exercises
1. Write a Java program that displays your name and postal address on three or
more lines. Next, use a GregorianCalendar object to print the date and time
before your name.
2. Write a Java program that appears to do nothing at all.
3. Create two GregorianCalendar objects by inserting these two statements,
System.out.println( new GregorianCalendar().getTime() );
System.out.println( new GregorianCalendar().getTime() );
into a main method. What happens when you execute the program?
4. Modify the program from the previous exercise as follows: Insert between the
two statements this Java instruction:
try { Thread.sleep(5000); }
catch (InterruptedException e) { }
We will not dissect this complex statement—simply stated, it causes the main
method to delay for 5000 milliseconds (that is, 5 seconds). Compile and execute
the program. What happens?
5. Write the smallest Java program (fewest number of characters) that you can.
Is there a largest possible (most number of characters) program?
6. Write a Java program that draws this picture on the display:
/\
/ \
---| - |
| | ||
2.8. BEYOND THE BASICS
2.8
47
Beyond the Basics
2.8.1 Syntax
2.8.2 Semantics
2.8.3 Java Packages
2.8.4 Java API
Here is optional, supplemental material that will bolster your understanding of the
concepts in this chapter.
2.8.1
Syntax
When we discuss the appearance of a program’s statements—spelling, use of blanks,
placement of punctuation—we are discussing the program’s syntax. The Java compiler
strictly enforces correct syntax, and we have no choice but to learn the syntax rules
it enforces.
Appendix I gives a precise description of the syntax for the subset of the Java
language we use in this text. The description is precise, detailed, and a bit tedious.
Nonetheless, it is important that we learn to read such definitions, because they tell
us precisely what grammatically correct Java programs look like.
For exercise, we present here the part of the syntax definition that matches the
examples seen in this chapter. (Note: because they are developed more fully in the
next chapter, we omit the presentation of variable names here.)
Class
A Java class has this format:
CLASS ::=
public class IDENTIFIER { METHOD* }
The above is an equation, called a syntax rule or a BNF rule. Read the equation,
CLASS ::= ... as stating, “a well-formed CLASS has the format ...”.
We see that a well-formed CLASS begins with the keywords, public and class,
followed by an entity called an IDENTIFIER, which we describe later. (The IDENTIFIER
is the class’s name, e.g., NameAndDate in Figure 4. For now, think of an IDENTIFIER
as a single word.)
Following the class’s name is a left bracket, {, then zero or more entities called
METHODs, defined momentarily. (The * should be read as “zero or more.”) A class is
concluded with the right bracket, }.
The classes in Figures 2 and 4 fit this format—both classes hold one method, main.
48
Method
A method, like the main method, can have this format:
METHOD ::=
public static void METHOD_HEADER
METHOD_BODY
That is, following the words, public static void, a METHOD HEADER (the method’s
name) and METHOD BODY (its body) appear.
METHOD_HEADER ::=
IDENTIFIER ( FORMALPARAM_LIST? )
The METHOD HEADER has a format consisting of its name (e.g., main), followed by
a left bracket, followed by an optional FORMALPARAM LIST. (The ? means that the
FORMALPARAM LIST can be absent.) Then there is a right bracket.
In this chapter, the FORMALPARAM LIST was always String[] args, but we study
other forms in a later chapter.
METHOD_BODY ::=
{ STATEMENT* }
The body of a method is a sequence of zero or more STATEMENTs enclosed by brackets.
Statement
There are several statement forms in Java; the form we used in Chapter 2 is the
message-sending statement, called an INVOCATION. The format looks like this:
STATEMENT ::=
INVOCATION ::=
INVOCATION ;
RECEIVER . IDENTIFIER ( ARGUMENT_LIST? )
That is, an INVOCATION has a format that first lists the RECEIVER, which is the name
of an object (e.g., System.out). A period follows, then comes an IDENTIFIER, which
is the method name (e.g., print). Finally, there are matching brackets around an
optional ARGUMENT LIST, defined later.
The syntax rule for well-formed RECEIVERs is instructive:
RECEIVER ::= IDENTIFIER
| RECEIVER . IDENTIFIER
| OBJECT_CONSTRUCTION
We read the vertical bar, |, as “or.” That is, there are three possible ways of writing
a RECEIVER: The first is just a single IDENTIFIER, e.g., System is a RECEIVER; the second
way is an existing receiver followed by a period and an identifier, e.g., System.out.
The third way is by writing an OBJECT CONSTRUCTION, e.g., new GregorianCalendar;
see below.
Notice how the recursive (self-referential) definition of RECEIVER gives a terse and
elegant way to state precisely that a RECEIVER can be a sequence of IDENTIFIERs
separated by periods.
2.8. BEYOND THE BASICS
49
Object Construction
OBJECT_CONSTRUCTION ::=
new IDENTIFIER ( ARGUMENT_LIST? )
An OBJECT CONSTRUCTION begins with the keyword, new, followed by an IDENTIFIER
that is the name of a class. Brackets enclose an optional ARGUMENT LIST.
Messages to objects and newly constructed objects can use ARGUMENT LISTs:
ARGUMENT_LIST ::= EXPRESSION [[ , EXPRESSION ]]*
EXPRESSION ::= LITERAL | INVOCATION
An ARGUMENT LIST is a sequence of one or more EXPRESSIONs, separated by commas.
(The double brackets, [[ and ]], indicate that the * groups both the comma and the
EXPRESSION.
For example, say that "hello" and 49 are both well-formed LITERALs. Then,
both are well-formed EXPRESSIONs, and this means "hello", 49 is a well-formed
ARGUMENT LIST
All the examples in this chapter used ARGUMENT LISTs that consisted of zero (e.g.,
System.out.println()) or just one (e.g., System.out.println(new GregorianCalendar().
getTime())) EXPRESSIONs.
Within Chapter 2, an EXPRESSION was either a LITERAL, like "hello", or an
INVOCATION (new GregorianCalendar().getTime()).
Literal and Identifier
These constructions will be developed more carefully in the next chapter; for the moment, we state that a LITERAL consists of numbers, like 12 and 49, and strings, which
are letters, numerals, and punctuation enclosed by double quotes. The IDENTIFIERs
used in Chapter 2 were sequences of upper- and lower-case letters.
2.8.2
Semantics
The syntax rules in the previous section tell us nothing about what a Java application
means (that is, what the application does). When we discuss a program’s meaning,
we are discussing its semantics. From the examples in this chapter, we learned the
informal semantics of a number of Java constructions. As a exercise, we repeat this
knowledge here. The subsections that follow are organized to match the similarly
named subsections of syntax rules in the previous section, and by reading both in
parallel, you can gain a systematic understanding of the syntax and semantics of the
Java constructions used in this Chapter.
50
Class
Given a class, public class IDENTIFIER { METHOD* }, a user starts the class as an
application by typing a class’s name, that is, the IDENTIFIER part. The file with the
name IDENTIFIER.class is located, and an object is constructed by copying the file
into primary storage.
Begin Footnote: As noted earlier in the Chapter, this explanation deliberately
avoids technicalities regarding invocation of so-called static methods of classes. The
issue will be handled later. End Footnote
A message is sent to the newly constructed object’s main METHOD.
Method
When an object receives a message, it must identify which method is requested by
the message. The object extracts from the message the method name, IDENTIFIER,
and it locates the METHOD whose METHOD HEADER mentions the same IDENTIFIER:
public static void METHOD_HEADER METHOD_BODY
(At this time, we cannot discuss the use of the FORMALPARAM LIST and we skip this.
In a later chapter, we learn that the ARGUMENT LIST information that is attached to a
message “connects” or “binds” to the FORMALPARAM LIST.)
The statements in the METHOD BODY are executed, one by one, in order. It is possible
for the last statement in the METHOD BODY to “reply” with an “answer” to the message.
We study this behavior in a later chapter.
Statement
An invocation, RECEIVER . IDENTIFIER ( ARGUMENT LIST? ), sends a message to the
object named RECEIVER, telling it to execute its method named IDENTIFIER. The
optional ARGUMENT LIST states additional details that help the method do its job. If
the RECEIVER’s method, IDENTIFIER, is equipped to “reply” with an “answer,” then
the answer is inserted at the very position in the program where the message was
sent.
Object Construction
The phrase, new IDENTIFIER ( ARGUMENT LIST? ), constructs an object in primary
storage from the file, IDENTIFIER.class. The optional ARGUMENT LIST lists information
that aids in the construction. The construction step generates an internal “address”
that names the newly constructed object, and this address is treated as a “reply”
similar to that described in the previous subsection.
2.8. BEYOND THE BASICS
2.8.3
51
Java Packages
It is painful to write any computer program completely “from scratch” and it is better
to use already-written objects and classes to simplify the task. We did this in the examples in this chapter when we used the System.out object and the GregorianCalendar
class to help us display text in the command window and ask the computer’s clock
the time.
Objects and classes like these two are organized into folders called packages.
System.out lives in a package named java.lang, and GregorianCalendar appears
in java.util. The former package, java.lang, contains many objects and classes
that are essential for basic programming. Indeed, java.lang contains those Javacomponents that represent the “computing environment” portrayed in Figure 2 in
Chapter 1. For this reason, every Java application automatically gets use of the
objects and classes in java.lang.
But there are many other Java packages—for graphics, networking, disk-file manipulation, general utilities—and if an application wishes to use a component from
one of these packages, then that package must be explicitly imported for the use
of the application. We saw this in Figure 4, where we stated import java.util.*
at the beginning of class NameAndDate so that class GregorianCalendar could be
found. (java.util is the general utility package; it has classes that can create clocks,
calendars, and other structures. In Chapter 4, we will import the java.awt and
javax.swing packages, whose classes know how to draw graphics on the display.)
2.8.4
Java API
There are literally hundreds of objects and classes organized into Java’s two dozen
packages. How do we learn the names of these components and how to use them?
It is a bit early to undertake this ambitious task, but we can at least learn where to
look for basic information—we read Java’s Application Programming Interface (API)
documentation.
The Java API documentation is a summary of the contents of the packages; it is
a bit like a dictionary, where each word is listed with its proper spelling and a onesentence description of its use. The Java API documentation lists the components of
its packages and gives short explanations about how to use the components.
Fortunately, the Java API documentation is organized as a collection of Web pages.
It is best to download into your computer a complete set of the API’s web pages;
see http://java.sun.com for details about “Documentation.” But you are welcome
to survey the API documentation at Sun’s Web site at the URL just mentioned, as
well.
Let’s make a quick search for information about the System.out object. We begin
52
at the API’s starting Web page, which looks something like the following:
We recall that System.out lives in the java.lang package, so we search down the list
of packages to find and select the link named java.lang. (If you do not know the
package where a component lives, you can always use the alphabetized index.)
2.8. BEYOND THE BASICS
53
When we reach the Web page for java.lang, we see a description of the package:
The components of the package are listed, and one them is named System. By selecting
54
its link, we see
Among the System’s components, we find and select the one named out, and we end
2.8. BEYOND THE BASICS
55
our investigation by viewing the details for out (that is, System.out):
In this way, you can browse through the Java packages and learn their contents.
(As an exercise, you should consult the Web page for java.util and locate class
GregorianCalendar; you will see that it has many more methods than just the getTime
method we used so far.)
Chapter 3
Arithmetic and Variables
3.1 Integer Arithmetic
3.2 Named Quantities: Variables
3.2.1 Variables Can Vary: Assignments
3.3 Arithmetic with Fractions: Doubles
3.4 Booleans
3.5 Operator Precedences
3.6 Strings, Characters, and their Operations
3.7 Data-Type Checking
3.8 Input via Program Arguments
3.8.1 Converting between Strings and Numbers and Formatting
3.8.2 Temperature Conversion with Input
3.9 Diagnosing Errors in Expressions and Variables
3.10 Java Keywords and Identifiers
3.11 Summary
3.12 Programming Projects
3.13 Beyond the Basics
Traditional computer programs calculate: They calculate monthly payments on a
loan, compute the roots of a quadratic equation, or convert dollars into euros. Such
calculations rely on two concepts from algebra—arithmetic operations and variables—
and this chapter develops both as they appear in programming.
The chapter’s objectives are to
• Review the principles of arithmetic computation and introduce the notion of
data type, which ensures consistency within computations.
• Introduce computer variables and their traditional uses.
• Provide a simplistic but effective means to supply input data to a program,
so that one program can be used over and over to perform a family of related
calculations.
3.1. INTEGER ARITHMETIC
3.1
57
Integer Arithmetic
An electronic computer is an “overgrown” pocket calculator, so it is no surprise that
we can write computer programs that tell a computer to calculate on numbers. We
will learn to write such programs in Java by studying a classic example: calculating
the value of the coins in your pocket.
How do you calculate the value of your spare change? No doubt, you separate
into groups your quarter coins (25-cent pieces), dimes (10-cent pieces), nickels (5cent pieces), and pennies (1-cent pieces). Once you count the quantity of each, you
multiple each quantity by the value of the coin and total the amounts.
For example, if you have 9 quarters, 2 dimes, no nickels, and 6 pennies, you would
calculate this total:
(9 times 25) plus (2 times 10) plus (0 times 5) plus (6 times 1)
which totals to 251 cents, that is, $2.51.
If your arithmetic skills are weak, you can embed the above computation into a
simple Java program and have the computer do it for you, because the Java language
lets you write arithmetic expressions like 9 * 25 (the * means multiplication) and (9
* 25) + (2 * 10) (the + means addition), and so on—symbols like * and + are called
operators, and the operators’ arguments (e.g., 9 and 25 in 9 * 25) are their operands.
Here is the program that does the calculation:
/** Total computes the amount of change I have, based on
* 9 quarters, 2 dimes, no nickels, and 6 pennies */
public class Total
{ public static void main(String[] args)
{ System.out.println("For 9 quarters, 2 dimes, no nickels, and 6 pennies,");
System.out.print("the total is ");
System.out.println( (9 * 25) + (2 * 10) + (0 * 5) + (6 * 1) );
}
}
This program prints in the command window:
For 9 quarters, 2 dimes, no nickels, and 6 pennies,
the total is 251
Of course, the crucial statement is
System.out.println( (9 * 25) + (2 * 10) + (0 * 5) + (6 * 1) );
which embeds the arithmetic calculation as the argument to System.out’s println
method. As we discovered in the previous chapter, the argument part of a message
is always calculated to its answer before the message is sent. In the present case, it
58
means that the multiplications and additions are computed to 251 before System.out
is told to print.
If a comment, like the one in the above example, extends across multiple lines, we
begin each new line with an asterisk; the reason is explained in Chapter 5.
In the Java language, whole numbers, like 6, 0, 251, and -3, are called integers.
We see momentarily that the Java language uses the keyword, int, to designate the
collection of integers.
Begin Footnote: More precisely, int is Java’s name for those integers that can be
binary coded within one computer word, that is, the integers in the range of -2 billion
to 2 billion. End Footnote.
Of course, you can write Java programs to do other numerical calculations. For
the moment, consider addition (+), subtraction (-), and multiplication (*); you can
use these in a Java program the same way you write them on paper, e.g.,
1 + ((2 - 4) * 3) + 5
is a Java arithmetic expression that calculates to 0. You can test this example simply
enough:
public class Test
{ public static void main(String[] args)
{ System.out.println(1 + ((2 - 4) * 3) + 5); }
}
When the computer executes this program, it calculates the expression from left to
right, respecting the parentheses; if we could peer inside the computer’s processor,
we might see these steps:
=>
=>
=>
1 + ((2 - 4) * 3) + 5
1 + ((-2) * 3) + 5
1 + (-6) + 5
-5 + 5 => 0
When you write arithmetic expressions, insert parentheses to make clear the order in
which operations should occur.
Exercises
Calculate each of these expressions as the computer would do; show all the calculation
steps. After you have finished the calculations, write each of them within a Java
application, like the one in this section.
1. 6 * ((-2 + 3) * (2 - 1))
2. 6 * (-2 + 3) * (2 - 1)
3. 6 * -2 + 3 * (2 - 1)
4. 6 * -2 + 3 * 2 - 1
3.2. NAMED QUANTITIES: VARIABLES
3.2
59
Named Quantities: Variables
You will not enjoy using the previous change-counting program, Total, over and over,
because it will be difficult to alter it each time you come home with a new set of coins
in your pocket. We can improve the situation by giving names to the quantities of
quarters, dimes, nickels, and pennies. These names are called computer variables, or
variables for short.
Used in the simplest way, a variable is a just a name for an integer, just like
“Lassie” is just a name for your pet dog.
Begin Footnote: But as already noted in Chapter 2, a variable name is in reality
a storage cell that holds the named integer. For the moment, don’t worry about
the storage cell; this detail is revealed only when necessary in a later section. End
Footnote
To give the name, quarters, to the integer, 9, we write this Java statement:
int quarters = 9;
The keyword, int, signifies that quarters names an integer; indeed, it names 9.
This form of statement is called a variable declaration; more precisely, it is a variable initialization, because it creates a new variable and initializes it with a numerical
value. The keyword, int, is a data type—it names a “type” or “species” of data
value, namely the integers. The keyword, int, tells the Java compiler that the value
of quarters, whatever it might be, must be from the data type int.
Since the variable, quarters, names an integer, we use it just like an integer, e.g.,
System.out.println(quarters * 25);
would calculate and print 225. When we use a variable like quarters, in an expression,
we say we are referencing it.
Indeed, when the Java compiler examines a reference to a variable, like the one in
the previous statement, it verifies that the variable’s data type (here, int) is acceptable to the context where the variable is referenced (here, the variable is multiplied by
25—this is an acceptable context for referencing variable quarters). This examination by the Java compiler, called data-type checking—prevents problems which might
arise, say, if an int-typed variable was referenced in a context where a string was
expected.
When we need to distinguish between variables, like quarters, that denote integers, and actual integers like 6 and 225, we call the latter literals.
The name you choose for a variable is called an identifier; it can be any sequence of letters, numerals, or the underscore, , or even a dollar sign, $ (not recommended!), as long the name does not begin with a numeral, e.g., quarters or
QUArTers or my 4 quarters. But Java keywords, like public and class, cannot be
used as variable names.
60
Figure 3.1: change computing program
/** TotalVariables computes the amount of change I have, based on the values
* named by the four variables, quarters, dimes, nickels, and pennies. */
public class TotalVariables
{ public static void main(String[] args)
{ int quarters = 9;
int dimes = 2;
int nickels = 0;
int pennies = 6;
System.out.println("For these quantities of coins:");
System.out.print("Quarters = ");
System.out.println(quarters);
System.out.print("Dimes = ");
System.out.println(dimes);
System.out.print("Nickels = ");
System.out.println(nickels);
System.out.print("Pennies = ");
System.out.println(pennies);
System.out.print("The total is ");
System.out.println( (quarters * 25) + (dimes * 10)
+ (nickels * 5) + (pennies * 1) );
}
}
Here is an improved version of the change-counting example in the previous section; it names each of the four quantities and prints a detailed description of the
quantities and their total. Here is what the program prints:
For these quantities of coins:
Quarters = 9
Dimes = 2
Nickels = 0
Pennies = 6
The total is 251
The program itself appears in Figure 1. Note that long statements, like the last
one in the Figure, can extend to multiple text lines, because the semicolon marks a
statement’s end.
It is easy to see the four variables and understand how they are referenced for
change calculation. Now, if you wish to modify the program and calculate a total
for different quantities of coins, all you must do is change the appropriate variable
initializations, and the remainder of the program works correctly.
3.2. NAMED QUANTITIES: VARIABLES
61
We finish the change calculation example with this last improvement: We make
the total print as dollars and cents, like so:
For these quantities of coins:
Quarters = 9
Dimes = 2
Nickels = 0
Pennies = 6
The total is 2 dollars and 51 cents
The secret to converting a cents amount to dollars-and-cents is: divide the cents
amount by 100, calculating how many whole dollars can be extracted—this is the
quotient of the result. In the Java language, an integer quotient is computed by the
integer-division operator, /. For example,
251 / 100 computes to 2,
because 100 can be extracted from 251 at most 2 times (and leaving a nonnegative
remainder).
The remainder part of an integer divison is calculated by the remainder (“modulo”) operator, %:
251 % 100 computes to 51,
because after groups of 100 are extracted from 251 as many as possible, 51 remains
as the leftover.
To use this technique, we write these statements:
int total = (quarters * 25) + (dimes * 10) + (nickels * 5) + (pennies * 1);
System.out.print("The total is ");
System.out.print(total / 100);
System.out.print(" dollars and ");
System.out.print(total % 100);
System.out.println(" cents");
The variable initialization lets total name the total of the change, and the statements
that follow apply the quotient and remainder operations to total. These statements
print
The total is 2 dollars and 51 cents
The long sequence of print statements just seen can be compressed into just one
with this Java “trick”: When a textual string is “added” to another string or an
integer, a longer string is formed. For example
System.out.println("Hello" + "!");
System.out.println("Hello" + (48 + 1));
62
Figure 3.2: Calculating the value of change
/** Total computes the amount of change I have, based on
the values named by the variables,
quarters, dimes, nickels, and
pennies */
public class Total
{ public static void main(String[] args)
{ int quarters = 5;
int dimes = 2;
int nickels = 0;
int pennies = 6;
System.out.println("For these quantities of coins:");
System.out.println("Quarters = " + quarters);
System.out.println("Dimes = " + dimes);
System.out.println("Nickels = " + nickels);
System.out.println("Pennies = " + pennies);
int total = (quarters * 25) + (dimes * 10) + (nickels * 5) + (pennies * 1);
System.out.println("The total is " + (total / 100) + " dollars and "
+ (total % 100) + " cents");
}
}
will print the lines
Hello!
Hello49
In Java, the + operator is overloaded to represent both numerical addition as well as
textual string concatenation (appending one string to another). Perhaps this is not
a wise use of the + symbol, but it is certainly convenient!
The above trick lets us print the dollars-cents total as one long statement:
System.out.println("The total is " + (total / 100) + " dollars and "
+ (total % 100) + " cents");
Figure 2 exploits everything we have learned in the final version of the changecalculation program.
Exercises
1. Modify the application in Figure 1 so that it calculates the value of 3 quarters
and 12 nickels; compile it and execute it.
3.2. NAMED QUANTITIES: VARIABLES
63
2. Modify the application in Figure 1 so that it displays the value of each group
of coins and then the value of all the coins. Try the application for 4 quarters,
1 nickel, and 1 penny. The application should reply:
For these quantities of coins:
Quarters = 4, worth 100 cents
Dimes = 0, worth 0 cents
Nickels = 1, worth 5 cents
Pennies = 1, worth 1 cents
The total is 106 cents
3. Modify Figure 2 so that it displays its output in decimal format, e.g.,
The total is $2.51
Explain what happens when the modified application is used with the quantities
of coins listed in the previous Exercise. (We will repair the difficulty in the
section, “Converting between Strings and Numbers and Formatting.”)
4. For practice, write an application in Java that prints a multiplication table of
all pairs of numbers between 1 and 3, that is, 1*1, 1*2, and so on, up to 3*3.
3.2.1
Variables Can Vary: Assignments
As the examples in the previous section showed, variables name numerical quantities.
But they can do more than that—the value that a variable names can change as a
computation proceeds. Unlike the variables in algebra, computer variables can truly
vary.
Here’s why: When a variable is initialized, a storage cell in primary storage is
created, and the number named by the variable is placed into the cell. Here is a
picture of the object created in primary storage by class Total in Figure 2 just after
the four variables are initialized:
Total
main
{ int
quarters ==
int dimes ==
5
2
int nickels ==
0
int pennies ==
6
> System.out.println("For these quantities of coins:");
System.out.println("Quarters = " + quarters);
...
}
64
The diagram shows that each number is saved in a cell. When a statement like
System.out.println("Quarters = " + quarters) is executed, the reference to quarters
causes the computer to look inside the cell named by quarters and use the integer
therein.
The above picture is important, because the Java language allows you to change
the value held in a variable’s cell; you use a statement called an assignment. Here is
a small example, where a variable, money, is initialized to 100, and then the money is
completely withdrawn:
int money = 100;
System.out.println(money);
money = 0;
System.out.println(money);
The statement, money = 0, is the assignment; it alters the value within the variable
cell that already exists. This sequence of statements displays in the command window,
100
0
because the cell’s initial value, 100, was overwritten by 0 by the assignment.
A more interesting example creates a variable to hold money and then deposits
50 more into it:
int money = 100;
System.out.println(money);
money = money + 50;
System.out.println(money);
First, 100 is printed, and then the assignment, money = money + 50 calculates a new
value for money’s cell, namely, the previous value in the cell plus 50. Thus, 150 prints
the second time the value in money’s cell is consulted.
We use the power of assignments to write another classic program with change:
Say that we desire a program that prints the coins one needs to convert a dollars-andcents amount into coins. For example, if we have 3 dollars and 46 cents, the program
reports that we receive this change:
quarters = 13
dimes = 2
nickels = 0
pennies = 1
If you solved this problem on a street corner, you would first count the number
of quarter-dollar coins that are needed and subtract the amount of quarters from the
starting money (e.g., 3.46 - (13*0.25) = 0.21). Then, you would repeat the process
for dimes (0.21 - (2*0.10) = 0.01), nickels (0.01 - (0*0.5) = 0.01) and pennies. The
value of the remaining money decreases from 3.46 to 0.21 to 0.01 to 0.
Here is the algorithm for the change-making method:
3.2. NAMED QUANTITIES: VARIABLES
65
1. Set the starting value of money.
2. Subtract the maximum number of quarters from money, and print the quantity
of quarters extracted.
3. Subtract the maximum number of dimes from money, and print the quantity of
dimes extracted.
4. Subtract the maximum number of nickels from money, and print the quantity of
nickels extracted.
5. The remainder of money is printed as pennies.
Here is how we might write the first step in Java:
int dollars = 3;
int cents = 46;
int money = (dollars * 100) + cents;
The second step of the algorithm is cleverly written as follows:
System.out.println("quarters = " + (money / 25));
money = money % 25;
These statements exploit integer division, /, and integer modulo, %.
In the first statement, money / 25 calculates the maximum number of quarters to
extract from money. There are two important points:
1. The integer division operator, /, calculates integer quotient—the number of
times 25 can be subtracted from money without leaving a negative remainder. In
the example, since money’s cell holds 346, the quotient is 13, because a maximum
of 13 quarters (13*25 = 325) can be wholly subtracted from 346 without leaving
a negative remainder. Here are additional examples for intuition:
• 14 / 3 computes to 4 (and the remainder, 2, is forgotten)
• 6 / 3 computes to 2 (and there is no remainder)
• 4 / 5 computes to 0, because 5 cannot be wholly subtracted from 4
2. The calculation of the division, money / 25, does not alter the value in money’s
cell, which remains 346—only an assignment statement can change the value in
money’s cell.
The second statement, money = money % 25, deals with the second point just mentioned: Since we have calculated and printed that 13 whole quarters can be extracted
from the amount of money, we must reset the value in money’s cell to the remainder.
This can be done either of two ways:
66
Figure 3.3: change-making program
/** MakeChange calculates the change for the amounts in variables
*
dollars and cents. */
public class MakeChange
{ public static void main(String[] args)
{ int dollars = 3;
int cents = 46;
int money = (dollars * 100) + cents;
System.out.println("quarters = " + (money / 25));
money = money % 25;
System.out.println("dimes = " + (money / 10));
money = money % 10;
System.out.println("nickels = " + (money / 5));
money = money % 5;
System.out.println("pennies = " + money); }
}
1. By the statement,
money = money - ((money / 25) * 25);
which calculates the monetary value of the extracted quarters and subtracts
this from money.
2. By the more elegant statement,
money = money % 25;
whose modulo operator, %, calculates the integer remainder of dividing money
by 25 and assigns it to money. In this example, the remainder from performing
346/25 is of course 21. Here are additional examples of computing remainders:
• 14 % 3 computes to 2
• 6 % 3 computes to 0
• 4 % 5 computes to 4
The combination of the integer quotient and remainder operations calculates the
correct quantity of quarters. We apply this same technique for computing dimes and
nickels and see the resulting program in Figure 3.
3.2. NAMED QUANTITIES: VARIABLES
67
When the application starts, the main method executes, and the three variable
initializations create three cells:
MakeChange
main
{ int
3
dollars ==
int cents ==
46
int money == 346
> System.out.println("quarters = " + (money / 25));
money = money % 25;
> System.out.println("dimes = " + (money / 10));
money = money % 10;
...
}
At this point, quarters = 13 is printed, because 346 / 25 gives the quotient, 13.
The assignment, money = money % 25, that follows causes the value in money’s cell to
decrease, because money % 25 computes to 21:
MakeChange
main
{ int
dollars ==
int cents ==
46
int money ==
21
3
...
> System.out.println("dimes = " + (money / 10));
money = money % 10;
...
}
The value in money’s cell changes twice more, as dimes and nickels are extracted from
it.
The circular-appearing assignments in the example, like money = money % 25, might
distress you a bit, because they look like algebraic equations, but they are not. The
semantics of the previous assignment statement proceeds in these three steps:
1. The variable cell named by money is located
2. The expression, money % 25, is computed to an answer, referencing the value
that currently resides in money’s cell.
3. The answer from the previous step is placed into money’s cell, destroying whatever value formerly resided there.
It is unfortunate that the equals sign is used to write an assignment statement—in
Java, = does not mean “equals”!
Finally, because of the existence of the assignment statement, it is possible to
declare a valueless variable in one statement and assign to it in another:
68
int dollars;
dollars = 3;
// this is a declaration of dollars;
// this assigns 3 to the cell
it creates a cell
but if possible, declare and initialize a variable in one and the same statement.
Exercises
1. Revise the program, in Figure 3 so that it makes change in terms of five-dollar
bills, one-dollar bills, quarters, dimes, nickels, and pennies.
2. What are the results of these expressions? 6/4; 6%4; 7/4; 7%4; 8/4; 8%4; 6/-4;
-6%4; 6%-4.
3. Use algebra to explain why the assignment, money = money % 25, in the MakeChange
program correctly deducts the number of a quarters from the starting amount
of money.
4. Write this sequence of statements in Java:
• A variable, my money, is initialized to 12.
• my money is reduced by 5.
• my money is doubled.
• my money is reset to 1.
• The value of my money is sent in a println message to System.out.
5. Write an execution trace of this application:
public class Exercise3
{ public static void main(String[] args)
{ int x = 12;
int y = x + 1;
x = x + y;
y = x;
System.out.println(x + " equals " + y);
}
}
3.3
Arithmetic with Fractions: Doubles
Here is the formula for converting degrees Celsius into degrees Fahrenheit:
f = (9.0/5.0)*c + 32
3.3. ARITHMETIC WITH FRACTIONS: DOUBLES
69
Once we decide on a value for c, the degrees Celsius, we can calculate the value of
f, the degrees in Fahrenheit. So, if c is 22, we calculate that f is 71.6, a fractional
number. In Java, fractional numbers are called doubles (“double-precision” fractional
numbers). The Java keyword for the data type of doubles is double.
Double literals are conveniently written as decimal fractions, e.g., 9.0 and -3.14159,
or as mantissa-exponent pairs, e.g., 0.0314159E+2, which denotes 3.14159 (that is,
0.0314159 times 102 ) and 3E-6, which denotes .000003 (that is, 3 times 1/106 ).
Java allows the classic arithmetic operations on doubles: addition (whose operator
is +), subtraction (-), multiplication (*), and division (/). Given fractional (double)
operands, the operations calculate doubles as results. For example, we can write (1.2
+ (-2.1 / 8.4)) * 0.33, and the computer calculates
=>
=>
(1.2 + (-2.1 / 8.4)) * 0.33
(1.2 + (-0.25)) * 0.33
(0.95) * 0.33 => 0.3135
Note that division, /, on fractional numbers produces fractional results—for example,
7.2 / 3.2 computes to 2.25, and 6.0 / 4.0 computes to 1.5.
Returning to the temperature conversion formula, we write it as a Java application.
The key statements are these two:
int c = 22;
double f = ((9.0/5.0) * c) + 32;
The variable initialization for f starts with the keyword, double, to show that f names
a double.
It is acceptable to mix integers, like c, with doubles in an expression—the result
will be a double, here, 71.6. (This is the case even when the fraction part of the result
is zero: 2.5 * 4 computes to 10.0.)
The preceding examples present an important point about the basic arithmetic
operations, +, -, *, and /:
• When an arithmetic operation, like +, is applied to two arguments of data type
int, e.g., 3 + 2, then the result of the operation is guaranteed to be an integer—
have data type int—as well.
• When an arithmetic operation is applied to two numerical arguments, and at
least one of the arguments has type double, e.g., 3 + 2.5, then the result is
guaranteed to have type double.
The “guarantees” mentioned above are used by the Java compiler to track the data
types of the results of expressions without actually calculating the expressions themselves. We examine this issue in a moment.
The completed temperature program is simple; see Figure 4. When started, this
application displays,
70
Figure 3.4: temperature conversion
/** CelsiusToFahrenheit converts a Celsius value to Fahrenheit.
public class CelsiusToFahrenheit
{ public static void main(String[] args)
{ int c = 22;
// the degrees Celsisus
double f = ((9.0/5.0) * c) + 32;
System.out.println("For Celsius degrees " + c + ",");
System.out.println("Degrees Fahrenheit = " + f);
}
}
*/
For Celsius degrees 22,
Degrees Fahrenheit = 71.6
In addition to fractional representation, the Java language lets you write doubles in
exponential notation, where very large and very small doubles are written in terms of
a mantissa and an exponent. For example, 0.0314159E+2 is the exponential-notation
representation of 3.14159. (You see this by multiplying the mantissa, 0.0314159, by
102 , which is 10 raised to the power of the exponent, 2.) Another example is 3e-6,
which denotes .000003 (that is, 3 times 1/106 )—it doesn’t matter whether the E is
upper or lower case.
Mathematicians and engineers who work with doubles often require operations
such as exponentiation, square root, sine, cosine, and so on. The Java designers have
assembled a class, named Math, that contains these methods and many more. For
example, to compute the square root of a double, D, you need only say, Math.sqrt(D):
double num = 2.2;
System.out.println("The square root of " + num + " is " + Math.sqrt(num));
(You do not have to construct a new Math object to use sqrt.) Similarly, Math.pow(D1,
D2) computes D1D2 . Finally, Math.abs(D) computes and returns the absolute value
of D, that is, D without its negation, if there was one. A list of the more useful
computational methods for numbers appears in the supplementary section, “Helper
Methods for Mathematics,” which appears at the end of this chapter.
Although doubles and integers coexist reasonably well, complications arise when
numbers of inappropriate data types are assigned to variables. First, consider this
example:
int i = 1;
double d = i;
What value is saved in d’s cell? When i is referenced in d’s initialization statement,
the integer 1 is the result. But 1 has type int, not double. Therefore, 1, is cast into
3.3. ARITHMETIC WITH FRACTIONS: DOUBLES
71
the fractional number, 1.0 (which has type double), and the latter is saved in d’s cell.
(Computers use different forms of binary codings for integers and doubles, so there
is a true internal translation from 1 to 1.0.)
In contrast, the Java compiler refuses to allow this sequence,
double d = 1.5;
int i = d + 2;
because the compiler determines that the result of d + 2, whatever it might be, will
have data type double. Since a double will likely have a non-zero fractional part,
there would be loss of information in the translation of the double (here, it would be
3.5), into an integer. The Java compiler announces that there is a data-type error in
i’s initialization statement.
If the programmer truly wants to lose the fractional part of 3.5, she can use a cast
expression to indicate this:
double d = 1.5;
int i = (int)(d + 2);
The phrase, (int) forces the double to be truncated into an integer; here, 3 is saved
in i’s cell. We will have occasional use for such casts.
Exercises
1. Rewrite the program, CelsiusToFahrenheit, into a program, FahrenheitToCelsius,
that converts a double value of Fahrenheit degrees into double value of Celsius
degrees and prints both values.
2. Recall that one kilometer is 0.62137 of a mile. Rewrite the program, CelsiusToFahrenheit,
into a program, KilometersToMiles, that takes as input an integer value of kilometers and prints as output a double value of the corresponding miles.
3. Calculate the answers to these expressions; be careful about the conversions
(casts) of integer values to doubles during the calculations:
(a) (5.3 + 7) / 2.0
(b) (5.3 + 7) / 2
(c) 5.3 + (7 / 2)
(d) (1.0 + 2) + ((3%4)/5.0)
72
3.4
Booleans
The Java language lets you compute expressions that result in values other than
numbers. For example, a calculation whose answer is “true” or “false” is a boolean
answer; such answers arise when one compares two numbers, e.g., 6 > (4.5 + 1) asks
if 6 is greater than the sum of 4.5 and 1; the expression calculates to true, and this
statement,
System.out.println( 6 > (4.5 + 1) );
prints true.
The Java data type, boolean, consists of just the two values, true and false.
Booleans are used as answers to questions; here is an example: We employ the
temperature conversion formula to determine whether a given Celsius temperature is
warmer than a given Fahrenheit temperature. The algorithm is
1. Set the given Celsius and Fahrenheit temperatures.
2. Convert the Celsius amount into Fahrenheit.
3. Compare the converted temperature to the other Fahrenheit temperature.
The corresponding Java statements read this simply:
double C = 22.0;
double F = 77.0;
double C_converted = ((9.0/5.0) * C) + 32;
System.out.print(C + " Celsius warmer than " + F + " Fahrenheit?
System.out.println(C_converted > F);
");
These statements will print
22.0 Celsius warmer than 77.0 Fahrenheit?
false
Here are the standard comparison operations on numbers. All these operations
use operands that are integers or doubles and all compute boolean results:
Operation
greater-than
Operator symbol
Example
>
less-than
<
less-than-or-equals
<=
greater-than-or-equals
>=
equality
==
inequality
!=
6 > (3 + 1) calculates to
true
6.1 < 3
calculates to
false
(3.14 * -1) <= 0 calculates to true
1 >= 2
calculates to
false
(1 + 2) == (2 + 1) calculates to true
0.1 != 2.1 calculates to
true
3.4. BOOLEANS
73
It is perfectly acceptable to save a boolean answer in a variable:
boolean b = (3 < 2);
This declares b and initializes it to false. Indeed, the same action can be done more
directly as
boolean b = false;
because you are allowed to use the literals, false and true as well. You can reassign
a new value to a boolean variable:
b = ((3 * 2) == (5 + 1));
This resets b to true. Notice that arithmetic equality in the above example is written
as ==. Also, arithmetic operations are calculated before comparison operations, which
are calculated before the assignment, so the previous example can be abbreviated to
b = (3 * 2 == 5 + 1);
or just to
b =
3 * 2 == 5 + 1;
Finally, the usual numerical operations (+, -, *, / , %) cannot be used with
boolean arguments, because it makes no sense to “add” or “subtract” logical truths.
(E.g., what would true - false mean?) The Java compiler will report an error
message if you attempt such actions.
Exercises
1. Build a complete Java application that uses the technique in this section to
determine whether a given celsius temperature is warmer than a given fahrenheit
temperature. Test the application with 40 Celsisus and 40 Fahrenheit.
2. Build a Java application that is told a quantity of dimes and a quantity of
nickels and prints whether the quantity of dimes is worth less than the quantity
of nickels. Test the application with 4 dimes and 6 nickels.
3. Calculate the results of these expressions:
(a) (3 * 2) >= (-9 - 1)
(b) 3 * 2 != 5.5 + 0.4 + 0.1
74
3.5
Operator Precedences
When the Java interpreter calculates an arithmetic expression, it proceeds from left
to right: Given an expression of the form, EXPRESSION1 operator EXPRESSION2, the
interpreter evaluates EXPRESSION1 to its result before it evaluates EXPRESSION2 and
then applies the operator to the two resulting values.
Parentheses within an expression are highly recommended to make clear which
expressions belong as operands to which operators. Consider 1 * 2 + 3–what is the
* operator’s second operand? Is it 2? Or 2 + 3? If the expression was properly
parenthesized, as either 1 * (2 + 3) or (1 * 2) + 3, this confusion would not arise.
If parentheses are omitted from an expression that contains multiple operators,
the Java compiler will apply its own rules to direct the computation of the Java
interpreter. These rules, called operator precedences, are listed shortly.
One simple and “safe” example where parentheses can be omitted is a sequence
of additions or multiplications, e.g., 1 + 2 + 3 or 1 * 2 * 3. There is no confusion
here, because both addition and multiplication are associative—the order that the
additions (multiplications) are performed does not affect the result.
When multiplications and divisions are mixed with additions and subtractions in
an expression without parentheses, the interpreter still works left to right, but when
there is a choice, the interpreter does multiplications and divisions before additions
and subtractions. We say that the precedences of multiplication and division are
higher than those of addition and subtraction. For example, 1 + 2.0 * 3 results in
7.0, because there is a choice between computing 1 + 2.0 first or 2.0 * 3 first, and
the Java interpreter chooses the latter, that is, the multiplication is performed just
as if the expression was bracketed as 1 + (2.0 * 3).
Here is the ordering of the precedences of the arithmetic operations, in the order
from highest precedence (executed first, whenever possible) to lowest (executed last):
unary negation, e.g., -3
multiplication, *, division/quotient, /, and modulo %
addition/string concatenation, +, and subtraction, comparison operations, <, <=, >, >=
comparisons of equality, ==, and inequality, !=
Although it is not recommended to write expressions like the following one, the
precedence rules let us untangle the expression and compute its answer:
=>
=>
=>
=>
=>
3 + -4 * 5 != 6 - 7 - 8
3 + -20 != 6 - 7 - 8
-17 != 6 - 7 - 8
-17 != -1 - 8
-17 != -9
true
3.6. STRINGS, CHARACTERS, AND THEIR OPERATIONS
75
It is always best to use ample parentheses to indicate clearly the match of operands
to operators in an expression.
Exercises
1. Calculate the answers for each of these expressions:
(a) 6 * -2 + 3 / 2 - 1
(b) 5.3 + 7 / 2 + 0.1
(c) 3*2%4/5.0*2*3
2. Another way of understanding operator precedences is that the precedences are
rules for inserting parentheses into expressions. For example, the precedences
tell us that the expression 1 + 2.0 * 3 should be bracketed as 1 + (2.0 * 3).
Similarly, 5 - 3 / 2 * 4 + 6 * -2 / -3 + 1.5 is bracketed as ((5 - ((3 / 2)
* 4)) + ((6 * -2) / -3)) + 1.5. For each of the expressions in the previous
exercise, write the expression with its brackets.
3.6
Strings, Characters, and their Operations
In Chapter 2 we noted that a textual phrase enclosed in double quotes, e.g., "hello",
is a string. Like numbers and booleans, strings are values that can be computed
upon and saved in variables. Java’s data-type name for strings is String (note the
upper-case S); here is an example:
String name = "Fred Mertz";
Literal strings can contain nonletters, such as blanks, numerals, tabs, and backspaces;
here are examples:
• " " (all blanks)
• "1+2" (two numerals and a plus symbol—not an addition!)
• "" (an empty string)
• "!?,.\t\b\’\"\n\r\\" (four punctuation symbols, followed by
– a tab (\t),
– a backspace (\b),
– a single quote (\’),
– a double quote (\"),
– a newline (\n),
76
– a return (\r),
– and a backslash (\\))
The last example shows that some symbols which represent special keys on the keyboard must be typed with a leading backslash. If you print the last string:
System.out.println("!?,.\t\b\’\"\n\r\\");
you will see
!?,.
\
’"
because the tab, backspace, quotes, newline, return, and backslash display themselves
correctly in the command window.
As we saw earlier in this chapter, the + operator computes on two strings by
concatenating (appending) them together:
System.out.println("My name is " + name);
The operator can also force a number (or boolean) to be concatenated to a string,
which is a useful convenience for simply printing information:
System.out.println("My name is R2D" + (5 - 3));
Of course, the parenthesized subtraction is performed before the integer is attached
to the string.
Integers and doubles are different from strings—you can multiply and divide with
integers like 49 and 7 but you cannot do arithmetic on the the strings "49" and "7".
Indeed, "49" + "7" computes to "497", because the + symbol represents concatenation
when a string argument is used.
Later in this Chapter, we will learn techniques that transform numbers into strings
and strings into numbers; these techniques will be necessary for an application to
accept input data from a human user and do numerical computation on it.
The String type owns a rich family of operations, and we present several of them
here. The operations for strings have syntax different from the arithmetic and comparison operators previously seen because strings are actually objects in Java, and the
operations for strings are actually methods. For this reason, the following examples
should be carefully studied:
• To compare two strings S1 and S2 to see if they hold the same sequence of
characters, write
S1.equals(S2)
3.6. STRINGS, CHARACTERS, AND THEIR OPERATIONS
77
For example, "hello".equals("hel"+"lo") returns the boolean answer, true,
whereas "hello".equals("Hello") results in false.
Of course, the method can be used with a string that is named by a variable:
String s = "hello";
System.out.println(s.equals("hel"+"lo"));
System.out.println(s.equals(s));
prints true on both occasions.
• To determine a string’s length, use the length() method: for string S, the
message
S.length()
returns the integer length of the string, e.g.,
String s = "ab";
int i = s.length();
assigns 2 to i.
• For string, S, S.trim() computes a new string that looks like string S but without
any leading or trailing blanks. For example,
String s = " ab c ";
String t = s.trim();
assigns "ab c" to t—both the leading and trailing blanks are removed.
These methods and many others are listed in Table 5. Use the Table for reference;
there is no need to study all the methods at this moment. In addition to the operations
in the Table, you can locate others in the API documentation for class String within
the java.lang package.
Finally, a warning: Do not use the == operation to compare strings for equality! For
reasons that cannot be explained properly until a later chapter, the expression S1 ==
S2 does not validate that strings S1 and S2 contain the same sequence of characters;
instead, it checks whether S1 and S2 are the same identical object. The proper
expression to check equality of two strings is S1.equals(S2).
78
Figure 3.5: methods on strings
Method
S1.equals(S2)
S.length()
Semantics
equality
comparison—returns
whether strings S1 and S2 hold
the same sequence of characters
returns the length of string S
S.charAt(E)
returns the character at position
E in S
S.substring(E1,
E2)
returns the substring starting at
position E1 and extending to position E2 - 1
returns a string that looks like S
but in upper-case letters only
returns a string that looks like S
but in lower-case letters only
returns a string like S but without leading or trailing blanks
searches S1 for the first occurrence of S2 that appears inside
it, starting from index i within
S1. If S2 is found at position j,
and j >= i, then j is returned;
otherwise, -1 is returned.
like
the
equals
method,
compareTo compares the characters in string S1 to S2: if S1
is “less than” S2 according to
the lexicographic (dictionary)
ordering, -1 is returned; if S1
and S2 are the same string, 0 is
returned; if S1 is “greater than”
S2 according to the lexicographic
ordering, then 1 is returned.
S.toUpperCase()
S.toLowerCase()
S.trim()
S1.indexOf(S2,
i)
S1.compareTo(S2)
Data typing restrictions
S1 and S2 must have type
String; the answer has type
boolean.
if S has data type String,
then the result has type int
if S has data type String and
E has data type int, then the
result has data type char
if S has data type String, E1
and E2 have data type int,
then the result is a String
if S has data type String, the
result has data type String
if S has data type String, the
result has data type String
if S has data type String, the
result has data type String
if S1 and S2 have data type
String and i has data type
int, the result has data type
int
if S1 and S2 have data type
String, then the result has
data type int
3.6. STRINGS, CHARACTERS, AND THEIR OPERATIONS
79
Characters
The individual symbols within a string are called characters, and the data type of
characters is char. A character can be represented by itself by placing single quotes
around it, e.g, ’W’, ’a’, ’2’, ’&’, etc.
We noted earlier symbols like the tab and backspace keys have special codings:
• \b (backspace)
• \t (tab)
• \n (newline)
• \r (return)
• \" (doublequote)
• \’ (single quote)
• \\ (backslash)
Of course, a variable can be created to name a character:
char backspace = ’\b’;
Java uses the Unicode format for characters; inside the computer, characters are
actually integers and you can do arithmetic on them, e.g., ’a’+1 converts (casts) ’a’
into its integer coding, 97, and adds 1.
Begin Footnote: Indeed, it is possible to cast an integer into a printable character:
If one writes (char)(’a’+1), this casts the result of the addition into the character,
’b’—the phrase, (char) is the cast, and it forces the numeric value to become a
character value. End Footnote.
To examine a a specific character within a string, use the charAt(i) method from
Table 5, where i is a nonnegative integer. (Important: The characters within a string,
S, are indexed by the position numbers 0, 1, ..., S.length()-1.) Examples are:
• char c = "abc".charAt(0)
which saves character ’a’ in variable c’s cell.
• String s = "abc" + "de";
char c = s.charAt(2+1);
which saves ’d’ in c’s cell.
• "abc".charAt(3)
which results in an error that stops execution, because there is no character
number 3 in "abc".
80
Also, characters can be compared using the boolean operations in the previous section.
For example, we can compare the leading character in a string, s, to see if it equals
the character ’a’ by writing:
boolean result =
s.charAt(0) == ’a’;
The comparision works correctly because characters are saved in computer storage as
integer Unicode codings, so the equality, inequality, less-than, etc. comparisons can
be performed.
Exercises
1. Calculate the results of these expressions:
(a) 2 + ("a" + " ") + "bc"
(b) 1 + "" + 2 + 3
(c) 1 + 2 + "" + 3
(d) 1 + 2 + 3 + ""
(e) 1 + "" + (2 + 3)
2. Here are two strings:
String t = "abc ";
String u = "ab";
What is printed by these statements?
(a) System.out.println(t.equals(u));
(b) System.out.println(u.charAt(1) == t.charAt(1));
(c) System.out.println(t.length() - u.length());
(d) System.out.println(u + ’c’);
(e) System.out.println(t.trim());
(f) System.out.println(t.toUpperCase());
3.7
Data-Type Checking
Because of the variety of values—integers, doubles, booleans, and strings—that can be
expressed in a Java program, the Java compiler must enforce consist use of these values
in statements; this is called data-type checking. For example, the second statement
below is incorrect,
3.7. DATA-TYPE CHECKING
81
boolean b = true;
System.out.println(b * 5);
because the multiplication operator cannot execute with a boolean argument. Without doing the actual computation, the Java compiler notices the inconsistency and
announces there is a data-type error.
A data type is a “species” of value, much like horses, lions, and giraffes are species
of animals. Trouble can arise when different species mix, and for this reason, the Java
compiler uses data types to monitor usage of values and variables for compatibility.
More generally, data types are useful because
• They ensure compatibility of operands to operators, e.g., 1 * 2.3 is an acceptable expression but "abc" * 2.3 is not.
• They predict a property about the result of an expression, e.g., whatever the
result of 1 * 2.3 might be, it is guaranteed to be a double value.
• They help create variable cells of the appropriate format, e.g., double d declares
a cell that is suited to hold only doubles.
• They help enfore compatibilities of expressions to variables in assignments, e.g.,
d = 1 * 2.3 is an acceptable assignment, because it places a double into a cell
declared to hold only doubles.
We can list the steps the Java compiler takes to type-check an expression; consider
these two variables and the arithmetic expression that uses them:
int i = 1;
double d = 2.5;
... ((-3 * i) + d) > 6.1 ...
Since both -3 and i have data type int, the Java compiler concludes that their
addition will produce an answer that has type int.
The process continues: Since -3 * i has type int, but d has type double, this
implies that the left operand will be automatically cast into a double and that the
answer for (-3 * i) + 2, must be a double. Finally, the less-than comparison will
produce a value that has type boolean.
The process outlined in the previous paragraph is sometimes drawn as a tree, as
in Figure 6. The tree displays the numbers’ data types and how they combine into
the type of the entire expression. A variant of this “data-type tree” is indeed drawn
in primary storage by the Java compiler.
The notion of data type extends to objects, too—the Java compiler uses the name
of the class from which the object was constructed as the object’s data type. For
example, in Figure 4 of Chapter 2 we wrote
GregorianCalendar c = new GregorianCalendar();
82
Figure 3.6: data-type tree for an arithmetic expression
(( -3 * i )+ d ) > 2
int
int
int
int
double
double
double
double
double
boolean
which created a cell named c that holds (an address of) an object of data type
GregorianCalendar. The Java compiler makes good use of c’s data type; it uses it to
verify that object c can indeed be sent a getTime message, as in,
System.out.println(c.getTime());
This statement is well formed because getTime is one of the methods listed within
class GregorianCalendar, and c has data type, GregorianCalendar. The Java compiler can just as easily notice an error in
c.println("oops");
because c’s data type is of a class that does not possess a println method.
In summary, there are two forms of data types in the Java language:
• primitive types, such as int, double, and boolean
• reference (object) types, such as GregorianCalendar
Variables can be declared with either form of data type.
Surprisingly, type String is a reference type in Java, which means that strings are
in fact objects! This is the reason why the string operations in Table 5 are written
as if they were messages sent to objects—they are!
Exercises
Pretend you are the Java compiler.
1. Without calculating the answers to these expressions, predict the data type of
each expression’s answer (or whether there is a data-type error):
3.8. INPUT VIA PROGRAM ARGUMENTS
83
(a) 5.3 + (7 / 2)
(b) 3 * (1 == 2)
(c) 1 < 2 < 3
(d) "a " + 1 + 2
(e) ("a " + 1) * 2
2. Which of these statements contain data type errors?
int x = 3.5;
double d = 2;
String s = d;
d = (d > 0.5);
System.out.println(s * 3);
3.8
Input via Program Arguments
The applications in this chapter are a bit unsatisfactory, because each time we want
to reuse a program, we must reedit it and change the values in the program’s variable
initialization statements. For example, if we wish to convert several temperatures
from Celsius to Fahrenheit, then for each temperature conversion, we must reedit the
program in Figure 4, recompile it, and restart it.
It would be far better to write the temperature conversion application just once,
compile it just once, and let its user start the application again and again with
different numbers for degrees Celsius. To achieve this, we make the application read
a numeric temperature as input data each time it starts.
Java uses program arguments (also known as command-line arguments) as one
simple way to supply input data to an application.
Begin Footnote: In the next Chapter, we learn a more convenient method for
supplying input data. End Footnote
When she starts an application, the user types the program arguments—these
are the inputs. The application grabs the program arguments and assigns them to
internal variables.
Say that we have rewritten the temperature conversion program in Figure 4 to
use program arguments:
• If you installed the JDK, then when you start an application, you type the
program argument on the same line as the startup command, that is, you type
java CelsiusToFahrenheit 20
if you want to convert 20 Celsius to Fahrenheit.
84
• If you use an IDE to execute applications, you type the program argument in the
IDE’s field labelled “Program Arguments,” “Arguments,” or “Command-Line
Parameters.” (See Figure 7.) Then you start the application.
Before we develop the temperature-conversion application, we consider a simpler example that uses a program argument; perhaps we write an application, called
LengthOfName, that accepts a person’s name as its input data and prints the name’s
length in immediate response:
java LengthOfName "Fred Mertz"
The name, Fred Mertz, has length 10.
This is accomplished by this simple application, whose main method uses the name,
args[0], to fetch the value of the program argument:
/** LengthOfName prints the length of its input program argument */
public class LengthOfName
{ public static void main(String[] args)
{ String name = args[0];
// the program argument is held in args[0]
int length = name.length();
System.out.println("The name, " + name + ", has length " + length);
}
}
When an application with program arguments is started, the arguments are automatically placed, one by one, into distinct internal variables named args[0], args[1], and
so on. Using these names, the main method can grab the program arguments.
In general, there can be no program arguments at all or multiple arguments. In
the former case, nothing special needs to be done; in the latter, the arguments are
written next to each other, separated by spaces, e.g.,
java ThisApplicationUsesFourArguments Fred Mertz 40 -3.14159
When the application is started, args[0] holds the string, "Fred", args[1] holds
the string, "Mertz", args[2] holds the string, "40", and args[3] holds the string,
"-3.14159"—all the program arguments become strings, whether they are surrounded
by double quotes or not!
Why are args[2] and args[3] in the example holding strings and not numbers?
The answer lies within the phrase, String[] args, which appears in method main’s
header line: This defines the name, args, for the variables that grab the program
arguments, and the data type, String, which we must use with every main method,
forces every program argument to become a string.
Begin Footnote: In Chapter 5 we learn that args is an example of a parameter,
and in Chapter 8 we learn that args is an array. But we do not need the explanations
of these concepts here to use program arguments. EndFootnote
Therefore, an application that relies on numbers for inputs must construct the
numbers from the strings it reads as the program arguments. We now see how to do
this.
3.8. INPUT VIA PROGRAM ARGUMENTS
Figure 3.7: Supplying a program argument to an IDE
85
86
3.8.1
Converting between Strings and Numbers and Formatting
With the aid of a “helper object,” we can convert a string of numerals, like "20", into
the integer, 20. And, we can use another object to convert an integer or double into
a nicely formatted string for printing.
Converting Strings into Integers and Doubles
The Java designers have written class Integer, from which we can construct an
object that knows how to translate a string of numerals into an integer. (At this
point, you should review the example in Figures 3 and 4, Chapter 2, that used class
GregorianCalendar to create a similar “helper” object.)
Say that S is a string of numerals that we wish to translate into the corresponding
integer, e.g.,
String S = "20";
Here are the steps we take:
• Create a new Integer object, by saying
new Integer(S)
The keyword, new, creates an object from class Integer. The object grabs the
value of the argument (in this case, the value of S is "20"), saves it inside itself,
and calculates its integer translation.
• Send the new object an intValue message. This can be done in two statements,
e.g.,
Integer convertor = new Integer(S);
int c = convertor.intValue();
or in just one:
int c = new Integer(S).intValue();
The message asks the object to return the integer translation. The integer—
here, 20—is returned as the answer and is assigned to c.
In addition to class Integer, there is class Double, which creates objects that
convert strings of numerals and decimals into double values, e.g.,
String approx_pi = "3.14159";
double d = new Double(approx_pi).doubleValue();
3.8. INPUT VIA PROGRAM ARGUMENTS
87
assigns 3.14159 to d.
Begin Footnote: Both class Integer and class Double are located in the Java
package, java.lang. Although it is acceptable to add the statement, import java.lang.*
to the beginning of an application that uses the classes, the Java interpreter will automatically search java.lang when it tries to locate a class; hence, import java.lang.*
is an optional statement and will not be used here. End Footnote
Converting a Number into a String and Formatting it for Printing
Converting a number into a string is simple: Because the operator, +, will combine a
number and a string, we can concatenate an empty string to a number to “convert”
it to a string:
double d = 68.8;
String s = d + "";
In the above example, s is assigned "68.8".
When printing a double, we often wish to present it in a fixed decimal format.
For example,
System.out.println ("$" + (100.0/3.0));
prints
$33.333333333333336
where we probably would prefer just $33.33 to appear.
There is yet another class, named DecimalFormat, that constructs objects that
know how to format doubles in a fixed decimal format:
• Because the class is located in the package, java.text, include
import java.text.*;
at the beginning of the application.
• Construct a DecimalFormat object as follows:
new DecimalFormat(PATTERN)
where PATTERN is a string that specifies the significant digits to the left and right
of the decimal point. For example, the pattern, "0.00", in new DecimalFormat("0.00")
states that there must be at least one digit to the left of the decimal point and
there must be exactly two digits to the right of the decimal point. (The rightmost fractional digit is rounded.)
Other patterns, e.g., ".000" or "00.0" are acceptable, and you may consult
the Java API specification for class DecimalFormat for a full description of the
legal patterns.
88
• Send a format(D) message to the object, where D is a double value, e.g.,
DecimalFormat formatter = new DecimalFormat("0.00");
double d = 100.0 / 3.0;
String s = formatter.format(d);
The format method returns the formatted string representation of its double
argument, d; the previous statements place the string, 33.33, in s’s cell.
Here are the steps presented together:
import java.text.*;
public class Test
{ public static void main(String[] args)
{ ...
DecimalFormat formatter = new DecimalFormat("0.00");
double money = 100.0/3.0;
System.out.println ("$" + formatter.format(money));
...
}
}
3.8.2
Temperature Conversion with Input
We use the techniques from the previous section to make the temperature conversion
application from Figure 4 read a Celsuis temperature as its input and display its
translation to Fahrenheit. The input temperature will be a program argument that
is supplied at the startup command, e.g.,
java CelsiusToFahrenheit 20
Within the application, the argument is fetched and converted to an integer by writing:
int c = new Integer(args[0]).intValue();
Recall that we use the name, args[0]—this is the internal variable that grabs the
program argument when the program starts.
Next, we compute the result and format it as a decimal with exactly one fractional
digit:
double f = ((9.0/5.0) * c) + 32;
DecimalFormat formatter = new DecimalFormat("0.0");
System.out.println("Degrees Fahrenheit = " + formatter.format(f));
3.8. INPUT VIA PROGRAM ARGUMENTS
89
Figure 3.8: application using a program argument
import java.text.*;
/** CelsiusToFahrenheit converts an input Celsius value to Fahrenheit.
*
input: the degrees Celsius, a program argument, an integer
*
output: the degrees Fahrenheit, a double */
public class CelsiusToFahrenheit
{ public static void main(String[] args)
{ int c = new Integer(args[0]).intValue(); // args[0] is the program argument
double f = ((9.0/5.0) * c) + 32;
System.out.println("For Celsius degrees " + c + ",");
DecimalFormat formatter = new DecimalFormat("0.0");
System.out.println("Degrees Fahrenheit = " + formatter.format(f));
}
}
Figure 8 shows the revised temperature-conversion program.
The program in Figure 8 can be used as often as we like, to convert as many
temperatures as we like:
java CelsiusToFahrenheit 20
java CelsiusToFahrenheit 22
java CelsiusToFahrenheit -10
and so on—try it.
An Execution Trace of Temperature Conversion
The concepts related to program arguments are important enough that we must
analyze them in detail with an execution trace. Say that the user starts the application
in Figure 8 with the program argument, 20, which the Java interpreter reads as the
string, "20".
A CelsiusToFahrenheit object is created in computer storage, and its execution
starts at the first statement of its main method:
CelsiusToFahrenheit
main(args[0] == "20" )
{ >int c = new Integer(args[0]).intValue();
double f = ((9.0/5.0)*c) + 32;
System.out.println("For Celsius degrees " + c + ",");
DecimalFormat formatter = new DecimalFormat("0.0");
System.out.println("Degrees Fahrenheit = " + formatter.format(f));
}
90
args[0] holds the program argument. (We will not depict the System.out object in
these trace steps; just assume it is there.)
The first statement creates a cell named c; initially, there is no integer in it:
CelsiusToFahrenheit
main(args[0] == "20" )
{ int c == ?
> c = new Integer(args[0]).intValue();
double f = ((9.0/5.0)*c) + 32;
...
}
The value placed in the cell is computed by the expression, new Integer(args[0]).intValue().
First, args[0] must be calculated to its value; this is the string, "20":
CelsiusToFahrenheit
main(args[0] == "20" )
{ int c == ?
> c = new Integer("20").intValue();
double f = ((9.0/5.0)*c) + 32;
...
}
Next, the helper object is created in computer storage:
CelsiusToFahrenheit
a1 : Integer
main(args[0] == "20" )
holds 20
...
intValue()
{ a method that returns 20 }
{ int c == ?
> c = a1.intValue();
double f = ((9.0/5.0)*c) + 32;
...
}
The computer invents an internal address for the helper object, say, a1, and inserts
this address into the position where the object was created.
Next, execution of the statements in main pauses while object a1 receives the
message, intValue(), and executes the requested method:
CelsiusToFahrenheit
a1 : Integer
main(args[0] == "20" )
holds 20
...
intValue()
{ > a method that returns 20 }
{ int c == ?
> c = AWAIT THE RESULT FROM a1;
double f = ((9.0/5.0)*c) + 32;
...
}
3.8. INPUT VIA PROGRAM ARGUMENTS
91
Object a1 quickly returns 20 to the client object:
CelsiusToFahrenheit
main(args[0] == "20" )
{ int c == ?
> c = 20
double f = ((9.0/5.0)*c) + 32;
...
}
(Note: Object a1 stays the same, and we no longer show it.) Finally, the integer is
assigned to c’s cell:
CelsiusToFahrenheit
main(args[0] == "20" )
{ int c == 20
> double f = ((9.0/5.0)*c) + 32;
System.out.println("For Celsius degrees " + c + ",");
DecimalFormat formatter = new DecimalFormat("0.0");
System.out.println("Degrees Fahrenheit = " + formatter.format(f));
}
The remaining statements in the main method execute in the fashion seen in earlier
execution traces; in particular, a DecimalFormat object is created in the same manner
as the Integer object, and its format method returns the string, "68.0", which is
printed.
Exercises
1. Compile and execute Figure 8 three times with the program arguments 20, 22,
and -10, respectively.
2. Revise the change-making program in Figure 3 so that the dollars and cents
values are supplied as program arguments, e.g.,
java MakeChange 3 46
3. Revise either or both of the programs, CelsiusToFahrenheit and KilometersToMiles,
that you wrote as solutions to the preceding Exercises set, “Arithmetic with
Fractions: Doubles,” so that the programs use program arguments.
4. Say that a program, Test, is started with the program arguments 12 345 6
7.89. Write initialization statements that read the arguments and place the
string "12" into a variable, s; place the integer 6 into a variable, i; place the
double 7.89 into a variable, d.
92
5. Write a sequence of three statements that do the following: reads a program
argument as an integer, i; reads a program argument as an integer, j; prints
true or false depending on whether i’s value i s greater than j’s.
6. Exercise 3 of the section, “Named Quantities: Variables,” had difficulties printing correctly formatted answers when the dollars-cents amount printed contained a cents amount less than 10. Use class DecimalFormat to solve this
problem. Test your solution with the inputs of four quarters, one nickel and
one penny.
3.9
Diagnosing Errors in Expressions and Variables
Expressions and variables give a programmer more power, but this means there is
more opportunity to generate errors. The errors can have these two forms:
• Errors related to spelling, grammar, and context (data types), that the Java
compiler detects. These are called compile-time errors.
• Errors that occur when the program executes, and a statement computes a bad
result that halts execution prematurely. These are called run-time errors or
exceptions.
As we noted in the previous Chapter, the Java compiler performs a thorough job
of checking spelling and grammar. For example, perhaps Line 4 of our program was
written
System.out.println( (1+2(*3 );
The compiler notices this and announces,
Test.java:4: ’)’ expected.
System.out.println( (1+2(*3 );
^
Sometimes the narrative (here, ’)’ expected) is not so enlightening, but the identification of the error’s location is always helpful.
When using variables, take care with their declarations; one common error is
misspelling the data-type name, String:
Test1.java:5: Class string not found in type declaration.
string s;
^
3.9. DIAGNOSING ERRORS IN EXPRESSIONS AND VARIABLES
93
The error message is not so helpful, but the location of the error, is the tip-off.
With the inclusion of arithmetic expressions, a new form of error, a data-type
error, can appear. For example, the statement,
System.out.println(3 + true);
generates this compiler complaint,
Test.java:4: Incompatible type for +. Can’t convert boolean to int.
System.out.println(3 + true);
^
which states that integers and booleans cannot be added together. Although such
error messages are an annoyance initially, you will find that they are a great assistance
because they locate problems that would certainly cause disaster if the program was
executed.
Data-type errors also arise if a programmer attempts to save a value of incorrect
type in a variable, e.g.,
int i = true;
The compiler will refuse to allow the assignment. The reason should be clear—if the
next statement in the program was
System.out.println(i * 2);
then a disaster would arise. A variable’s data type is a “guarantee” that the variable
holds a value of a specific format, and the Java compiler ensures that the guarantee
is maintained.
A related issue is whether a variable holds any value at all; consider this example:
int i;
System.out.println(i * 2);
If allowed to execute, these statements would cause disaster, because i holds no
number to multiply by 2. The Java compiler notices this and complains.
Yet another variable-related error is the declaration of one name in two declarations:
int a = 1;
double a = 2.5;
System.out.println(a);
// which
a?
The compiler refuses to allow such ambiguities and announces a redeclaration error.
The previous diagnoses by the compiler are a great help, because they prevent
disasters that would arise if the program was executed. Unfortunately, the Java
compiler cannot detect all errors in advance of execution. For example, divison by
zero is a disaster, and it is hidden within this sequence of statements:
94
int i = 0;
System.out.println(1 / i);
These statements are spelled correctly and are correctly typed, but when they are
executed, the program stops with this announcement:
java.lang.ArithmeticException: / by zero
at Test.main(Test.java:4)
This is a run-time error, an exception. The message identifies the line (here, Line 4)
where the error arises and explains, in somewhat technical jargon, what the error is.
The above example of an exception is a bit contrived, but exceptions readily arise
when a program’s user types incorrect input information as a program argument.
Consider this program sequence:
int i = new Integer(args[0]).intValue();
System.out.println(1 / i);
If the program’s user enters 0 as the program argument, the program will halt with
the same exception as just seen—the program is properly written, but its user has
caused the run-time error. Even worse, if the user submits a non-number for the
input, e.g., abc, this exception is generated:
java.lang.NumberFormatException: abc
at java.lang.Integer.parseInt(Integer.java)
at java.lang.Integer.<init>(Integer.java)
at Test.main(Test.java:4)
The error message notes that the exception was triggered at Line 4 and that the
origin of the error lays within the Integer object created in that line.
Finally, some errors will not be identified by either the Java compiler and interpreter. For example, perhaps you wish to print whether the values held by variables
x and y are the same, and you write:
int x = 3;
int y = 7;
System.out.println(x = y);
The statements pass the inspection of the Java compiler, and when you start the
application, you expect to see either true or false appear. Instead, you will see 7!
The reason for this surprising behavior is that x = y is an assignment and not a
test for equality (x == y). The statement, System.out.println(x = y), assigns y’s
value to x’s cell and then prints it. For historical reasons, the Java compiler allows
assignments to appear where arithmetic expressions are expected, and results like the
one seen here are the consequence. Remember to use == for equality comparisons.
3.10. JAVA KEYWORDS AND IDENTIFIERS
3.10
95
Java Keywords and Identifiers
At the beginning of this chapter we noted that a variable name is an identifier.
Stated precisely, an identifier is a sequence of letters (upper and lower case), digits,
underscore symbol, , and dollar sign, $, such that the first character is nonnumeric.
Thus, x 1 is an identifier, but 1 x is not.
Identifiers are used to name variables, of course, but they are used for naming
classes and methods also, e.g., Total and println are also identifiers and must be
spelled appropriately. For consistent style, classes are named by identifiers that begin
with an upper-case letter, whereas method names and variable names are named by
identifiers that begin with lower-case letters.
Java’s keywords have special meaning to the Java compiler and cannot be used
as identifiers. Here is the list of keywords: abstract boolean break byte case cast
catch char class const continue default do double else extends final finally
float for future generic goto if implements import inner instanceof int interface
long native new null operator outer package private protected public rest return
short static super switch synchronized this throw throws transient try var void
volatile while
3.11
Summary
Here is a listing of the main topics covered in this chapter:
New Constructions
• variable declaration (from Figure 1):
int quarters = 5;
• arithmetic operators (from Figure 1):
System.out.println( (quarters * 25) + (dimes * 10)
+ (nickels * 5) + (pennies * 1) );
• assignment statement (from Figure 2):
money = money % 25;
• data types (from Figure 4):
int c = 22;
double f = ((9.0/5.0) * c) + 32;
96
• program argument (from Figure 6):
int c = new Integer(args[0]).intValue();
New Terminology
• operator: a symbol, like * or -, that denotes an arithmetic operation
• operand: an argument to an operator, e.g., i in 3 + i.
• data type: a named collection, or “species” of values. In Java, there are two
forms, primitive types, like int, double, and boolean, and reference types, like
GregorianCalendar and DecimalFormat.
• variable: a cell that holds a stored primitive value (like an integer) or an address
of an object.
• referencing a variable: fetching a copy of the value saved in a variable
• declaration: the statement form that constructs a variable
• initialization: the statement that gives a variable its first, initial value.
• assignment: the statement form that inserts a new value into a variable
• program argument: input data that is supplied to a Java application at startup,
typically by typing it with the startup command.
• compile-time error: an error that is identified by the Java compiler
• run-time error (exception): an error that is identified by the Java interpreter,
that is, when the application is executing.
Points to Remember
• The Java language lets you calculate on numbers, booleans, strings, and even
objects. These values are organized into collections—data types—so that the
Java compiler can use the types to monitor consistent use of variables and values
in a program.
• Input data that is supplied by means of program arguments are accepted as
strings and are saved in the variables, args[0], args[1], and so on, in the main
method.
• Strings can be converted into numbers, if needed, by means of objects created
from class Integer and class Double.
3.12. PROGRAMMING PROJECTS
97
New Classes for Later Use
• class Integer. Converts strings of numerals into integers; has method intValue(),
which returns the integer value of the string argument, S, in new Integer(S).
See Figure 6.
• class Double. Converts strings of numerals into integers; has method doubleValue(),
which returns the double value of the string argument, S, in new Double(S).
• String. Used like a primitive data type, but it is actually a class; see Table 5
for an extensive list of operations (methods).
• class DecimalFormat. Found in package java.text. Has the method, format(D),
which returns the decimal-formatted value of double, D, based on the pattern,
P, used in new DecimalFormat(P). See Figure 8.
3.12
Programming Projects
1. One ounce equals 28.35 grams. Use this equivalence to write an application
that converts a value in ounces to one in grams; an application the converts a
value in kilograms to one in pounds. (Recall that one kilogram is 1000 grams,
and one pound is 16 ounces.)
2. One inch equals 2.54 centimeters. Use this equivalence to write the following
applications:
(a) One that converts an input of feet to an output of meters; one that converts
meters to feet. (Note: one foot contains 12 inchs; one meter contains 100
centimeters.)
(b) One that converts miles to kilometers; one that converts kilometers to
miles. (Note: one mile contains 5280 feet; one kilometer contains 1000
meters.)
(c) The standard formula for converting kilometers to miles is 1 kilometer =
0.62137 miles. Compare the results from the application you just wrote
to the results you obtain from this formula.
3. Write an application that reads four doubles as its input. The program then
displays as its output the maximum integer of the four, the minimum integer of
the four, and the average of the four doubles. (Hint: the Java method Math.max
(resp., Math.min) will return the maximum (resp., minimum) of two doubles,
e.g., Math.max(3.2, -4.0) computes 3.2 as its answer.)
98
4. The two roots of the quadratic equation ax2 + bx + c = 0 (where a, x, b, and c
are doubles) are defined
√
√
−b − b2 − 4ac
−b + b2 − 4ac
and x =
x=
2a
2a
Write an application that computes these roots from program arguments for a,
b, and c. (Hint: the square root of E is coded in Java as Math.sqrt(E).)
5. (a) The compounded total of a principal, p (double), at annual interest rate,
i (double), for n (int) years is
total = p((1 + i))n )
Write an application computes the compounded total based on program
arguments for p, i, and n. (Hint: the power expression, ab , is coded in
Java as Math.pow(a,b).)
(b) Rewrite the above equation so that it solves for the value of p, thereby
calculating the amount of starting principal needed to reach total in n
years at annual interest rate i. Write an application to calculate this
amount.
(c) Rewrite the above equation so that it solves for the value of i, thereby
calculating the interest rate (the so-called discount rate) needed to reach
a desired total starting at p and taking y years. Write an application to
calculate this interest rate.
6. (a) The annual payment on a loan of principal, p (double), at annual interest
rate, i (double), for a loan of duration of y (int) years is
payment =
(1 + i)y ∗ p ∗ i
(1 + i)y − 1
Write an application that computes this formula for the appropriate program arguments. (Hint: the power expression, ab , is coded in Java as
Math.pow(a,b).)
(b) Given the above formula, one can calculate the amount of debt remaining
on a loan after n years of payments by means of this formula:
zn − 1
debtAf terN years = (p ∗ z ) − (payment ∗
)
z−1
n
where z = 1 + i, and payment is defined in the previous exercise.
Extend the application in the previous exercise so that it also prints the
remaining debt for the Years 0 through 5 of the calculated loan.
99
3.12. PROGRAMMING PROJECTS
7. (a) Starting with a principal, p, the total amount of money accumulated after
contributing c each year for y years, assuming an annual interest rate of i,
goes as follows:
(1 + i)y+1 − (1 + i)
)
i
Write an application that computes total for the four input values.
total = (p ∗ (1 + i)y ) + (c ∗
(b) Given a total amount money saved, total, the annual annuity payment
that can be paid out every year for a total of z years, and still retain a
nonnegative balance, is
payment =
total ∗ (1 + i)z−1 ∗ i
(1 + i)z − 1
assuming i is the annual interest that is paid into the remaining money.
Write an application that computes the formula given the three inputs.
8. The acceleration of an automobile is the rate of change of its velocity. The
formula for computing acceleration is
Vf − V i
t
where Vi is the automobile’s initial velocity, Vf is its finial velocity, and t is
the elapsed time the vehicle took, starting from Vi , to reach Vf . (Note: to use
the formula, the measures of velocity and time must be consistent, that is, if
velocities are expressed in miles per hour, then time must be expressed in hours.
The resulting accleration will be in terms of miles and hours.)
acceleration =
(a) Write an application that computes acceleration based on input arguments
for Vi , Vf , and t.
(b) Write an application that computes the time needed for an automobile to
reach a final velocity from a standing start, given as input value of the
acceleration.
(c) Write an application that computes the final velocity an automobile reaches,
given a standing start and input values of time and acceleration.
9. Say that an automobile is travelling at an acceleration, a. If the automobile’s
initial velocity is Vi , then the total distance that the automobile travels in time
t is
distance = Vi t + (1/2)a(t2 )
(Again, the measures of values must be consistent: If velocity is stated in terms
of miles per hour, then time must be stated in hours, and distance will be
computed in miles.) Use this formula to write the following applications:
100
(a) The user supplies values for Vi , a, and t, and the application prints the
value of distance.
(b) The user supplies values only for Vi and a, and the application prints the
values of distance for times 0, 1, 2, ...
10. Write an application, CountTheSeconds, that converts an elapsed time in seconds
into the total number of days, hours, minutes, and leftover seconds contained in
the time. For example, when the user submits an input of 289932, the program
replies
289932 is
3 days,
8 hours,
32 minutes, and
12 seconds
11. Write an application, DecimalToBinary, that translates numbers into their binary representations. The program’s input is a program argument in the range
of 0 to 63. For example, when the user types an input of 22, ¡/pre¿ the program
replies:
22 in binary is 010110
(Hint: Study the change-making program for inspiration, and use System.out.print
to print one symbol of the answer at a time.)
12. Number theory tells us that a number is divisible by 9 (that is, has remainder
0) exactly when the sum of its digits is divisible by 9. (For example, 1234567080
is divisible by 9 because the sum of its digits is 36.)
Write an application that accepts an integer in the range -99,999 to 99,999 as
its input and prints as its output the sum of the integer’s digits and whether or
not the integer is divisible by 9. The output might read as follows:
For input 12345,
the sum of its digits is 15.
Divisible by 9? false
13. In the Math object in java.lang there is a method named random: using Math.random()
produces a (pseudo-)random double that is 0.0 or larger and less than 1.0. Use
this method to write a program that receives as input an integer, n, and prints
as its output a random integer number that falls within the range of 0 to n.
3.12. PROGRAMMING PROJECTS
101
14. Next, use Math.random() to modify the CelsiusToFahrenheit application in Figure 4 so that it each time it is started, it chooses a random Celsius temperature
between 0 and 40 and prints its conversion, e.g.,
Did you know that 33 degrees Celsuis
is 91.4 degrees Fahrenheit?
15. Write an application, Powers, that takes as its input an integer, n and prints as
output n’s powers from 0 to 10, that is, n0 , n1 , n2 , ..., n10 .
16. Write an application, PrimeTest, that takes as its input an integer and prints
out which primes less than 10 divide the integer. For example, for the input:
java PrimeTest 28
The program replies
2
3
5
7
divides
divides
divides
divides
28?
28?
28?
28?
true
false
false
true
17. Write an application that computes a worker’s weekly net pay. The inputs are
the worker’s name, hours worked for the week, and hourly payrate. The output
is the worker’s name and pay. The pay is computed as follows:
• gross pay is hours worked multiplied by payrate. A bonus of 50% is paid
for each hour over 40 that was worked.
• a payroll tax of 22% is deducted from the gross pay.
18. Write an application that computes the cost of a telephone call. The inputs
are the time the call was placed (this should be written as a 24-hour time, e.g.,
2149 for a call placed a 9:49p.m.), the duration of the call in minutes, and the
distance in miles of the destination of the call. The output is the cost of the
call. Cost is computed as follows:
• Basic cost is $0.003 per minute per mile.
• Calls placed between 8am and midnight (800 to 2400) are subject to a 20%
surcharge. (That is, basic cost is increased by 0.2.)
• A tax of 6% is added to the cost.
The difficult part of this project is computing the surcharge, if any. To help do
this, use the operation Math.ceil(E), which returns the nearest integer value
that is not less than E. For example, Math.ceil(0.2359 - 0.0759) equals 1.0,
but Math.ceil(0.0630 - 0.0759) equals 0.0.
102
3.13
Beyond the Basics
3.13.1 Longs, Bytes, and Floats
3.13.2 Helper Methods for Mathematics
3.13.3 Syntax and Semantics of Expressions and Variables
In these optional, supplemental sections, we examine topics that extend the essential
knowledge one needs to program arithmetic.
3.13.1
Longs, Bytes, and Floats
Almost all the computations in this text rely on just integers, doubles, booleans, and
strings. But the Java language possesses several other primitive data types, and we
survey them here.
The data type int includes only those whole numbers that can be binary coded in
a single storage word of 32 bits—that is, numbers that fall in the range of -2 billion
to 2 billion (more precisely, between −231 and (231 ) − 1). To work with integers that
fall outside this range, we must use long integers. (The data type is long.) Create
a long integer by writing a number followed by an L, e.g., 3000000000L is a long 3
billion. Long variables are declared in the standard way, e.g.,
long x = 3000000000L;
Longs are needed because answers that fall outside the range of the int type
are unpredictable. For example, write a Java program to compute 2000000000 +
1000000000 and compare the result to 2000000000L + 1000000000L The former overflows, because the answer is too large to be an integer, and the result is nonsensical.
If you plan to work with numbers that grow large, it is best to work with longs.
At the opposite end of the spectrum is the byte data type, which consists of
those “small” integers in the range -128 to 127, that is, those integers that can be
represented inside the computer in just one byte—eight bits—of space. For example,
75 appears inside the computer as the byte 01001011. (To make 75 a byte value,
write (byte)75.) Byte variables look like this:
byte x = (byte)75;
One can perform arithmetic on byte values, but overflow is a constant danger.
Java also supports the data type float of fractional numbers with 8 digits of
precision. A float is written as a fractional number followed by the letter, F. For
example, 10.0F/3.0F calculates to 3.3333333. A double number can be truncated to
8 digits of precision by casting it to a float, e.g.,
double d = 10/3.0;
System.out.println( (float)d );
103
3.13. BEYOND THE BASICS
Figure 3.9: data types
Data Type name
byte
int
long
float
double
char
boolean
String
Members
one-byte
whole
numbers, in range
-128 to 127
one-word integers
(whole numbers),
in range -2 billion
to 2 billion
two-word integers
(whole numbers)
fractional numbers
with 8 digits of precision
fractional numbers
with 16 digits of
precision
single
characters
(two-byte integers)
truth values
text strings
Sample values
(byte)3
(byte)-12
Sample expression
(byte)3 +
(byte)1
3, -2000, 0
3+(2-1)
3000000000L, 3L
2000000000L +
1000000000L
(float)(10.0/3.0F)
6.1F, 3.0E5F,
6.1, 3.0E5,
-1e-1
10.5 / -3.0
’a’, ’\n’, ’2’
’a’ + 1
true, false
"Hello to you!",
"", "49"
(6 + 2) > 5
"Hello " + "to"
displays 3.3333333, which might look more attractive than 3.3333333333333335, which
would be displayed if the cast was omitted.
Floats can be written in exponential notation e.g.,
float x = 3e-4F;
which saves .0003F in x.
Table 9 summarizes the data types we have encountered.
3.13.2
Helper Methods for Mathematics
Within the java.lang package, class Math provides a variety of methods to help with
standard mathematical calculations. (Some of the methods were mentioned in the
Programming Projects section.) Table 10 lists the more commonly used ones; many
more can be found in the Java API for class Math.
104
Figure 3.10: mathematical methods
Constant name
Math.PI
Math.E
Method
Math.abs(E)
Semantics
the value of PI, 3.14159...
the value of E, 2.71828...
Semantics
returns the absolute value of its numeric argument, E
Math.ceil(E)
Math.max(E1,
E2)
returns the nearest integer value that
is not less than E. (That is, if E has a
non-zero fraction, then E is increased
upwards to the nearest integer.)
returns the nearest integer value that
is not greater than E. (That is, if E has
a non-zero fraction, then E is decreased
downwards to the nearest integer.)
returns the cosine of its argument, E, a
numeric value in radians. (Note: 180
degrees equals PI radians.)
returns the maximum of its two numeric arguments
Math.min(E1,
E2)
returns the minimum of its two numeric arguments
Math.pow(E1,
E2)
returns E1 raised to the power E2:
E1E2
Math.random()
returns a (pseudo)random number
that is 0.0 or greater but less than 1.0
returns E rounded to the nearest whole
number
Math.floor(E)
Math.cos(E)
Math.round(E)
Math.sin(E)
Math.sqrt(E)
Math.tan(E)
returns the sine of its argument, E, a
numeric value in radians. (Note: 180
degrees equals PI radians.)
returns the square root of its argument
returns the tangent of its argument, E,
a numeric value in radians. (Note: 180
degrees equals PI radians.)
Data typing
double
double
Data typing
returns the same data type
as its argument, e.g., a
double argument yields a
double result
E should be a double; the
result is a double
E should be a double; the
result is a double
returns a double
Similar to multiplication—
the result data type depends on the types of the
arguments
Similar to multiplication—
the result data type depends on the types of the
arguments
Both arguments must be
numeric type; the result is
a double
the result has type double
if E has type float, the result has type int; if E has
type double, the result has
type long
returns a double
The arguments must be
numeric; the result is a
double
returns a double
3.13. BEYOND THE BASICS
3.13.3
105
Syntax and Semantics of Expressions and Variables
The syntax and semantics of expressions and variables are surprisingly rich, and we
will reap rewards from investing some time in study. The starting point is the notion
of “data type”:
Data Type
Here is the syntax of Java data types as developed in this chapter:
TYPE ::= PRIMITIVE_TYPE | REFERENCE_TYPE
PRIMITIVE_TYPE ::= boolean | byte | int | long | char | float | double
REFERENCE_TYPE ::= IDENTIFIER | TYPE[]
Types are either primitive types or reference types; objects have data types that
are REFERENCE TYPEs, which are names of classes—identifiers like GregorianCalendar.
Chapter 2 hinted that String[] is also a data type, and this will be confirmed in
Chapter 8.
Semantics: In concept, a data type names a collection of related values, e.g., int
names the collection of integer numbers. In practice, the Java compiler uses data
types to monitor consistent use of variables and values; see the section, “Data Type
Checking” for details.
Declaration
DECLARATION ::=
TYPE IDENTIFIER [[ = INITIAL_EXPRESSION ]]? ;
A variable’s declaration consists of a data type, followed by the variable name, followed by an optional initialization expression. (Recall that the [[ E ]]? states that
the E-part is optional.) The data type of the INITIAL EXPRESSION must be consistent
with the TYPE in the declaration.
See the section, “Java Keywords and Identifiers,” for the syntax of Java identifiers.
Semantics: A declaration creates a cell, named by the identifier, to hold values
of the indicated data type. The cell receives the value computed from the initial
expression (if any).
Statement and Assignment
The addition of declarations and assignments motivates these additions to the syntax
rule for statements:
STATEMENT ::=
DECLARATION
| STATEMENT_EXPRESSION ;
STATEMENT_EXPRESSION ::= OBJECT_CONSTRUCTION | INVOCATION | ASSIGNMENT
The syntax of an assignment statement goes
106
ASSIGNMENT := VARIABLE = EXPRESSION
VARIABLE ::= IDENTIFIER
The Java compiler enforces that the data type of the expression to the right of the
equality operator is compatible with the type of the variable to the left of the equality.
Semantics: An assignment proceeds in three stages:
1. determine the cell named by the variable on the left-hand side of the equality;
2. calculate the value denoted by the expression on the right-hand side of the
equality;
3. store the value into the cell.
Expression
The variety of arithmetic expressions encountered in this chapter are summarized as
follows:
EXPRESSION ::=
LITERAL
| VARIABLE
| EXPRESSION INFIX_OPERATOR EXPRESSION
| PREFIX_OPERATOR EXPRESSION
| ( EXPRESSION )
| ( TYPE ) EXPRESSION
| STATEMENT_EXPRESSION
LITERAL ::= BOOLEAN_LITERAL
| INTEGER_LITERAL | LONG_LITERAL
| FLOAT_LITERAL | DOUBLE_LITERAL
| CHAR_LITERAL | STRING_LITERAL
INFIX_OPERATOR ::=
+ | - | * | / | %
| < | > | <= | >= | ==
PREFIX_OPERATOR ::= INITIAL_EXPRESSION ::= EXPRESSION
|
!=
A LITERAL is a constant-valued expression, like 49, true, or "abc". The ( TYPE )
EXPRESSION is a cast expression, e.g., (int)3.5.
Semantics: Evaluation of an expression proceeds from left to right, following the
operator precedences set down in the section, “Operator Precedences.”
Chapter 4
Input, Output, and State
4.1 Interactive Input
4.1.1 Dialog Output
4.2 Graphical Output
4.2.1 Panels and their Frames
4.2.2 Customizing Panels with Inheritance
4.3 Format and Methods for Painting
4.3.1 Constructor Methods and this object
4.4 Objects with State: Field Variables
4.4.1 Using Fields to Remember Inputs and Answers
4.4.2 Scope of Variables and Fields
4.5 Testing a Program that Uses Input
4.6 Summary
4.7 Programming Projects
4.8 Beyond the Basics
Realistic programs contain subassemblies that are dedicated to input and output
activities. This chapter presents standard techniques for designing and using inputoutput classes. The objectives are to
• employ interactive input, where an application can interact with its user and
request input when needed for computation;
• use inheritance to design graphics windows that display output in the forms of
text, colors, and pictures.
• show how an object can “remember” the “state” of the computation by means
of field variables.
108
Figure 4.1: an architecture with an input-view
Controller
main
Input View
4.1
[the “start-up” component]
[reads interactive input]
Output View
[displays output]
Interactive Input
The temperature-conversion program in Figure 8, Chapter 3, receives its input from
a program argument, which is data that is supplied the moment that a program is
started. (Review the section, “Input via Program Arguments,” in that chapter for the
details.) The program-argument technique is simple, but it is not realistic to demand
that all input data be supplied to an application the moment it is started—indeed,
most programs use interactive input, where the program lets its user submit data
while the program executes.
To implement interactive input, an application must use an input-view component,
as displayed in Figure 1.
The Figure makes several points: First, the application is built from several
classes, where the “start-up class” contains the main method. Since this class controls
what happens next, we call it the controller. The controller asks another component
to read inputs that the user types; the part of the program that is responsible for reading input is called the input-view. The controller uses yet another part for displaying
output; this is the output-view.
The programs in Chapters 2 and 3 had no input-view, and System.out was used
as a simple output-view. In this section, we improve the situation by adding an
input-view that interacts with the user to receive input.
It is best to see this new concept, the input-view, in an example: Say that we revise
the temperature-conversion application so that its user starts it, and the application
4.1. INTERACTIVE INPUT
109
displays an input dialog that requests the input Celsius temperature:
The user interacts with the input dialog by typing an integer, say 20, and pressing
the OK button:
The dialog disappears and the answer appears in the command window:
For Celsius degrees 20,
Degrees Fahrenheit = 68.0
It is the input-view object that interacts with the user and reads her input. The
extensive library of Java packages includes a class that knows how to construct such
input dialogs; the class is JOptionPane. The crucial steps for using the class to
generate an input-reading dialog are
1. At the beginning of the application, add the statement,
import javax.swing.*;
so that the Java interpreter is told to search in the javax.swing package for
JOptionPane.
2. Create the dialog on the display by stating
String input = JOptionPane.showInputDialog(PROMPT);
110
Figure 4.2: Interactive input from a dialog
import java.text.*;
import javax.swing.*;
/** CelsiusToFahrenheit2 converts an input Celsius value to Fahrenheit.
*
input: the degrees Celsius, an integer read from a dialog
*
output: the degrees Fahrenheit, a double */
public class CelsiusToFahrenheit2
{ public static void main(String[] args)
{ String input =
JOptionPane.showInputDialog("Type an integer Celsius temperature:");
int c = new Integer(input).intValue(); // convert input into an int
double f = ((9.0 / 5.0) * c) + 32;
DecimalFormat formatter = new DecimalFormat("0.0");
System.out.println("For Celsius degrees " + c + ",");
System.out.println("Degrees Fahrenheit = " + formatter.format(f));
}
}
where PROMPT is a string that you want displayed within the dialog. (For the
above example, the prompt would be "Type an integer Celsius temperature:".)
The message, showInputDialog(PROMPT), creates the dialog that accepts the
user’s input. The text string that the user types into the dialog’s field is returned as the result when the user presses the dialog’s OK button. In the above
statement, the result is assigned to variable input. (Like program arguments,
inputs from dialogs are strings.)
Begin Footnote: Perhaps you noted that, although JOptionPane is a class, an
showInputDialog message was sent to it—why did we not create an object from class
JOptionPane? The answer is elaborated in the next Chapter, where we learn that
showInputDialog is a static method, just like main is a static method, and there is
no need to create an object before sending a message to a static method. Again,
Chapter 5 will present the details. End Footnote
Figure 2 shows how the temperature-conversion application is modified to use the
dialog as its input view. As noted above, the input received by the dialog is a string,
so we must convert the string into an integer with a new Integer “helper object”:
int c = new Integer(input).intValue();
The application’s outputs are computed and are displayed in the command window,
but later in this chapter we learn how to display outputs in graphics windows of our
own making.
Of course, an application can ask its user for as many inputs as it desires. For
example, the change-making application in Figure 3, Chapter 3, might be converted
4.1. INTERACTIVE INPUT
111
into an application that asks its user for the dollars input and then the cents input—
the first two statements in Figure 3’s main method are replaced by these, which
construct two dialogs:
String d =
JOptionPane.showInputDialog("Type an integer dollars amount:");
int dollars = new Integer(d).intValue();
String c =
JOptionPane.showInputDialog("Type an integer cents amount:");
int cents = new Integer(c).intValue();
One obvious question remains: What happens when the user presses the dialog’s
Cancel button instead of its OK button? If you try this, you will see that the program
prematurely halts due to an exception:
Exception in thread "main" java.lang.NumberFormatException: null
at CelsiusToFahrenheit2.main(CelsiusToFahrenheit2.java:11)
JOptionFrame’s input dialog is written so that a press of its Cancel button causes it
to return a “no value” string, called null. (This is different from the empty string,
""—null means “no string at all.”)
Although null is assigned to variable input, it is impossible for the new Integer
helper object to convert it into an integer, and the program halts at this point.
Exceptions are studied in more detail later.
Another odd behavior is seen when the application is used correctly and displays the converted temperature: The application does not appear to terminate, even
though all the statements in its main method have completed. Alas, JOptionPane is
the culprit—it is still executing, unseen.
Begin Footnote: JOptionPane and the other graphics classes in this chapter generate separate threads of execution, that is, they are separately executing objects. So,
even though the main method’s “thread” terminates, the “thread” associated with
JOptionPane is still executing (perhaps “idling” is a better description), hence, the
application never terminates.
Multiple threads of execution are valuable for programming animations and applications where multiple computations must proceed in parallel. End Footnote
To terminate the application, use the Stop or Stop Program button on your IDE;
if you use the JDK, press the control and c keys simultaneously in the command
window.
Figure 3 displays the class diagram for the application in Figure 2; JOptionPane has
taken its position as the input view, and the helper classes, Integer and DecimalFormatter,
are included because the controller sends messages to objects created from them.
Exercises
1. Write a sequence of statements that use JOptionPane to read interactively a
person’s name (a string) and the person’s age (an integer).
112
Figure 4.3: architecture for temperature-conversion application
JOptionPane
showInputDialog
CelsiusToFahrenheit2
main
Integer
intValue
System.out
DecimalFormatter
format
2. Write an application, class Test, that reads interactively an integer and prints
in the command window the integer’s square. Test this program: What happens
when you, the user, abuse it by typing your name instead of a number? When
you press only the OK key for your input? When you refuse to type anything
and go to lunch instead?
3. Revise the MakeChange program in Figure 3, Chapter 3, so that it uses interactive
input.
4.1.1
Dialog Output
If an application’s output consists of only one line of text, we might present this line
in an output dialog, rather than in the command window. Indeed, the output from
the previous application might be presented in an output dialog like this:
When the user presses the OK button, the dialog disappears.
The JOptionPane class contains a method, showMessageDialog, which constructs
and displays such dialogs. The format of messages to showMessageDialog goes
JOptionPane.showMessageDialog(null, MESSAGE);
where MESSAGE is the string to be displayed in the dialog. The first argument, null,
is required for reasons explained in Chapter 10; for now, insert null on faith.
We can modify Figure 2 to use the dialog—use this statement,
4.2. GRAPHICAL OUTPUT
113
Figure 4.4: a simple graphics window
JOptionPane.showMessageDialog(null, c + " Celsius is "
+ formatter.format(f) + " Fahrenheit");
as a replacement for the two System.out.println statements at the end of the main
method in Figure 2.
4.2
Graphical Output
We now build output-views that replace System.out in our applications. The views
we build will be “graphics windows” that display words, shapes, and colors on the
display. Graphics windows are objects, and like all objects they must be constructed
from classes. In the Java libraries, there already exist classes from which we can
construct empty windows. To build an interesting window, we use the prewritten
classes in a new way—we extend them by inheritance.
A simple graphics window appears in Figure 4. It takes tremendous work to
build the window in the Figure from scratch, so we start with the classes of windows
available in the javax.swing library and extend them to create windows like the one
in the Figure. To get started, we study a specific form of graphics window called a
frame and we learn how to “paint” text and diagrams on a panel and insert the panel
into its frame.
114
Figure 4.5: creating an invisible frame
import javax.swing.*;
/** FrameTest1 creates a frame */
public class FrameTest1
{ public static void main(String[] args)
{ JFrame sample frame = new JFrame();
System.out.println("Where is the frame?");
}
}
Figure 4.6: creating an empty frame
import javax.swing.*;
/** FrameTest2 creates and displays an empty frame */
public class FrameTest2
{ public static void main(String[] args)
{ JFrame sample frame = new JFrame();
sample frame.setSize(300, 200); // tell frame to size itself
// tell frame to make itself visible
sample frame.setVisible(true);
System.out.println("Frame has appeared!");
}
}
4.2.1
Panels and their Frames
In Java, graphics text and drawings are displayed within a frame. Frames are built
from class JFrame, which lives in the package, javax.swing. Any application can use
class JFrame; see Figure 5.
The frame is created by the phrase, new JFrame(), and we name the frame,
sample frame. As usual, the appropriate import statement must be included.
If you execute this example, you will be disappointed—no frame appears, and
the application itself does not terminate, even though all of main’s statements have
successfully executed. (As noted in the previous section, you must terminate the
application manually.)
We can repair the situation by sending messages to the frame object, telling it to
size itself and show itself on the display. Figure 6 shows how, by sending the messages
setSize and setVisible to the JFrame object.
The message, setSize(300, 200), tells sample frame to size itself with width 300
pixels and height 200 pixels. (The unit of measurement, “pixels,” is explained later.
The sizes, 300 and 200, were chosen because frames in 3-by-2 proportion tend to be
4.2. GRAPHICAL OUTPUT
115
Figure 4.7: an empty frame
visually pleasing.) Then, setVisible(true) tells sample frame to make itself truly
visible; the result is that the frame in Figure 7 appears on the display.
Alas, the frame in Figure 7 is empty—we have inserted nothing within it to display!
To remedy this, we must construct a panel object and draw text or shapes onto the
panel. The panel is “inserted” into the frame so that we can see it on the display.
4.2.2
Customizing Panels with Inheritance
A panel is the Java object on which one draws or “paints.” The javax.swing library
contains a class JPanel from which we can construct blank panels. To draw or paint
on the panel, we write instructions for painting and attach them to the panel using
a technique called inheritance. We introduce panel-painting-by-inheritance with a
simple example.
Say that the graphics window in Figure 7 should display the text, Hello to you!,
in red letters, giving the results in Figure 8. To do this, we take these steps:
1. Starting from class JPanel, we write our own customized variant of a panel
and name it, say, class TestPanel. Within class TestPanel, we include the
instructions for painting Hello to you!, in red letters.
2. We construct an object from class TestPanel, we construct a frame object
from class JFrame, and we insert the panel object into the frame object.
3. We size and show the frame, like we did in Figure 6, and the frame with its
panel appears on the display.
116
Figure 4.8: a graphics window with text
Here is the format we use to write class TestPanel:
import java.awt.*;
import javax.swing.*;
public class TestPanel extends JPanel
{
public void paintComponent(Graphics g)
{ ... // instructions that paint text and shapes on the panels’s surface
}
}
The crucial, new concepts are as follows:
• public class TestPanel asserts that we can construct TestPanel objects from
the new class; we do it by stating, new TestPanel().
• The phrase, extends JPanel, connects class TestPanel to class JPanel—it
asserts that every TestPanel object that is constructed will have the contents of
a prebuilt, blank JPanel object plus whatever extra instructions we have written
within class TestPanel.
Here is some terminology: TestPanel is called the subclass; JPanel is the superclass.
When we write class TestPanel, we do not edit or otherwise alter the file where
class JPanel resides.
4.2. GRAPHICAL OUTPUT
117
Figure 4.9: a panel that displays a string
import java.awt.*;
import javax.swing.*;
/** TestPanel creates a panel that displays a string */
public class TestPanel extends JPanel
{ /** paintComponent paints the panel
* @param g - the ‘‘graphics pen’’ that draws the items on the panel */
public void paintComponent(Graphics g)
{ g.setColor(Color.red);
g.drawString("Hello to you!", 30, 80);
}
}
• The extra instructions within class TestPanel are held in a newly named
method, paintComponent. Like method main, paintComponent holds instructions to be executed. Specifically, paintComponent must hold the instructions
that state how to paint text, shapes, and colors on the panel’s surface.
The paintComponent method is started automatically, when the panel appears
on the display, and is restarted automatically every time the panel is iconified
(minimized/closed into an icon) and deiconified (reopened from an icon into a
window) or covered and uncovered on the display.
paintComponent’s header line contains the extra information, Graphics g. Every
panel has its own “graphics pen” object that does the actual painting on the
panel’s surface; g is the name of the pen. (We study this issue more carefully
in the next chapter.)
• Finally, the graphics pen uses classes from an additional package, java.awt, so
the statement, import java.awt.*, must be inserted at the beginning of the
class.
Figure 9 shows the completed class, TestPanel. Within paintComponent, we see
the statements,
g.setColor(Color.red);
g.drawString("Hello to you!", 30, 80);
The first statement sends a message to the graphics pen, g, asking it to “fill” itself
with red “ink.” (Colors are named Color.red, Color.black, etc. See the section,
“Colors for Graphics,” at the end of the Chapter.) The second message tells g to
write the string, "Hello to you!" on the surface of the window. The numbers, 30
and 80, state the position on the window where string drawing should start, namely
118
Figure 4.10: Constructing a panel and inserting it into a frame
import javax.swing.*;
/** FrameTest2 creates and displays an empty frame */
public class FrameTest2
{ public static void main(String[] args)
{ // construct the panel and frame:
TestPanel sample panel = new TestPanel();
JFrame sample frame = new JFrame();
// insert the panel into the frame:
sample frame.getContentPane().add(sample panel);
// finish by sizing and showing the frame with its panel:
sample frame.setSize(300, 200);
sample frame.setVisible(true);
System.out.println("Frame has appeared!");
}
}
30 pixels to the right of the window’s left border and 80 pixels downwards from the
window’s top. (The unit of measurement, “pixel,” will be defined in the next section.)
Finally, the commentary for the paintComponent method contains an extra line of
explanation of the graphic pen:
/** paintComponent paints the window
* @param g - the ‘‘graphics pen’’ that draws the items onto the window */
The reason for the idiosyncratic phrase, @param, is revealed in the next chapter.
Although we might construct a panel object, by stating, new TestPanel, we cannot
execute class TestPanel by itself. A panel must be inserted into a frame object, so
we write a controller class whose main method constructs a panel and a frame, inserts
the panel into the frame, and shows the frame. Figure 10 shows us how.
The first statement within the main method constructs a panel object from class
TestPanel and names it sample panel, and the second statement constructs a frame
object similarly. To insert sample panel into sample frame, we must use the wordy
statement:
sample_frame.getContentPane().add(sample_panel);
Crudely stated, the phrase, getContentPane(), is a message that tells the frame to
“open” its center, its “content pane”; then, add(sample panel) tells the content pane
to hold the sample panel. A precise explanation of these steps will be given in Chapter
10, but until then, read the above statement as a command for inserting a panel into
an empty frame.
119
4.2. GRAPHICAL OUTPUT
Figure 4.11: architecture of application with MyOwnFrame
FrameTest2
main
MyOwnFrame
paintComponent
JFrame
JPanel
Graphics
setColor
drawString
getContentPane
setSize
setVisible
Once the frame and panel are assembled, we set the size and show the frame as
before.
As usual, class FrameTest2 is placed in a file, FrameTest2.java, and class TestPanel
is placed in the file, TestPanel.java. Both files are kept in the same disk folder (directory). When we execute FrameTest2, this creates a FrameTest2 object in primary
storage and starts the object’s main method, which itself creates the TestPanel and
JFrame objects, which appear on the display. After these objects are constructed, the
contents of primary storage look like this:
FrameTest2
main(...)
{ ... }
a1 : TestPanel
paintComponent(Graphics g) { ...
}
The object holds instructions
contained in class JPanel
as well as the variable g == a2
a2 : Graphics
setColor(...) { ... }
drawString(...) { ... }
...
a3 : JFrame
setSize(...)
{ ...
setVisible(...)
...
}
{ ...
}
[The Graphics object is constructed
automatically with the TestPanel.]
The picture emphasizes that the construction of a TestPanel object causes a Graphics
object (the graphics “pen”) to be constructed also. Also, since TestPanel extends
JPanel, the TestPanel object receives the internal variables and methods of an empty
JPanel object as well as the newly written paintComponent method.
Figure 11 shows the architecture of the simple application. The large arrowhead
from MyOwnFrame to JFrame indicates that the former class extends/inherits the latter.
The Figure provides an opportunity to distinguish between the forms of connections between classes: The large arrowhead tells us that a TestPanel is a JPanel but
with extra structure. The normal arrowheads tell us that FrameTest2 uses a JFrame
and a TestPanel. The is-a and uses-a relationships are crucial to understanding the
architecture of the application.
120
We finish this section by presenting the panel that constructs the graphics window
in Figure 4; it appears in Figure 12. The controller that uses the panel also appears
in the Figure. Before you study the Figure’s contents too intently, please read the
commentary that immediately follows the Figure.
Class MyPanel in Figure 12 contains several new techniques:
• Within the paintComponent method, variables like frame width, frame heigth,
left edge, width, and height give descriptive names to the important numeric
values used to paint the panel.
• Within paintComponent,
g.drawRect(left_edge, top, width, height);
sends a drawRect message to the graphics pen to draw the outline of a rectangle. The drawRect method requires four arguments to position and draw the
rectangle:
1. The position of the rectangle’s left edge; here, it is left edge.
2. The position of the rectangle’s top edge; here, it is top.
3. The width of the rectangle to be drawn; here, this is width.
4. The height of the rectangle to be drawn; here, this is height.
Thus, the rectangle’s top left corner is positioned at the values 105 and 70, and
the rectangle is drawn with width 90 pixels and height 60 pixels.
• Similarly,
g.fillRect(0, 0, frame_width, frame_height);
draws a rectangle, and this time, “fills” its interior with the same color as the
rectangle’s outline. Of course, 0, 0 represents the upper left corner of the panel.
Again,
g.fillOval(left_edge + width - diameter, top, diameter, diameter);
draws and fills an oval. Since a circle is an oval whose height and width are
equal, the four arguments to the fillOval method are
1. The position of the oval’s left edge, which is computed as left edge +
width (the rectangle’s right border) minus diameter (the circle’s diameter).
2. The position of the oval’s top edge, which is top.
4.2. GRAPHICAL OUTPUT
Figure 4.12: a colorful graphics window
import java.awt.*;
import javax.swing.*;
/** MyPanel creates a colorful panel */
public class MyPanel extends JPanel
{ /** paintComponent fills the panelwith the items to be displayed
* @param g - the ‘‘graphics pen’’ that draws the items */
public void paintComponent(Graphics g)
{ int frame width = 300;
int frame height = 200;
g.setColor(Color.white); // paint the white background:
g.fillRect(0, 0, frame width, frame height);
g.setColor(Color.red);
int left edge = 105;
// the left border where the shapes appear
int top = 70;
// the top where the shapes appear
// draw a rectangle:
int width = 90;
int height = 60;
g.drawRect(left edge, top, width, height);
// draw a filled circle:
int diameter = 40;
g.fillOval(left edge + width - diameter, top, diameter, diameter);
}
}
import javax.swing.*;
import java.awt.*;
/** FrameTest3 displays a colorful graphics window */
public class FrameTest3
{ public static void main(String[] args)
{ JFrame my frame = new JFrame();
// insert a new panel into the frame:
my frame.getContentPane().add(new MyPanel());
// set the title bar at the top of the frame:
my frame.setTitle("MyFrameWriter");
// an easy way to color the backgrond of the entire window:
int frame width = 300;
int frame height = 200;
my frame.setSize(frame width, frame height);
my frame.setVisible(true);
System.out.println("Frame has appeared!");
}
}
121
122
3. The width and height of the oval, which are both just diameter.
This message draws a circle at the position 155 and 70, of width and height 40
by 40 pixels.
To do a decent job of painting, we must learn more about the format of a window
and about the arguments one supplies to methods like drawRect and fillOval; we
study this in the next section.
Within the controller, FrameTest3, we see these new techniques:
• One statement both constructs the panel and inserts it into the frame:
my_frame.getContentPane().add(new MyPanel());
Since the panel is not referenced any more by the controller, there is no need
to attach to it a variable name.
• Two more methods of class JFrame help construct the window: setTitle("MyFrameWriter")
places "MyFrameWriter" into the window’s title bar, and
Exercises
Change MyPanel (and FrameTest3) as follows:
1. The displayed circle is painted black. (Hint: use Color.black.)
2. The size of the panel is 400 by 400 pixels.
3. The background color of the window is yellow. (Hint: use Color.yellow.)
4. The oval is painted with size 80 by 40.
5. The rectangle is drawn in the lower right corner of the 300-by-200 window.
(Hint: In this case, the upper left corner of the rectangle would be at pixel
position 210, 140.)
4.3
Format and Methods for Painting
Every panel and frame, and window in general, has a width and a height. In Figure
12, the statement, setSize(300,200), sets the width of the window to 300 pixels and
its height to 200 pixels. A pixel is one of the thousands of “dots” you see on the
display screen; it holds one “drop” of color. (For example, to display the letter, “o”
on the display, the operating system must color a circle of pixels with black color.)
Within a window, each pixel has a “location,” which is specified by the pixel’s
distance from the window’s left border and its distance from the window’s top (and
123
4.3. FORMAT AND METHODS FOR PAINTING
Figure 4.13: locating a pixel at coordinates 100,50
50
100
height
200
width 300
Figure 4.14: ¡tt¿g.drawRect(105, 70, 90, 60)¡/tt¿
70
105
height
60
width 90
not the bottom, contrary to conventional usage). Figure 13 shows how one locates
the pixel at position (100, 50) within a window sized 300 by 200.
It is traditional to discuss a pixel’s position in terms of its x,y coordinates, where
x is the horizontal distance from the window’s left border, and y is the distance from
the window’s top border.
Using pixels, we can give precise directions to a graphics pen: g.drawRect(105,
70, 90, 60) tells the graphics pen, g, to position the top left corner of a rectangle at
coordinates 105,70; the size of the rectangle will be width 90 by height 60. See Figure
14.
To draw an oval, think of it as a as a round shape that must inscribed within
an imaginary rectangular box. Therefore, g.fillOval(105, 90, 40, 40) tells the
graphics pen to position a 40 by 40 oval (a circle of diameter 40) inside an imaginary
40 by 40 box, at coordinates 105, 90. See Figure 15.
The graphics pen has methods to draw lines, text, rectangles, ovals, and arcs of
ovals. The methods behave like the two examples in the Figures.
When you paint on a panel, ensure that the coordinates you use indeed fall within
the panel’s size. For example, drawing an oval at position, 300, 300 within a window
124
Figure 4.15: g.fillOval(105, 90, 40, 40)
70
105
height
40
width 40
of size 200, 200 will show nothing. Also, assume that a window’s top 20 pixels (of its
y-axis) are occupied by the title bar—do not paint in this region.
Table 16 lists the methods of class Graphics we use for telling a graphics object
what to draw. For each method in the table, we see its name followed by a list of
the arguments it must receive. (For example, drawString(String s, int x, int y)
states that any message requesting use of drawString must supply three arguments—
a string and two integers. For the sake of discussion, the string is named s and the
integers are named x and y.)
It might be best to study the details of Table 16 as they are needed in the examples
that follow.
4.3.1
Constructor Methods and
this
object
Every panel must be inserted into a frame, and it is tedious to write over and over
again the instructions that construct the frame and insert the panel into the frame.
It is better to write the panel class so that that the panel itself constructs its own
frame and inserts itself into the frame. This is done using two new and important
techniques: constructor methods and self reference via this.
Here is an example that illustrates the new techniques: Say that you want a
4.3. FORMAT AND METHODS FOR PAINTING
125
Figure 4.16: Graphics pen methods
Methods from class Graphics
Name
setColor(Color c)
Description
Fills the graphics pen with color, c
drawLine(int x1, int y1, int x2,
int y2)
drawString(String s, int x, int y)
Draws a line from position x1, y1 to x2,
y2
Displays the text, a String, s, starting at
position x, y, which marks the base of the
leftmost letter
Draws the outline of a rectangle of size
width by height, positioned so that the
upper left corner is positioned at x, y
Like drawRect but fills the rectangle’s interior with the color in the pen
Draws the outline of an oval of size width
by height, positioned so that the upper
left corner of the imaginary “box” that encloses the oval is positioned at x, y
drawRect(int x, int y, int width,
int height)
fillRect(int x, int y, int width,
int height)
drawOval(int x, int y, int width,
int height)
fillOval(int x, int y, int width,
int height)
drawArc(int x, int y, int width,
int height, int start angle, int
thickness)
fillArc(int x, int y, int width,
int height, int start angle, int
thickness)
paintImage(Image i, int x, int y,
ImageObserver ob)
Like drawOval but fills the oval’s interior
Like drawOval but draws only part of
the oval’s outline, namely the part starting at start angle degrees and extending
counter-clockwise for thickness degrees.
Zero degrees is at “3 o’clock” on the oval;
the arc extends counter-clockwise for a positive value of thickness and clockwise for
a a negative value.
Like drawArc, but fills the interior of the
partial oval, drawing what looks like a
“slice of pie”
Displays the image file (a jpg or gif image) i with its upper left corner at position, x, y, with “image observer” ob. (This
method is employed by examples in Chapter 10; it is included here for completeness.)
126
program that displays the current time as a clock in a window:
As usual, we must write a class that extends JPanel whose paintComponent method
has instructions for drawing the clock’s face. But we must also perform the mundane
steps of constructing a frame for the panel, inserting the panel into the frame, and
displaying the frame. Can we merge these steps with the ones that define and paint
the panel? We do so with a constructor method.
Every class is allowed to contain a “start up” method, which automatically executes when an object is constructed from the class. The start-up method is called
a constructor method, or constructor, for short. The constructor method typically
appears as the first method in a class, and it must have the same name as the class’s
name. For example, if we are writing a panel named ClockWriter, then its constructor
method might appear as follows:
/** ClockWriter draws a clock in a panel. */
public class ClockWriter extends JPanel
{ /** ClockWriter is the constructor method: */
public ClockWriter()
{ ... // start-up instructions that execute when the
// object is first constructed
}
/** paintComponent draws the clock with the correct time.
* @param g - the graphics pen that does the drawing */
public void paintComponent(Graphics g)
{ ... }
}
Remember that public ClockWriter() contains the start-up instructions that execute
exactly once, when the ClockWriter object is first constructed by means of the phrase,
4.3. FORMAT AND METHODS FOR PAINTING
127
new ClockWriter()
Now, we understand that the semantics of the phrase is twofold:
• A ClockWriter object is constructed in primary storage;
• A message is sent to the new object to execute the instructions in its constructor
method, also named ClockWriter.
We wish to write class ClockWriter so that it constructs a panel and paints the
graphical clock, and we will write the class’s constructor method so that it constructs
the panel’s frame, inserts the panel into the frame, and shows the frame. Figure 17
shows how we do this.
When the instruction, new ClockWriter() executes, a ClockWriter object is constructed in storage. Then, the constructor method, also named ClockWriter, executes:
1. JFrame clocks frame = new JFrame() constructs a frame object in storage.
2. clocks frame.getContentPane().add(this) sends a message to the frame, telling
it to add this ClockWriter panel into it.
The pronoun, this, is used within ClockWriter’s constructor method to refer to
this newly built ClockWriter object whose start-up instructions are executing.
When an object refers to itself, it uses this, just like you use “I” and “me”
when you refer to yourself.
3. The last four instructions in the constructor method tell the frame how to
present itself on the display.
Now, whenever you state, new ClockWriter(), the panel displays itself in a frame that
it constructed itself.
The paintComponent method does the hard work of drawing the clock on the panel.
The clock is constructed from a circle and two slender, filled arcs, one arc for the
minutes’ hand and one for the hours’ hand. Based on the current time, in hours and
minutes (e.g., 10:40 is 10 hours and 40 minutes), we use these formulas to calculate
the angles of the clock’s two hands:
minutes_angle = 90 - (minutes * 6)
hours_angle = 90 - (hours * 30)
(The rationale is that a clock face contains 360 degrees, so each minute represents
6 degrees and each hour represents 30 degrees of progress on the face. Subtraction
from 90 is necessary in both formulas because Java places the 0-degrees position at
the 3 o’clock position.)
To fetch the current time, we construct a GregorianCalendar object and uses its
get method to retrieve the current minutes and hours from the computer’s clock.
128
Figure 4.17: class to paint the clock
import java.awt.*;
import javax.swing.*;
import java.util.*;
/** ClockWriter draws a clock in a panel. */
public class ClockWriter extends JPanel
{ public ClockWriter()
{ int width = 200; // the width of the clock
// construct this panel’s frame:
JFrame clocks frame = new JFrame();
// and insert this panel into its frame:
clocks frame.getContentPane().add(this);
// show the frame:
clocks frame.setTitle("Clock");
clocks frame.setSize(width, width);
clocks frame.setVisible(true);
}
/** paintComponent draws the clock with the correct time.
* @param g - the graphics pen that does the drawing */
public void paintComponent(Graphics g)
{ int width = 200; // the width of the clock
g.setColor(Color.white);
g.fillRect(0, 0, width, width); // paint the background
GregorianCalendar time = new GregorianCalendar();
int minutes angle = 90 - (time.get(Calendar.MINUTE) * 6);
int hours angle = 90 - (time.get(Calendar.HOUR) * 30);
// draw the clock as a black circle:
int left edge = 50;
int top = 50;
int diameter = 100;
g.setColor(Color.black);
g.drawOval(left edge, top, diameter, diameter);
// draw the minutes’ hand red, 10 pixels smaller, with a width of 5 degrees:
g.setColor(Color.red);
g.fillArc(left edge + 5, top + 5, diameter - 10, diameter - 10,
minutes angle, 5);
// draw the hours’ hand blue, 50 pixels smaller, with a width of -8 degrees:
g.setColor(Color.blue);
g.fillArc(left edge + 25, top + 25, diameter - 50, diameter - 50,
hours angle, -8);
}
/** The main method assembles the clock in its frame.
* is inserted here for testing purposes. */
public static void main(String[] args)
{ new ClockWriter(); }
}
The method
4.3. FORMAT AND METHODS FOR PAINTING
129
Begin Footnote: The get method and its arguments, Calendar.MINUTE and Cale
ndar.HOUR are explained in Table 22; we do not need the details here. End Footnote
Drawing the clock’s hands is nontrivial: The minutes’ hand must be drawn as a
filled arc, slightly smaller than the size of the clock’s face. So that we can see it, the
arc has a thickness of 5 degrees. A smaller arc is drawn for the hours’ hand, and it is
attractive to draw it with a thickness of -8 degrees. (See the explanation of drawArc
in Table 16 for the difference between positive and negative thickness.)
Begin Footnote: Why was the minutes’ hand drawn with a positive (counterclockwise) thickness and the hours’ hand drawn with a negative (clockwise) thickness?
Partly, to show that it can be done! But also because the negative thickness makes
the hours’ hand more attractive, because this makes the hand “lean” forwards towards the next hour. See Exercise 2 for a better alternative to the negative thickness.
End Footnote.
Because class ClockWriter contains all the instructions for building and displaying the clock, we can write a main method that is just this simple:
public static void main(String[] args)
{ new ClockWriter(); }
There is no need to attach a variable name to the ClockWriter object, so it suffices
to state merely new ClockWriter().
It seems silly to write a separate controller class that contains just these two lines of
text, so we use a standard shortcut—insert the main method into class ClockWriter
as well! At the bottom of Figure 17, you can see the method. You start the main
method in the usual way, say, by typing at the command line,
java ClockWriter
The Java interpreter finds the main method in class ClockWriter and executes its
instructions.
Begin Footnote: In the next chapter we learn why the main method can be embedded in a class from which we construct objects. End Footnote
You should try this experiment: Start ClockWriter and read the time. Then,
iconify the window (use the window’s iconifiy/minimize button), wait 5 minutes, and
deiconify (“open”) the window—you will see that the clock is repainted with a new
time, the time when the window was reopened!
The explanation of this behavior is crucial: each time a graphics window is
iconified (or covered by another window) and deiconified (uncovered), the window
is sent a message to execute its paintComponent method. For the example in Figure 17, each time the paintComponent method executes, it constructs a brand new
GregorianCalendar object that fetches the current time, and the current time is
painted.
130
Exercises
1. Modify class ClockWriter so that its hours’ hand is exactly the same length
as the minutes’ hand but twice as wide.
2. Improve the positioning of the hours’ hand in class ClockWriter so that it
moves incrementally, rather than just on the hour. Use this formula:
hours_angle = 90 - ((hours + (minutes/60.0)) * 30)
3. Make the clock “digital” as well as “analog” by printing the correct time, in
digits, at the bottom of the window.
4.4
Objects with State: Field Variables
In Chapter 1, we noted that an object has an “identity,” it knows some things, and
it is able to do some things. In the Java language, an object’s identity is its address
in primary storage, and its abilities are its methods.
To give an object knowledge, we equip it with variables that can be shared, read,
and updated by the object’s methods. Such variables are called field variables, or
fields for short.
Here is a standard use of fields: Perhaps you noticed in the previous two examples
(MyWriter and ClockWriter) that variable names were used to name the width and
the height of the panels that were displayed. For example, we saw this coding in the
ClockWriter example in Figure 17:
public class ClockWriter extends JPanel
{ public ClockWriter()
{ int width = 200; // the width of the clock
...
clocks_frame.setSize(width, width);
...
}
public void paintComponent(Graphics g)
{ int width = 200; // the width of the clock
...
g.fillRect(0, 0, width, width);
...
}
}
It is unfortunately that we must declare width twice, as two local variables; we can
make do with one if we change width into a field variable and declare it just once,
like this:
4.4. OBJECTS WITH STATE: FIELD VARIABLES
131
public class ClockWriter extends JPanel
{ private int width = 200; // the field remembers the panel’s width
public ClockWriter()
{
...
clocks_frame.setSize(width, width);
...
}
public void paintComponent(Graphics g)
{
...
g.fillRect(0, 0, width, width);
...
}
}
Field width remembers the panel’s width and is shared by both the constructor
method and paintComponent — it is not “local” or “owned” by either. The field’s
declaration is prefixed by the keyword, private; the keyword means “usable only by
the methods in this class (and not by the general public).”
This simple change makes the class ClockWriter easier to use and maintain,
because a key aspect of the panel’s identity — its width — is listed in a single
variable that is listed in a key position of the class.
Here is a second example that shows we can change the value of a field variable:
Say that we desire a graphics window that “remembers” and prints a count of how
many times it has been painted on the display:
A variable is the proper tool for remembering such a count, but the variable cannot be declared inside the object’s paintComponent method, because each time the
132
paintComponent method is started, the variable will be reinitialized at its start-
ing count.
If the variable is declared within the constructor method, then the
paintComponent method is not allowed to reference it.
Indeed, we have taken for granted a fundamental principle of variables: a variable
declared within a method can be used only by the statements within that method. Such
variables are called local variables, because they are “local” to the method in which
they are declared. Clearly, a local variable cannot be shared by multiple methods in
an object.
For the example application, we desire a variable that is initialized by the constructor method and is read and updated by the paintComponent method. The solution is
to declare the variable outside of both the constructor and paintComponent methods
as a field variable. Figure 18 shows how this is done with a field named count.
Field count’s declaration is prefixed by the keyword private; the keyword means
“usable only by the methods in this class (and not by the general public).” Because
count is declared independently from the methods in the class, it is shared by all the
methods, meaning that the methods can fetch and change its value.
A field should always appear with an internal comment that explains the field’s
purpose and restrictions on its range of values and use, if any. (Such restrictions are
sometimes called the field’s representation invariant.) In the Figure, the comment
reminds us that count’s value should always be nonnegative. This information must
be remembered when we write the methods that reference and assign to the field.
The constructor method of Figure 18 sets count to zero, to denote that the window
has not yet been painted; it does not redeclare the variable—it merely assigns to it.
Each time a message is sent to paint, the paint method increments the value in count
(it does not redeclare the variable—it merely reads and assigns to it) and displays the
new value.
Here is a picture of how a FieldExample object looks in primary storage after its
constructor method has finished:
FieldExample
a2 : Graphics
a3 : JFrame
main(...)
{ ... > }
setColor(...) { ... }
drawString(...) { ... }
...
setSize(...) { ... }
setVisible(...) { ...
...
}
a1 : FieldExample
private int count ==
0
paintComponent(Graphics g) { ...
( also holds the variable g ==
count = count + 1; ...
}
a2
The diagram shows that the field, count, is indeed a variable cell whose value can be
used by the object’s methods (here, paintComponent). Also, the graphics pen required
by every panel is in fact saved as a field within the panel as well.
4.4. OBJECTS WITH STATE: FIELD VARIABLES
Figure 4.18: a field variable
import java.awt.*;
import javax.swing.*;
/** FieldExample displays how often a window is painted on the display */
public class FieldExample extends JPanel
{ private int count; // this field variable holds the count of how
// often the window has been painted; for this
// reason, its value must always be nonnegative.
/** FieldExample constructs the window. */
public FieldExample()
{ count = 0; // the window has never been painted
// construct the panel’s frame and display it:
JFrame my frame = new JFrame();
my frame.getContentPane().add(this);
int height = 200;
my frame.setSize((3 * height)/2, height);
my frame.setVisible(true);
}
/** paintComponent paints the the count of paintings
* @param g - the graphics pen */
public void paintComponent(Graphics g)
{ count = count + 1; // we are painting one more time
g.setColor(Color.black);
int margin = 25;
int line height = 20;
int first line = 40;
int baseline = first line + (line height * count);
g.drawString("Painted " + count + " times", margin, baseline);
}
/** The main method assembles the panel and frame and shows them. */
public static void main(String[] a)
{ new FieldExample(); }
}
133
134
Because this example is illustrative and not meant to be part of a serious application, the startup method, main, is embedded within class FieldExample. This lets
us quickly test the class by itself, e.g., java FieldExample.
You should compile and execute class FieldExample. By iconifying and deiconifying and by covering and uncovering the window, you will learn when and how the
paintComponent method does its work.
Fields can also hold (the addresses of) objects. For example, we might further
rewrite class ClockWriter in Figure 17 so that the class retains a GregorianCalendar
object in a field:
import java.awt.*;
import javax.swing.*;
import java.util.*;
/** ClockWriter2 draws a clock in a panel. */
public class ClockWriter2 extends JPanel
{ private int width = 200; // the panel’s width
private GregorianCalendar time = new GregorianCalendar(); // the panel’s clock
/** Constructor ClockWriter2 constructs the properly sized frame */
public ClockWriter2()
{ ... // the constructor method remains the same
}
/** paintComponent draws the clock with the correct time.
* @param g - the graphics pen that does the drawing */
public void paintComponent(Graphics g)
{ ...
int minutes_angle = 90 - (time.get(Calendar.MINUTE) * 6);
int hours_angle = 90 - (time.get(Calendar.HOUR) * 30);
... // the remainder of the method stays the same as in Figure 17
}
...
}
The field, time, is constructed when the object itself is constructed, and it is initialized with the address of a newly constructed GregorianCalendar object. When the
paintComponet method draws the clock, it sends messages to (the object named by)
time to fetch the time.
When one class, C1, constructs an object from class C2 and retains it within a
field, we say that C1 has a (or owns a) C2. In the above example, ClockWriter has a
GregorianCalendar. The has-a relationship is sometimes depicted in a class diagram
135
4.4. OBJECTS WITH STATE: FIELD VARIABLES
by an arrow whose head is a diamond:
ClockWriter2
paint
JPanel
Graphics
setColor
drawString
GregorianCalendar
get
Remember these crucial facts about the semantics of fields:
• The field’s cell is created when the object itself is constructed. If there is an
initial value for the field, the value is computed and assigned when the cell is
created. The cell lives inside the object, and the field is ready for use when the
constructor method starts execution.
• A field can be initialized when it is declared, e.g.,
private int width = 200;
If a field is not initialized, like count in Figure 18, the Java interpreter will
insert an initial value:
– Fields that hold numeric values are set to 0
– Fields that hold boolean values are set to false
– Fields that hold (addresses of) objects are set to null (“no value”)
If a field is not initialized when it is declared, it is best to initialize it within
the constructor method.
• The value in a field’s cell is retained even when the object is at rest (not executing
one of its methods). A field acts like an object’s “memory” or “internal state,”
because its value is remembered and can be referenced the next time a message
is sent to the object to execute a method.
• Field names should never be redeclared in a method. Say that we alter the
constructor method in Figure 18 to redeclare count:
public FieldExample()
{ int count = 0;
int height = 200;
setSize(height * 2, height);
setVisible(true);
}
136
The first statement creates another, distinct, “local” variable that is owned by
the constructor method—the 0 is assigned to this local variable and not to the
field! Unfortunately, the Java compiler allows this dubious double declaration
of count, and the result is typically a faulty program.
Exercises
1. Compile and execute this application:
public class ShowTwoFieldExamples
{ public static void main(String[] args)
{ FieldExample a = new FieldExample();
FieldExample b = new FieldExample();
}
}
How many windows does this construct? (Note: Be careful when you answer—
try moving the window(s) you see on the display.) When you iconify and deiconify one of the FieldExample windows, does this affect the appearance of the
other? What conclusion do you draw about the internal states of the respective FieldExample objects? Draw a picture of the objects in computer storage
created by executing ShowTwoFieldExamples.
2. Finish writing class ClockWriter2 and do an experiment: Start the application,
iconify the window it generates on the display, and after five minutes, reopen
the window. What time do you see? Constrast this behavior to that of class
ClockWriter in Figure 17.
3. Here is a humorous example that illustrates field use. Write a class EggWriter,
that generates a graphics window that displays an egg:
4.4. OBJECTS WITH STATE: FIELD VARIABLES
137
Each time the EggWriter window is iconified and deiconified, the egg is repainted
at half its former size:
Opening and closing the window enough makes the egg shrink into nothingness.
(Hint: Use a field to remember the size of egg to paint; each time the paint
method redraws the egg, it also halves the value of the field.)
4.4.1
Using Fields to Remember Inputs and Answers
We finish the chapter by combining the techniques we have learned into an application
that reads interactive input, computes an answer, and paints the answer into a graphics window. Although the resulting application has a less-than-ideal architecture, we
pursue it anyway, because it motivates the materials in the next chapter.
Returning to the Celsius-to-Fahrenheit application in Figure 2, we rewrite it to
display its answers in a graphics window. That is, the application generates the dialog
seen earlier in the chapter:
and in response to the user’s input, a graphics window is constructed that displays
138
the results:
The application’s algorithm is simple:
1. Generate a dialog the reads the input Celsius temperature;
2. Compute the corresponding Fahrenheit temperature;
3. Show a graphics window with the two temperatures.
Because of our limited technical knowledge, it is unclear how we design the the graphics window so that it knows which temperatures to print. This problem is simply
handled in Chapter 5, but for now we improvise: We write an output-view class,
class CelsiusToFahrenheitWriter, whose constructor method contains Steps 1 and
2 of the above algorithm. The Celsuis temperature and its Fahrenheit equivalent are
saved in fields that the paintComponent method uses in Step 3.
The end result is not elegant, but it is effective. Figure 19 shows how we write the
constructor method and the paintComponent method for CelsiusToFahrenheitWriter.
Please read the commentary that follows before studying the Figure’s contents.
The class’s fields are important: celsius and fahrenheit remember the values of
the input and output temperatures, so that the paint method can reference them
when it is time to print them on the window.
As a matter of style, we added two additional fields, which state basic layout
information for the graphics window—if we wish to alter the size or the margins of
the window, we revise these field declarations, which are easily spotted because they
are located at the beginning of the class and their names are spelled with upper case
letters (which is a Java tradition for such “layout” fields).
As promised, the frame’s constructor method has packed into it the steps of reading the input and computing the answer; the input and answer temperatures are saved
4.4. OBJECTS WITH STATE: FIELD VARIABLES
Figure 4.19: a panel for calculating and displaying temperatures
import java.awt.*;
import javax.swing.*;
import java.text.*;
/** CelsiusToFahrenheitWriter creates a panel that displays a
* Celsius temperature and its Fahrenheit equivalent. */
public class CelsiusToFahrenheitWriter extends JPanel
{ int frame height = 200;
{ private int celsius;
// the input Celsius temperature
private double fahrenheit; // a Fahrenheit temperature: its value must
//
be the translation of celsius into Fahrenheit
private int LEFT MARGIN = 20; // left margin for printing text
private int LINE HEIGHT = 20; // the height of one line of text
/** Constructor CelsiusToFahrenheitWriter receives the user’s input
* Celsius temperature, translates it, and displays the output. */
public CelsiusToFahrenheitWriter()
{ // read the input temperature and compute its translation:
String input =
JOptionPane.showInputDialog("Type an integer Celsius temperature:");
celsius = new Integer(input).intValue();
fahrenheit = ((9.0 / 5.0) * celsius) + 32;
// construct the frame and show the answer:
JFrame f = new JFrame();
f.getContentPane().add(this);
f.setTitle("Celsius to Fahrenheit");
f.setSize((3 * frame height) / 2, frame height);
f.setVisible(true);
}
/** paintComponent paints the panel with the two temperatures
* @param g - the panel’s graphics pen */
public void paintComponent(Graphics g)
{ g.setColor(Color.white); // paint the background:
g.fillRect(0, 0, (3 * frame height) / 2, frame height);
g.setColor(Color.red);
int first line = LINE HEIGHT * 4; // where to print the first line
g.drawString("For Celsius degrees " + celsius + ",", LEFT MARGIN,
first line);
DecimalFormat formatter = new DecimalFormat("0.0");
g.drawString("Degrees Fahrenheit = " + formatter.format(fahrenheit),
LEFT MARGIN, first line + LINE HEIGHT);
}
public static void main(String[] args)
{ new CelsiusToFahrenheitWriter(); }
}
139
140
in celsius and fahrenheit, respectively. Only then does the constructor method do
its usual business of constructing the graphics window.
4.4.2
Scope of Variables and Fields
There are two forms of variables in Java programs: local variables and field variables.
Local variables are declared and used only inside the body of a method; field variables
are declared independently of a class’s methods and are used by all the methods. The
“area of usage” of a variable is called its scope.
Here is the technical definition of “scope” for local variable declarations in Java:
The scope of a local variable declaration, starts at the declaration itself and extends
until the first unmatched right brace, }.
For example, consider the local variable, margin, within the paintComponent method
in Figure 18:
public void paintComponent(Graphics g)
{ count = count + 1; // we are painting one more time
g.setColor(Color.black);
int margin = 25;
int line_height = 20;
int first_line = 40;
int baseline = first_line + (line_height * count);
g.drawString("Painted " + count + " times", margin, baseline);
}
The scope of margin’s declaration are these statements:
int line_height = 20;
int first_line = 40;
int baseline = first_line + (line_height * count);
g.drawString("Painted " + count + " times", margin, baseline);
}
Thus, margin cannot be referenced or changed in the constructor method in Figure
18 nor in any of the statements in paintComponent that precede the declaration.
The scope definition for local variables appears to have a problem—consider this
example:
public static void main(String[] args)
// This method is erroneous!
{ int n = 2;
System.out.println(n);
double n = 4.1;
// the scope of double n overlaps that of
System.out.println(n); // which n is printed?!
}
int n !
4.4. OBJECTS WITH STATE: FIELD VARIABLES
141
Fortunately, the Java compiler refuses to translate this example, so the problem never
arises for an executing program.
On the other hand, it is possible to have two local variables with the same name
if their scopes do not overlap. The above example can be inelegantly repaired by
inserting an artificial pair of set braces:
public static void main(String[] args)
{ { int n = 2;
System.out.println(n);
} // int n’s scope stops here
double n = 4.1;
System.out.println(n);
// double n’s
}
value is printed
There is also a precise definition of scope for a field variable:
The scope of a field variable declaration extends throughout the class in which the
field is declared, except for those statements where a local variable declaration
with the same name as the field already has scope.
For example, the scope of field count in Figure 18 extends through both methods of
class FieldExample.
Begin Footnote: Unfortunately, the story is a bit more complex than this—the
order in which fields are declared matters, e.g.,
public class ScopeError
{ private int i = j + 1;
private int j = 0;
...
}
generates a complaint from the Java compiler, because the compiler prefers that field
j be declared before field i. End Footnote.
Although it is written in bad programming style, here is a contrived example that
illustrates scopes of fields:
public class Contrived extends JPanel
{ private double d = 3.14;
public Contrived()
{ System.out.println(s);
System.out.println(d);
int d = 2;
System.out.println(d);
s = d + s;
System.out.println(s);
142
setVisible(true);
}
private String s = "X" + d;
public void paintComponent(Graphics g)
{ System.out.println(d + " " + s); }
}
This class uses fields, d and s; when a Contrived object is created and its constructor
method executes, the constructor prints X3.14, then 3.14, then 2, and then 2X3.14.
This shows that the scope of s’s declaration extends throughout the class, whereas
the scope of field d’s declaration extends only as far as the declaration of int d, whose
scope takes precedence at that point.
Later, whenever paintComponent executes, it prints 3.14 2X3.14.
The unpleasantness of the example makes clear that it is best not to use the same
name for both a field variable and a local variable.
4.5
Testing a Program that Uses Input
A programmer quickly learns that merely because a program passes the scrutiny of
the Java compiler, it is not the case that the program will always behave as intended.
For example, perhaps we were editing the temperature-conversion program in Figure 19 and mistakenly typed fahrenheit = ((9.0 * 5.0) * celsius) + 32 when we
really meant fahrenheit = ((9.0 / 5.0) * celsius) + 32. The resulting program
will compile and produce wrong answers for its inputs. Therefore, a programmer has
the obligation of testing a program for possible errors.
Program testing proceeds much like a tool company tests its new tools: A tool
is tried on a variety of its intended applications to see if it performs satisfactorily or
if it breaks. Of course, a tool cannot be tested forever, but if a tool works properly
in testing for some extended period, then the tool company can confidently issue
a guarantee with the tools it sells. Such a guarantee typically lists the tolerances
and range of activities for which the tool will properly behave and warns the tool’s
user that performance cannot be guaranteed outside the recommended tolerances and
activities.
To test a computer program, one supplies various forms of input data and studies
the resulting outputs. The person testing the program might go about this in two
ways:
1. The tester studies the statements of the program, and invents inputs that might
make the statements malfunction. For example, if a program’s statements compute upon dollars-and-cents amounts, like the change-making program does, the
4.5. TESTING A PROGRAM THAT USES INPUT
143
tester tries to trick the program by submitting zeroes or negative inputs. Or, if
the program contains a statement like int x = y / z, the tester invents inputs
that might cause z to have a value of 0, which would cause a division-by-zero
error. Also, the tester invents enough inputs so that every statement in the
program is executed with at least one input. (This helps check the behavior of
the catch sections of exception handlers, for example.)
This style of testing is sometimes called “glass-box” or “white-box” testing,
because the tester looks inside the program and tries to find weaknesses in the
program’s internal construction.
2. Without looking at the program, the tester pretends to be a typical user and
supplies inputs that mimic those that the program will receive in its actual use.
The inputs will include common mistakes that a user might make (e.g., typing
a fractional number as an input when an integer is required).
This style of testing is sometimes called “black-box” testing, because the internal
structure of the program is not seen, and the tester focusses instead on the
program’s external, visible behaviors.
In practice, a combination of glass-box and black-box testing is used.
The primary purpose of testing is to identify errors in a program and repair them,
but testing also identifies the limits under which a program might be expected to
behave properly. For example, a change-making program must behave correctly for
positive dollars-and-cents inputs, but if it misbehaves with ridiculous inputs, e.g. a
negative dollars value, it is not a disaster—the program can be given to its users with
the caveat that correct behavior is guaranteed for only precisely defined, reasonable
forms of input data.
Unfortunately, even systematic testing may miss a problematic combination of
input data, so testing a program never brings complete confidence in the program’s
performance. Like the guarantee accompanying the newly purchased tool, the guarantee with a newly purchased program is inevitably limited.
The previous sentence should make you uneasy—perhaps there is a way of validating a program so that it is certain to work correctly for all possible inputs? Indeed,
one can employ techniques from mathematical logic to validate a program in this way,
but the effort is nontrivial. Nonetheless, some aspects of logical validation of code
will be used in subsequent chapters to aid the program development process.
Exercise
Rewrite the change-making application of Figure 3, Chapter 3, so that it requests two
integer inputs (a dollars value and a cents value) and makes change. Next, define some
“black box” test cases (of input data) for the change-making application in Figure 2,
Chapter 4; define some “white box” test cases. Based on your experiments, write a
144
list of the forms of interactive input will cause the program to operate properly and
write a list of the forms of input that might cause unexpected or incorrect answers.
4.6
Summary
The important aspects of this chapter are summarized in the following subsections.
New Constructions
• class definition by inheritance (from Figure 9):
public class TestPanel extends JPanel
{ ... }
• constructor method (from Figure 17):
/** ClockWriter draws a clock in a panel.
public class ClockWriter extends JPanel
{ public ClockWriter()
{ ... }
*/
...
}
• object self-reference (from Figure 17):
public ClockWriter()
{ ...
JFrame clocks_frame = new JFrame();
clocks_frame.getContentPane().add(this);
...
}
New Terminology
• input view: the component(s) of an application that receive input data
• output view: the component(s) of an application that display output data
• interactive input: input data that the user supplies while a program executes—
the user “interacts” with the executing program.
4.6. SUMMARY
145
• dialog: a window that displays a short message and accepts interactive input, say
by the user typing text or pressing a button. When the user finishes interaction
with the dialog, it disappears.
• null: a special Java value that denotes “no value.” Attempting arithmetic with
null or sending a message to it generates an exception (run-time error).
• constructor method: a method within a class that is executed when an object is
constructed from the class. The constructor method’s name must be the same
as the name of the class in which it appears.
• inheritance: a technique for connecting two classes together so that one is a
“customization” or extension of another. If class C2 inherits/extends class C1,
then any object constructed from class C2 will have all the structure defined
within class C1 as well as the structure within class C2.
• subclass and superclass: if class C2 extends C1, then C2 is a subclass of C1 and
C1 is a superclass of C2.
• this: a special Java value that denotes “this object.”
• painting (a window): drawing text, colors, and shapes on a graphics window;
done with the paintComponent method.
• graphics pen: the object used by a window’s paint method for painting
• frame: Java’s name for an application’s graphics window.
• pixel: one of the “dots” on the display screen; used as a measurement unit for
distances within a window.
• field variable: a variable declared in a class independently of the class’s methods.
A field can be referenced and assigned to by all methods in the class.
• is-a relationship: if class C2 extends class C1, we say that C2 is a C1.
• uses-a relationship: if class C2 sends messages to objects constructed from class
C1, we say that C2 uses a C1.
• has-a relationship: if class C2 possesses a field whose value is a C1-object that
it constructed, we say that C2 has a C1.
146
Points to Remember
• An application can receive interactive input and display simple output by means
of dialogs. In Java, class JOptionPane simply generates dialogs for input and
output.
• An application can display texts, colors, and shapes in a graphics window.
Graphics can be drawn on a panel, constructed from class JPanel. A panel
must be inserted into a frame, created from class JFrame, to be displayed.
We use inheritance to customize JPanel so that it displays graphics. The usual
customizations are
– a constructor method, which sets the window’s title and size, and
– a paintComponent method, which lists the shapes and colors to be painted
on the panel each time it appears on the display.
• Any object, like a graphics window, can have an internal state. The internal
state is represented as field variables, which are declared separately from the
methods that use them.
New Classes for Later Use
• JOptionPane: Found in package javax.swing. Helps construct dialogs with its
methods,
– showInputDialog(PROMPT): displays the string, PROMPT, and returns the
string typed by the user
– showMessageDialog(null, MESSAGE): displays the string, MESSAGE, to the
user
• class JPanel: Found in package javax.swing. Constructs an empty component, but is simply extended by inheritance to display graphics. Table 21.
• class JFrame: Found in package javax.swing. Constructs empty, zero-sized
graphics windows, but it can be sent messages to set its size and display itself.
Some of its methods are listed in Table 21.
• Graphics: A class found in package java.awt. The graphics pen used in a
window’s paint method is in fact an object constructed from class Graphics.
Some of the class’s methods are listed in Table 16.
4.7. PROGRAMMING PROJECTS
4.7
147
Programming Projects
1. For each of the programming projects you completed from the previous chapter,
Chapter 3, revise that project to use interactive input. For example, perhaps
you built the application that computes the distance an automobile travels
based on its initial velocity, Vi , its acceleration, a, and the time, t: distance =
Vi t+(1/2)a(t2 ) Convert that application into an interactive one: When it starts,
the application greets its user as follows:
Please type an initial velocity, in miles per hour (a double):
When the user enters a number, the application replies,
Next, please type the automobile’s acceleration (a double):
and
Finally, please type the elapsed time in hours (a double):
The application grabs the inputs, calculates its answer and displays it.
2. Write an application that helps a child learn multiplications. The program asks
the child to type two integers and what the child believes is the product of
the integers. Then, the program checks the child’s calculations and prints the
result. The interaction might go as follows:
Type an integer:
(the child types) 13
Type another:
(the child types) 11
Guess the answer:
(the child types) 143
The guess was true---the answer is 143.
3. In the java.lang package there is a method named Math.random: using Math.random()
produces a (pseudo-)random double between 0.0 and 1.0. Use this method to
modify the multiplication-drill program in the previous exercise so that the
program randomly picks the two integers that the child must multiply. For
example, the program says:
What is 4 times 8?
and the child types her guess, say, 33, and the program replies:
148
What is 4 times 8?
33
The guess was incorrect---the answer is 32.
The child starts the application by stating the upper bound, n, for the integers,
and this ensures that the program generates two integers in the range 0..n.
4. Write an application that uses interactive input to help a home buyer calculate
her expenses for buying and maintaining a house. The program asks the user
to type the following input values:
• the price of the house
• the down payment on the loan for the house
• The number of years for repaying the loan.
• the monthly utility expenses
In response, the application prints
• The monthly expense of keeping the house. (Note: this amount does not
include the down payment.)
• The annual expense of keeping the house. (Again, exclude the down payment.)
• The total expense of keeping the house for the number of years needed to
repay the loan. (Include the down payment in this amount.)
The application does not worry about inflation costs in its calculations.
Use this formula to compute the monthly payment on the loan for the house:
payment = {( 1 + i)y ∗ p ∗ i}{(1 + i)y − 1) ∗ 12} where y is the number of years
for repaying the loan, and p is loan’s principal, that is, the price of the house
minus the down payment.
5. Write an application that uses interactive input to help the user calculate her
monthly living expenses. The inputs requested by the program are
• the amount of money the user has budgeted each month for expenses
• the monthly rent
• the weekly groceries bill
• the annual cost of clothing, furniture, and other “dry goods”
• weekly transportation costs (e.g., gasoline, parking fees, bus tickets)
• monthly transportation costs (car payments, car insurance)
4.7. PROGRAMMING PROJECTS
149
• monthly utility costs
• weekly costs for entertainment, including lunches, coffees, etc.
The program produces as its output these numbers:
• the total monthly expenses
• the difference—positive or negative—between the money budgeted for expenses and the actual expenses
Assume that a year has 12.17 months and a month has 30 days, that is, 4.29
weeks.
6. The following projects are meant to give you experience at using methods for
painting. Program a graphics window that displays
(a) 4 concentric circles, drawn in the center of the window
(b) a transparent, 3-dimensional cube
(c) three eggs, stacked on top of one another
(d) a “snowman,” that is, three white spheres crowned by a black top hat
(e) a 4 by 4 red-and-black chessboard
(f) a “bull’s eye,” that is, 5 alternating red and yellow concentric circles, on a
black background
(g) the playing card symbols, spade, heart, diamond, and club (Hint: Build a
heart from a filled arc topped by two filled ovals; use similar tricks for the
other symbols.)
(h) a Star of David, drawn in gray on a white background
(i) a silly face in various colors
(j) a pink house with a red door resting on green grass with a blue sky (Hint:
for the house’s roof, use a partially filled oval.)
(k) your initials in blue block letters on a yellow background
7. As noted in Chapter 2, this complex Java statement,
try { Thread.sleep(2000); }
catch (InterruptedException e) { }
causes an object to pause for 2 seconds. For any of the graphics windows that
you wrote in response to the previous Project exercise, insert copies of this
“pause” statement in multiple places within the paint method. This will make
the parts of your painting appear one part each 2 seconds.
150
8. Draw a “bar graph” clock that displays the time like this:
(The time displayed is 11:10.)
9. To draw a line of length L, at an angle of D degrees, starting at position x,y, we
must calculate the end point of the line, x’,y’:
x’,y’
/
/ (angle D)
/
x,y . . . . . .
Here are the equations to do this:
x’ = x + L*(cosine( (D*PI)/180 ) )
y’ = y - L*(sine( (D*PI)/180 ) )
(Use Math.sin and Math.cos calculate sine and cosine, respectively. The value
PI is written Math.PI.) Use these equations to
(a) modify class ClockWriter in Figure 17 so that the graphical clock is a
circle plus one line for the minutes’ hand and one line for the hours’ hand.
(b) draw a sun (a circle filled with yellow) that has 6 equally spaced “rays of
sunshine” (yellow lines drawn from the center of the circle spaced every 60
degrees).
(c) improve the display of class ClockWriter so that the numerals 1 through
12 are drawn around the border of the clock face in their correct positions.
(Note: if you find it too tedious to draw all 12 numerals, draw just 2, 4,
and 10.)
151
4.8. BEYOND THE BASICS
Figure 4.20: methods for JFrame
class JFrame
Constructor
JFrame()
Method
getGraphics()
paint(Graphics g)
repaint()
setLocation(int x, int
y)
setSize(int width, int
height)
setTitle(String title)
setVisible(boolean yes)
4.8
constructs an empty frame of size 0-by-0 pixels
Returns as its answer the (address of) this frame’s
graphics pen
paints on the frame with the graphics pen, g
grabs the graphics pen and sends it in a message to
paint.
sets the frame’s location on the display so that its upper
left corner appears at location x, y
sets the frame’s size to width by height, in pixels
sets the frame’s title to title
If yes has value true, the frame appears on the display;
if yes has value false, the frame disappears
Beyond the Basics
4.8.1 Partial API for JFrame
4.8.2 Methods for GregorianCalendar
4.8.3 Colors for Graphics
4.8.4 Applets
The following optional sections develop details of several of the concepts introduced in
this chapter. The last section explains how to convert an application into an applet,
which is a Java program that can be started within a web browser.
4.8.1
Partial API for
JFrame
Class JFrame possesses a huge collection of methods, and Table 20 summarizes the
ones we will use for the next five chapters.
All these methods but getGraphics were used in this chapter. As for the latter,
it can be used at any time to “grab” a frame’s graphics pen. For example, a main
method can interfere with the appearance of a frame by grabbing the frame’s pen
and using it:
JFrame f = new JFrame();
152
Figure 4.21: class GregorianCalendar
class GregorianCalendar
Constructor
new GregorianCalendar(),
Methods
getTime()
get(E), where E can be any of the
arguments listed below
Arguments for get method
Calendar.SECOND
Calendar.MINUTE
Calendar.HOUR
Calendar.HOUR OF DAY
Calendar.DAY OF MONTH
Calendar.MONTH
Calendar.YEAR
creates an object holding the exact time when the
object was created
returns the date-and-time held within the object
returns the integer value requested by E
requests the seconds value of the time
requests the minutes value of the time
requests the hours value (12-hour clock) of the time
requests the hours value (24-hour clock) of the time
requests the day of the month of the time
requests the month of the time (Months are numbered 0 to 11.)
requests the current year of the time
Additional methods and arguments can be found in the API for class GregorianCalendar
in the package java.util.
Graphics pen = f.getGraphics();
pen.drawString("Hello from main!", 100, 100);
This example shows that getGraphics returns a result that is a value of type Graphics,
which is indeed what is indicated in the Table by the phrase, getGraphics(): Graphics.
The methods in the Table are part of any object created from a subclass of JFrame.
Additional methods for JFrame are found in JFrame’s API in package javax.swing and
are explained in Chapter 10.
4.8.2
Methods for
GregorianCalendar
For several chapters we have made good use of class GregorianCalendar. A summary
of its most useful methods appears in Table 21.
4.8.3
Colors for Graphics
Here are the basic colors you can use to color the background of a window and fill
a graphics pen: black, blue, cyan, darkGray, gray, green, lightGray, magenta,
orange, pink, red, yellow, white. All must be prefixed by Color., e.g., Color.orange.
4.8. BEYOND THE BASICS
153
In Java, colors are objects, and it is possible to send a color a message that asks it
to create a variant of itself with a slightly different tint, e.g., Color.orange.brighter()
and Color.orange.darker(). Try it: g.setColor(Color.orange.darker()). (Of course,
you can still use the Color.orange object, because it is distinct from the Color.orange.darker()
object.)
In addition, you can invent your own colors by stating new Color(r1, g1, b1),
where all of r1, g1, and b1 are numbers between 0 and 255 and are the “red value,”
“green value,” and “blue value,” respectively, of the color. For example, new Color(255,
175, 175) produces pink and can be used just like Color.pink.
You can declare variables to hold (addresses of) colors:
private Color BACKGROUND_COLOR = Color.yellow;
4.8.4
Applets
A Java applet is a program whose graphics window appears within a Web page.
Here is some background: You can use a web browser to read files on your computer. Files that contain hyper-text markup language (HTML) commands will be
displayed specially by the browser—you will see multiple columns, multiple type
fonts, pictures, and graphics. In particular, the HTML-command, applet, starts a
Java program (an applet) and displays its graphics window—the browser makes its
own copy of the program and executes it.
Thanks to the World-Wide-Web protocol, a web browser can locate HTMLformatted files on other computers in foreign locations. If an HTML-file uses an
applet, the file along with the applet are copied into the web browser for display. In
this regard, an applet is a “mobile program,” moving from browser to browser.
154
For practice, let’s make class MyPanel into an applet:
To do this, the “controller” that constructs the panel and displays it is the HTMLfile that contains an applet command. Figure 22 shows how the HTML-file might
appear. The statement, <applet code = "MyApplet.class" width=300 height=200>,
marks the position where the web browser should insert the graphics window; the
width and height of the window are indicated so that the browser can reserve the
correct space for the window.
A class that defines a panel ¿ requires several small changes to make it into an
applet:
• It extends JApplet (rather than JFrame).
• It does not need to be inserted into a frame. Its “frame” is the web browser
itself.
• Its constructor method, if any, is renamed into a method named init.
• Its paintComponent method is renamed paint.
4.8. BEYOND THE BASICS
155
Figure 4.22: The HTML-file ¡tt¿DisplayApplet.html¡/tt¿
<head>
<title>My Graphics Window </title>
</head>
<body>
Here is my graphics window as an applet:
<p>
<applet code = "MyApplet.class" width=300 height=200>
Comments about the applet go here; these are shown on the web
page if the applet cannot be displayed. </applet>
</body>
Figure 23 shows the applet; compare it to Figure 12.
Remember to compile MyApplet.java so that MyApplet.class is found in the same
folder (directory) as DisplayApplet.html When a web browser reads DisplayApplet.html,
the browser will execute MyApplet.class, displaying the applet.
Here is a comment about testing applets: A web browser copies an applet only
once, so updating an applet and refreshing the HTML-file that uses it will not show
the updates. It is better to test and revise an HTML page and its applet with a
testing program called an appletviewer. (If you use an IDE, set it to test applets; if
you use the JDK, use the command appletviewer DisplayApplet.html.) Once the
applet works to your satisfaction, only then use the web browser to display it.
Finally, you are warned that there are many different versions of web browsers,
and not all web browsers understand all the packages of the Java language. For
this reason, applets that tested correctly with an appletviewer may misbehave when
started from a web browser, and there is little one can do about it.
156
Figure 4.23: a simple applet
import java.awt.*;
import javax.swing.*;
/** MyApplet displays a window with geometric figures */
public class MyApplet extends JApplet
{
// An applet has no constructor method---instead, it has an
/** init constructs the applet for display */
public void init()
{ }
init
method:
// paintComponent is renamed paint:
/** paint fills the window with the items that must be displayed
* @param g - the ‘‘graphics pen’’ that draws the items onto the window */
public void paint(Graphics g)
{ int frame width = 300;
int frame height = 200;
g.setColor(Color.white); // paint the white background:
g.fillRect(0, 0, frame width, frame height);
g.setColor(Color.red);
int left edge = 105;
// the left border where the shapes appear
int top = 70;
// the top where the shapes appear
// draw a rectangle:
int width = 90;
int height = 60;
g.drawRect(left edge, top, width, height);
// draw a filled circle:
int diameter = 40;
g.fillOval(left edge + width - diameter, top, diameter, diameter);
}
}
Chapter 5
Component Structure: Method and
Class Building
5.1 Methods
5.2 Public Methods
5.2.1 Basic Public Methods
5.2.2 Constructor Methods
5.3 Parameters to Methods
5.3.1 Forms of Parameters
5.4 Case Study: General-Purpose Output Frame
5.5 Results from Methods: Functions
5.6 Private Methods
5.7 Summary
5.8 Programming Projects
5.9 Beyond the Basics
In this chapter, we write classes that contain methods of our own design. Our
objectives are
• to write public and private methods that accept arguments (parameters) and
reply with results;
• to write classes containing methods that we use over and over;
• to read and write interface specifications, which describe the behaviors of classes
and their methods.
These techniques will help us write applications whose component structure consists
of classes and methods completely of our own design.
158
5.1
Methods
At the beginning of this text, we remarked that an object owns a collection of methods for accomplishing work. For example, a program for word processing will have
methods for inserting and removing text, saving files, printing documents, and so
on. A bank accounting program will have methods for depositing and withdrawing
money from a bank account. And, a graphics-window object has methods for setting
the window’s size, setting its title bar, painting the window, and so on.
The term, “method,” comes from real-life examples, such as the plumber (joiner)
who has methods for fixing toilets, stopping leaks, and starting furnaces. A plumber’s
methods might be divided into “public” ones, that is, the methods that customers
ask of the plumber, and “private” ones, that is, shortcuts and tricks-of-the-trade the
plumber uses to complete a customer’s request.
A plumber is a person, an entity, an object. In addition to plumbers, the world
is full of other entities—carpenters, masons, electricians, painters, and so on. Each
of these entities have methods, and the entities work together on major tasks, e.g.,
building a house.
In a similar way, a program consists of multiple objects that communicate and cooperate by asking one another to execute methods. For example, in the previous chapters, we wrote applications that sent print and println messages to the preexisting
object, System.out. And, we constructed new objects, like new GregorianCalendar(),
from preexisting classes in Java’s libraries and sent messages to the new objects. These
objects cooperated with our applications to solve problems.
We must learn to write our own classes with methods of our own design. So far,
the methods we have written ourselves have been limited to two specific instances:
First, we wrote many applications that contained the public method, main. When
we start an application, an implicit message is sent to execute main. Second, we
learned to write one form of graphics window, which owns a constructor method and
a paintComponent method; the coding pattern looked like this:
import java.awt.*;
import javax.swing.*;
public class MyOwnPanel extends JPanel
{ public MyOwnPanel()
{ ... // instructions for initializing field variables,
// constructing the panel’s frame, and displaying it
}
public void paintComponent(Graphics g)
{ ... // instructions for painting on the panel
}
}
Recall that the constructor method executes when a new MyOwnPanel() object is con-
159
5.2. PUBLIC METHODS
structed, and the paintComponent method executes each time the panel must be
painted on the display.
In this chapter, we learn to write methods that are entirely our own doing and we
learn how to send messages to the objects that own these methods. In this way, we can
advance our programming skills towards writing programs like the word processors
and accounting programs mentioned at the beginning of this Section. We begin by
learning to write the most widely used form of method, the public method.
5.2
Public Methods
Consider this well-used statement:
System.out.println("TEXT");
The statement sends a println("TEXT") message to the object System.out, asking
System.out to execute its method, println.
The object that sends the message is called the client, and the object that receives
the message is the receiver (here, System.out). The receiver locates its method whose
name matches the one in the message and executes that method’s statements—we
say that the method is invoked. (Here, System.out locates its println method and
executes it, causing TEXT to appear in the command window; we say that println is
invoked.)
An application can send println messages to System.out because println is a
public method, that is, it is available to the “general public.”
We have used many such public methods and have written a few ourselves, most
notably, main and paintComponent. Consider again the format of the latter, which we
use to paint graphics on a panel:
public void paintComponent(Graphics g)
{ STATEMENTS // this is the body
}
// this is the header line
The syntax is worth reviewing: The method’s header line contains the keyword,
public, which indicates the general public may send paintComponent messages to
the graphics window that possesses this method. (In this case, the “general public”
includes the computer’s operating system, which sends a paintComponent message
each time the graphics window must be repainted on the display.) The keyword,
void, is explained later in this chapter, as is the formal parameter, Graphics g. The
statements within the brackets constitute the method’s body, and these statements
are executed when the method is invoked.
A public method must be inserted within a class. As we saw in Chapter 4, a class
may hold multiple methods, and a class may hold fields as well as methods. When
an object is constructed from the class, the object gets the fields and methods for its
own. Then, other objects can send messages to the object to execute the methods.
160
We begin with some basic examples of public methods.
5.2.1
Basic Public Methods
A public method that we write should be capable of one basic “skill” or “behavior”
that clients might like to use. Here is an example that illustrates this principle. (To
to help us focus on crucial concepts, we dispense with graphics for the moment and
work with basic techniques from Chapter 2.)
Pretend that you collect “Ascii art” insects, such as bees, butterflies, and ladybugs
(“ladybirds”):
,-.
\_/
>{|||}/ \
‘-^ hjw
_ " _
(_\|/_)
(/|\) ejm97
‘m’
(|)
sahr
(Note: the initials next to each image are the image’s author’s.) From time to time,
you might like to display one of the insects in the command window. Each time
you do so, you might write a sequence of System.out.println statements to print
the image, but this is foolish—it is better to write a method that has the ability to
print such an image on demand, and then your applications can send messages to this
method.
For example, here is the method we write to print the first image, a bee:
/** printBee prints a bee */
public void printBee()
{ System.out.println(" ,-.");
System.out.println(" \\_/");
System.out.println(">{|||}-");
System.out.println(" / \\");
System.out.println(" ‘-^ hjw");
System.out.println();
}
We always begin a method with a comment—the method’s specification—that documents the method’s purpose or behavior. The method’s header line comes next; it
states that the method is public and gives the method’s name, which we invent. For
5.2. PUBLIC METHODS
161
the moment, do not worry about the keyword, void, or the brackets, (); they will be
explained later. The method’s body contains the instructions for printing the bee.
A class that holds methods for printing the above images appears in Figure 1. The
Figure shows the above method plus two others grouped into a class, AsciiArtWriter.
There is also a fourth method, a constructor method, which we discuss momentarily.
When we compare class AsciiArtWriter to the graphics-window classes from
Chapter 4, we see a similar format: we see a constructor method followed by public
methods. And, we construct objects from the class in a similar fashion as well:
AsciiArtWriter writer = new AsciiArtWriter();
This statement constructs an object named writer that holds the three public methods. When the object is constructed, the constructor method is invoked, causing an
empty line to print in the command window.
Think of writer as an object, like System.out. This means we can send messages
to it:
writer.printBee();
Notice the syntax of the invocation: The format is the object’s name, followed by
the method name, followed by the parentheses. The parentheses help the Java compiler understand that the statement is indeed a method invocation—this is why the
parentheses are required.
The above format is the usual way of sending a message to an object, and it is
a bit unfortunate that the paintComponent methods we wrote in Chapter 4 were not
invoked in this usual way—paintComponent is a special case, because it is normally
invoked by the computer’s operating system when a window must be repainted on
the display.
Figure 2 shows an application that sends messages to an AsciiArtWriter object
to print two bees and one butterfly.
The advantage of writing class AsciiArtWriter is that we no longer need to remember the details of printing the Ascii images—they are saved in the class’s methods.
And, we can reuse the class over and over with as many applications as we like. This
is a primary motivation for writing methods and grouping them into a class. class
AsciiArtWriter is a pleasant addition to our “library” of program components.
Exercises
1. Say that we alter the class in Figure 2 to look like this:
public class DrawArt2
{ public static void main(String[] args)
{ AsciiArtWriter w = new AsciiArtWriter();
w.printButterfly();
162
Figure 5.1: class that draws Ascii art
/** AsciiArtWriter contains methods for drawing Ascii art */
public class AsciiArtWriter
{ /** Constructor AsciiArtWriter does an ‘‘initialization.’’ */
public AsciiArtWriter()
{ System.out.println(); }
/** printBee prints a bee */
public void printBee()
{ System.out.println(" ,-.");
System.out.println(" \\ /"); // the \ must be written as \\
System.out.println(">{|||}-");
System.out.println(" / \\");
System.out.println(" ‘-^ hjw");
System.out.println();
}
/** printButterfly prints a butterfly */
public void printButterfly()
{ System.out.println("
\""); // the " must be written as \"
System.out.println(" ( \\|/ )");
System.out.println("
(/|\\) ejm97");
System.out.println();
}
/** printLadybug prints a ladybug */
public void printLadybug()
{ System.out.println(" ‘m\’"); // the ’ must be written as \’
System.out.println(" (|) sahr");
System.out.println();
}
}
5.2. PUBLIC METHODS
163
Figure 5.2: application that prints Ascii art
/** DrawArt prints some Ascii art and a message */
public class DrawArt
{ public static void main(String[] args)
{ AsciiArtWriter writer = new AsciiArtWriter();
writer.printBee();
System.out.println("This is a test.");
writer.printButterfly();
writer.printBee();
}
}
w.printButterfly();
}
}
What is printed on the display?
2. Explain what appears in the command window when this application is executed. How many AsciiArtWriter objects are constructed?
public class TestArt
{ public static void main(String[] args)
{ AsciiArtWriter writer = new AsciiArtWriter();
writer.printBee();
new AsciiArtWriter().printButterfly();
writer.printLadyBug();
}
}
3. Here is a class with a public method:
import javax.swing.*;
public class HelperClass
{ public HelperClass()
{ } // nothing to initialize
/** computeSquareRoot reads an input integer and displays its square root. */
public void computeSquareRoot()
{ String s = JOptionPane.showInputDialog("Type a number:");
double d = new Double(s).doubleValue();
double root = Math.sqrt(d);
164
JOptionPane.showMessageDialog(null,
"The square root of " + d + " is " + root);
}
}
Write an application whose main method invokes the method to help its user
compute two square roots.
4. Write the missing method for this application:
import javax.swing.*;
/** NameLength calculates the length of two names.
* Input: two names, each typed into an input dialog
* Output: dialogs that display the names and their lengths.
public class NameLength
{ public static void main(String[] args)
{ HelperClass c = new HelperClass();
c.readNameAndDisplayItsLength();
c.readNameAndDisplayItsLength();
JOptionPane.showMessageDialog(null, "Finished!");
}
*/
public class HelperClass
{ ...
/** readNameAndDisplayItsLength reads one name and displays the
* name with its length
* Hint: for a string, x, x.length() returns x’s length */
...
}
5.2.2
Constructor Methods
A constructor method is a special case of a public method. When we construct an
object from a class, e.g.,
AsciiArtWriter writer = new AsciiArtWriter();
the object is constructed in computer storage and the class’s constructor method is immediately invoked. For this reason, you should read the phrase, new AsciiArtWriter()
in Figure 2, as both constructing a new object and sending a message to the method
named AsciiArtWriter().
AsciiArtWriter’s constructor does little, but as we saw repeatedly in Chapter
4, a constructor method is often used to “complete” the construction of an object:
The constructor methods for all graphics windows in Chapter 4 contained statements
5.2. PUBLIC METHODS
165
that set the windows’ sizes, background colors, framing, and visibilities. For example,
recall Figure 18, Chapter 4, which constructs a graphics window that displays a count
of the window’s paintings:
import java.awt.*;
import javax.swing.*;
/** FieldExample displays how often a window is painted on the display */
public class FieldExample extends JPanel
{ private int count; // this field variable holds the count of how
// often the window has been painted.
/** FieldExample constructs the window. */
public FieldExample()
{ count = 0; // the window has never been painted
JFrame my_frame = new JFrame();
my_frame.getContentPane().add(this);
int height = 200;
my_frame.setSize((3 * height)/2, height);
my_frame.setVisible(true);
}
... // See Figure 18, Chapter 4, for the rest of the class
}
The FieldExample’s constructor initializes count to zero, then frames, sizes, and displays the panel. Without the constructor to complete the FieldExample object’s
construction, the object would be useless. Review Chapter 4 to see again and again
where constructor methods are used this way.
Because a constructor method is invoked when an object is constructed, it is
usually labelled as a public method. A constructor method must have the same
name as the class that contains it, and again, this is why a statement like new
AsciiArtWriter() should be read as both constructing a new object and invoking
its constructor method. For reasons explained later in this chapter, the keyword,
void, never appears in the header line of a constructor method.
If the constructor has nothing to do, we can write it with an empty body:
public AsciiArtWriter()
{ }
When a new AsciiArtWriter() object is constructed, the do-nothing constructor
method is invoked and does nothing. The Java language allows you to write a class
that has no constructor method at all, but this style of programming will not be
followed in this text.
166
Execution Trace of Method Invocation
To reinforce our intuitions regarding method invocation, we now study the steps the
Java interpreter takes when it executes the application in Figure 2.
When the application is started, a DrawArt object appears in primary storage, and
its main method is started:
DrawArt
main
{ 1 > AsciiArtWriter writer = new AsciiArtWriter();
...
}
We annotate the execution marker with the numeral, 1, so that we can see the effects
of method invocation.
The first statement creates a storage cell named writer and starts construction
of an AsciiArtWriter object:
DrawArt
a1 : AsciiArtWriter
main
{ AsciiArtWriter writer == ?
AsciiArtWriter
{ 2 > System.out.println(); }
1 > writer = AWAIT COMPLETION OF METHOD
...
}
public void printBee() {...}
public void printButterfly() {...}
public void printLadybug() {...}
The object is constructed at an address, say a1, and its construction method is invoked. A new execution marker, 2>, shows that execution has been paused at position
1> and has started within the invoked method at 2>.
Within the constructor, the println invocation executes next. When this statement completes, the constructor method finishes, the address, a1, is returned as the
result, and execution resumes at point 1>:
DrawArt
a1 : AsciiArtWriter
main
{ AsciiArtWriter writer == ?
1 > writer = a1;
...
}
public void printBee() {...}
public void printButterfly() {...}
public void printLadybug() {...}
Once finished, a constructor method always, automatically returns the address of the
object constructed and the method disappears from the object. Notice that a1 is
inserted at the position of the invocation; this lets the assignment complete and the
execution proceed to the next statement:
DrawArt
a1 : AsciiArtWriter
main
{ AsciiArtWriter writer == a1
...
1 > writer.printBee();
...
}
public void printBee() {...}
public void printButterfly() {...}
public void printLadybug() {...}
167
5.2. PUBLIC METHODS
To execute, this invocation, the address of the receiver, writer, must be computed;
it is a1, so the invocation computes to a1.printBee(), meaning that execution starts
within printBee in a1:
DrawArt
a1 : AsciiArtWriter
main
{ AsciiArtWriter writer == a1
...
1> AWAIT COMPLETION OF METHOD
public void printBee() {...}
{ 2> System.out.println(...);
...
}
public void printButterfly() {...}
public void printLadybug() {...}
The statements in printBee execute one by one. Once finished, execution restarts at
1>, and printBee “resets” for future use:
DrawArt
a1 : AsciiArtWriter
main
{ AsciiArtWriter writer == a1
...
1> System.out.println("This is a test.");
...
}
public void printBee() {...}
public void printButterfly() {...}
public void printLadybug() {...}
In this way, the messages to writer are executed one by one.
Exercises
1. Here is a class with a constructor and public method:
public class PrintClass
{ public PrintClass()
{ System.out.println("A"); }
public void printB()
{ System.out.println("B"); }
}
What is printed by this application?
public class TestPrintClass
{ public static void main(String[] args)
{ PrintClass p = new PrintClass();
p.printB();
p.printB();
new PrintClass().printB();
PrintClass q = p;
}
}
168
2. Here is a class with a constructor and public method:
public class Counter
{ private int count;
public Counter()
{ count = 0; }
public void count()
{ count = count + 1;
System.out.println(count);
}
}
What is printed by this application?
public class TestCounter
{ public static void main(String[] args)
{ Counter a = new Counter();
a.count();
a.count();
Counter b = new Counter();
b.count();
a.count();
Counter c = a;
c.count();
a.count();
}
}
5.3
Parameters to Methods
When an object is sent a message, the message often contains additional information
in parentheses, called arguments. We have used arguments in messages many times;
the standard example is System.out.println("TEXT"), which sends the argument,
"TEXT", in its message to the System.out object.
The technical name for an argument is actual parameter, or parameter, for short.
When an object receives a message that includes actual parameters, the object gives
the parameters to the method named in the message, and the method binds the
parameters to local variables. We can see this in the following example.
Say that we wish to attach names to the bees that we draw with the AsciiArtWriter.
For example, this bee is named “Lucy”:
5.3. PARAMETERS TO METHODS
169
Figure 5.3: method with a parameter
/** printBeeWithName prints a bee with a name attached to its stinger.
* @param name - the name attached to the stinger */
public void printBeeWithName(String name)
{ System.out.println(" ,-.");
System.out.println(" \\ /");
System.out.println(">{|||}-" + name + "-");
System.out.println(" / \\");
System.out.println(" ‘-^ hjw");
System.out.println();
}
,-.
\_/
>{|||}-Lucy/ \
‘-^ hjw
To do this, we modify method printBee from Figure 1 so that it attaches a name
to its bee by means of a parameter. The method’s header line changes so that it
contains information about the parameter within its brackets:
public void printBeeWithName(String name)
The information in the parentheses, String name, is actually a declaration of a local
variable, name. The declaration is called a formal parameter—it binds to (is assigned
the value of) the incoming argument (actual parameter).
To understand this, say that we add the modified method in the Figure to class
AsciiArtWriter, and we invoke it as follows:
AsciiArtWriter writer = new AsciiArtWriter();
writer.printBeeWithName("Lucy");
The invocation, writer.printBeeWithName("Lucy"), gives the actual parameter, "Lucy",
to the method, printBeeWithName, which generates this initialization inside of a new
local variable inside the method’s body:
String name = "Lucy";
Then the variable, name, can be used within the body of printBeeWithName and it
causes "Lucy" to be used. Figure 3 shows the modifications.
The main motivation for using parameters is that a method can be invoked many
times with different values of actual parameters and can perform related but distinct
computations at each invocation. For example,
170
writer.printBeeWithName("Fred Mertz");
prints a bee similar to Lucy’s bee but with a different name.
We can write a method like printBeeWithName in advance of its invocations, because the method need not know exactly the name it attaches to the bee it draws,
but it assumes that the formal parameter—a variable—will contain the value when
the time comes to draw the bee.
As part of our documentation policy, the comments preceding the method include
a short description of each parameter and its purpose. For reasons explained at the
end of the chapter, we begin each parameter’s description with the text, * @param.
When printBeeWithName is invoked, it must be invoked with exactly one actual parameter, which must compute to a string. This was the case with writer.printBeeWithName("Lucy"),
but we might also state writer.printBeeWithName("Fred" + "Mertz"), whose argument is an expression that computes to a string, or we might use variables in the
argument:
String s = "Ricardo":
writer.printBeeWithName("Ricky " + s);
In each of these cases, the actual parameter is an expression that computes to a string
and binds to printBee’s formal parameter, name.
For example, the last invocation, writer.printBeeWithName("Ricky " + s), causes
the actual parameter, "Ricky " + s, to compute to "Ricky " + "Ricardo", which
computes to "Ricky Ricardo", which binds to name within method printBeeWithName.
In effect, the invocation causes this variant of printBeeWithName to execute:
{ String name = "Ricky Ricardo";
System.out.println(" ,-.");
System.out.println(" \\_/");
System.out.println(">{|||}-" + name + "-");
System.out.println(" / \\");
System.out.println(" ‘-^ hjw");
}
This example emphasizes that
Parameter binding works like initialization of a local variable—the actual parameter
initializes the variable (formal parameter), and the local variable is used only
within the body of the method where it is declared.
If we would attempt the invocation, writer.printBeeWithName(3), the Java compiler would announce that a data-type error exists because the actual parameter has
data type int, whereas the formal parameter has data type String. If we truly wanted
to attach 3 as the name of the bee, then we must convert the integer into a string, say
by, writer.printBeeWithName(3 + ""), which exploits the behavior of the + operator.
171
5.3. PARAMETERS TO METHODS
Execution Trace
To ensure that we understand the above example, we draw part of its execution trace.
Say that an application is about to draw a bee with a name:
DrawArt
a1 : AsciiArtWriter
main
{ AsciiArtWriter writer == a1
public
public
public
public
String s ==
"Ricardo"
void
void
void
void
printBee() {...}
printBeeWithName(String name){...}
printButterfly() {...}
printLadybug() {...}
1> writer.printBeeWithName("Ricky " + s);
...
}
First, the receiver object is computed; since writer holds address a1, this is the
receiver:
a1.printBeeWithName("Ricky " + s);
Next, the value of the argument is computed. Since s holds the string, "Ricardo",
the argument computes to "Ricky Ricardo":
DrawArt
a1 : AsciiArtWriter
main
{ AsciiArtWriter writer == a1
public
public
public
public
String s ==
"Ricardo"
void
void
void
void
printBee() {...}
printBeeWithName(String name){...}
printButterfly() {...}
printLadybug() {...}
1> writer.printBeeWithName("Ricky Ricardo");
...
}
Only after the argument computes to its result does the binding of actual to formal
parameter take place. This creates an initialization statement in printBeeWithName:
DrawArt
a1 : AsciiArtWriter
main
{ AsciiArtWriter writer == a1
...
printBeeWithName
{ 2> String name = "Ricky Ricardo";
System.out.println(...);
...
}
...
String s ==
"Ricardo"
1> AWAIT COMPLETION OF METHOD
...
}
The initialization executes as usual,
DrawArt
a1 : AsciiArtWriter
main
{ AsciiArtWriter writer == a1
...
printBeeWithName
String s ==
{ String name = "Ricky Ricardo";
"Ricardo"
2> System.out.println(...);
...
1> AWAIT COMPLETION OF METHOD
...
}
}
...
and at this point, printBee’s body behaves like any other method.
172
Exercises
1. What will this application print?
public class PrintClass
{ public PrintClass() { }
public void printName(String name)
{ String s = " ";
System.out.println(name + s + name);
}
}
public class TestPrintClass
{ public static void main(String[] args)
{ String s = "Fred";
new PrintClass().printName(s + s);
}
}
2. Write the missing bodies of this class’s methods; see the application that follows
for help, if necessary.
import javax.swing.*;
/** NameClass remembers a name and prints information about it. */
public class NameClass
{ private String name; // the name that is remembered
/** Constructor NameClass initializes a NameClass object
* @param n - the name that is to be remembered */
public NameClass(String n)
{ ... }
/** printName prints the name remembered by this object */
public void printName()
{ ... }
/** displayLength prints the integer length of the name remembered.
* (Hint: use the length method from Table 5, Chapter 3.) */
public void displayLength()
{ ... }
}
public class TestNameClass
5.3. PARAMETERS TO METHODS
173
{ public static void main(String[] args)
{ NameClass my_name = new NameClass("Fred Mertz");
System.out.print("my name is ");
my_name.printName();
System.out.print("my name has this many characters in it: ");
my_name.displayLength();
}
}
5.3.1
Forms of Parameters
What can be a parameter? The answer is important:
Any value that can be assigned to a variable can be an actual parameter.
This means numbers, booleans, and even (addresses of) objects can be actual parameters. Of course an actual parameter must be compatible with the formal parameter
to which it binds. To understand this, we study a series of examples based on this
method for printing an inverse:
import java.text.*;
// Note: This statement is inserted at the beginning
// of the class in which the following method appears.
/** printInverse prints the inverse, 1.0/i, of an integer, i,
* formatted to three decimal places.
* @param i - the integer that is inverted */
public void printInverse(int i)
{ DecimalFormat formatter = new DecimalFormat("0.000"); // see Chapter 4
double d = 1.0 / i;
String s = formatter.format(d); // formats d with three decimal places
System.out.println(s);
}
Parameter i is declared as an integer, so any invocation of inverseOf must use an
actual parameter that computes to an integer. (The Java compiler verifies this when
it checks the data type of the actual parameter in the invocation.) The method
computes the fractional inverse of i and formats it into three decimal places, using a
helper object constructed from class DecimalFormat, found in the package java.text
and introduced in Chapter 4. The formatted result is printed.
Say that we insert inverseOf into some new class, say, class MathOperations.
This lets us do the following:
MathOperations calculator = new MathOperations();
calculator.printInverse(3);
174
Figure 5.4: method with multiple parameters
import java.text.*;
// Note: This statement is inserted at the beginning
// of the class in which the following method appears.
/** printInverse prints the inverse, 1.0/i, of an integer, i.
* @param i - the integer that is inverted
* @param pattern - the pattern for formatting the fractional result */
public void printInverse(int i, String pattern)
{ DecimalFormat formatter = new DecimalFormat(pattern); // see Chapter 4
double d = 1.0 / i;
String s = formatter.format(d);
System.out.println(s);
}
which prints 0.333. Because of the declaration of its formal parameter, we cannot
invoke printInverse with doubles, e.g., calculator.printInverse(0.5) will be disallowed by the Java compiler—only integers can be actual parameters to the method.
But if printInverse had been written with this header line:
public void printInverse(double i)
Then the invocation,
calculator.printInverse(0.5);
would be acceptable, because the data type of the actual parameter is compatible
with the data type of the formal parameter. Indeed, we could also perform
calculator.printInverse(3);
because integers can be used in any context where doubles are expected. We see this
clearly when we remember that parameter binding is the same as variable initialization; therefore, the latter invocation generates this binding:
double i = 3;
which is acceptable in Java, because the 3 is converted into 3.0.
Methods may receive multiple parameters, and we can use this extension to good
effect in the above example. Say that we alter printInverse so that its client can
specify the pattern of precision used to print the inverse. The method now receives
two parameters; see Figure 4.
The parameters are stated in the header line, between the brackets, separated by
commas. When the method is invoked, there must be exactly two actual parameters,
and the first must be an integer and the second must be a string:
5.3. PARAMETERS TO METHODS
175
MathOperations calculator = new MathOperations();
calculator.printInverse(3, "0.00000"); // print with five decimal places
calculator.printInverse(5, "0.0"); // print with one decimal place
The important point to remember is
Actual parameters bind to formal parameters in order—the first actual parameter
binds to the first formal parameter, the second actual parameter binds to the
second formal parameter, and so on.
The particular variable names, if any, that are used in the actual parameters do not
matter—it is the order in which the actual parameters are listed that determines their
bindings to the formal parameters.
For example, the first invocation generates this execution of printInverse:
{ int i = 3;
String pattern = "0.00000";
DecimalFormat formatter = new DecimalFormat(pattern);
double d = 1.0 / i;
String s = formatter.format(d);
System.out.println(s);
}
The order of the actual parameters proves crucial when there is a situation like this
one,
public void printDivision(double n, double d)
{ System.out.println(n / d); }
where the data types of the two formal parameters are identical.
Finally, we modify Figure 4 to show that objects can also be parameters:
Begin Footnote: Indeed, since strings are implemented as objects in Java, we know
already that objects can be parameters, but the example that follows shows that
objects we construct with the new keyword can be parameters also. End Footnote
import java.text.*;
// Note: This statement is inserted at the beginning
// of the class in which the following method appears.
/** printInverse prints the inverse, 1.0/i, of an integer, i.
* @param i - the integer that is inverted
* @param f - the object that formats the fractional result */
public void printInverse(int i, DecimalFormat f)
{ double d = 1.0 / i;
String s = f.format(d);
System.out.println(s);
}
176
Remember that every class name generates its own data type, hence there is a data
type, DecimalFormat, and we use it to describe the second formal parameter, which
will bind to an object we construct:
MathOperations calculator = new MathOperations();
DecimalFormat five_places = new DecimalFormat("0.00000");
calculator.printInverse(3, five_places);
The invocation proceeds like the others: The actual parameters compute to their
results (in this case, the second parameter computes to the address of a DecimalFormat
object) and bind to the corresponding formal parameters.
Now that we are acquainted with the various forms of parameters, we can solve
a couple of mysteries. First, the header line of the paintComponent method one uses
for graphics windows reads
public void paintComponent(Graphics g)
The Graphics g part is a formal parameter, and whenever the operating system sends
a message to paintComponent, the message will contain an actual parameter that is
(the address of) the graphics-pen object that paintComponent uses for painting.
Second, method main’s header line also requires a parameter:
public static void main(String[] args)
The formal parameter, args, names the collection of program arguments that are
submitted when an application starts. As noted in Chapter 3, the arguments are
extracted from args by the names args[0], args[1], and so on. The data type,
String[], is read “string array”—it describes collections of strings. We study array
data types in Chapter 8.
We finish our examination of parameters with two final observations:
• Constructor methods may use formal parameters in the same fashion as other
public methods.
• A method whose header line has the form,
public void METHODNAME()
is a method with zero formal parameters and must be invoked by a statement
that uses zero actual parameters within the parentheses—RECEIVER OBJECT.METHODNAME()—
this is why an empty bracket set is required with some invocations.
5.3. PARAMETERS TO METHODS
177
Exercises
1. Here is a helper class:
public class ArithmeticClass
{ private int base;
public ArithmeticClass(int b)
{ base = b; }
public void printMultiplication(String label, double d)
{ System.out.println(label + (base * d)); }
}
(a) What does this application print? (Draw execution traces if you are uncertain about parameter passing.)
public class TestArithmeticClass
{ public static void main(String[] args)
{ ArithmeticClass c = new ArithmeticClass(2);
c.printMultiplication("3", 4.5 + 1);
int i = 4;
c.printMultiplication("A", i);
c.printMultiplication("A", i - 1);
}
}
(b) Explain the errors in this application:
public class Errors
{ public static void main(String[] args)
{ ArithmeticClass c = new ArithmeticClass();
printMultiplication("A", 5);
int s = 0;
c.printMultiplication(s, 2 + s);
c.printMultiplication(1, "A");
c.printMultiplication("A", 9, 10);
c.printSomething();
}
}
2. Write the missing methods for this class:
/** RunningTotal helps a child total a sequence of numbers */
public class RunningTotal
{ private int total; // the total of the numbers added so far
178
/** Constructor RunningTotal initializes the object */
public RunningTotal()
{ total = 0; }
/** addToTotal adds one more number to the running total
* @param num - the integer to be added to the total */
...
/** printTotal
...
prints the current total of all numbers added so far */
}
public class AddSomeNumbers
{ public static void main(String[] args)
{ RunningTotal calculator = new RunningTotal();
calculator.addToTotal(16);
calculator.addToTotal(8);
calculator.printTotal();
calculator.addToTotal(4);
calculator.printTotal();
}
}
3. The paintComponent method of the clock-writing class in Figure 17, Chapter 4,
can be rewritten so it uses this method:
/** paintClock paints a clock with the time
* @param hours - the current hours, an integer between 1 and 12
* @param minutes - the current minutes, an integer between 0 and 59
* @param x - the upper left corner where the clock should appear
* @param y - the upper right corner where the clock should appear
* @param diameter - the clock’s diameter
* @param g - the graphics pen used to paint the clock */
public void paintClock(int hours, int minutes, int x, int y,
int diameter, Graphics g)
Write this method, insert it into class ClockWriter in Figure 17, Chapter 4,
and rewrite ClockWriter’s paintComponent method to invoke it.
Next, make paintComponent draw two clocks—one for your time and one for the
current time in Paris.
Note: If you find this exercise too demanding, read the next section and return
to rework the exercise.
5.4. CASE STUDY: GENERAL-PURPOSE OUTPUT FRAME
5.4
179
Case Study: General-Purpose Output Frame
In practice, we design public methods when we are designing a class: We consider
what the responsibilities (behaviors) of the class might be, and we design public
methods for each of the expected behaviors. We write the class with the necessary
private fields and constructor methods so that the public methods operate properly.
A major component of our applications has been the “output view”—the part
that displays the computation results. We can reduce our dependency on System.out
as our output-view object if we design our own graphics window to display textual
output. Perhaps we write an application that asks its user to type input text:
The input is fetched and displayed in our new graphics window, e.g.,
Of course, we can use a JOptionPane-generated dialog for the input-view, but we must
design and write the output-view that has the ability to display a textual string.
Keeping this simple, we design the graphics window so that it displays exactly one
180
line of text and we can state where to position the text. (In the Exercises at the end
of this section, we make the window more versatile.)
We follow these steps when we design a new class:
1. List the class’s methods (its “responsibilities” or “behaviors”), and for each
method, give an informal description that states the method’s behavior and the
arguments the method requires to execute. This includes the constructor method
as well.
2. List the private fields (“attributes”) that will be shared by the the methods.
3. Write the methods’ bodies so that they have the listed behaviors.
All three items above are crucial to the person who writes the class; Item 1 is
important to those who use the class.
Let’s design the output frame; what methods should it have? Perhaps we decide
on two: (i) we can send a message to the frame to print a sentence; (ii)we can
tell the frame where it should print the sentence. Perhaps we can the first method,
writeSentence; obviously, the method will require an argument — the sentence to be
printed. The second method — call it, positionSentence — will require the x- and
y-coordinates that state where the sentence should be printed on the frame.
Also, we will require a constructor method that constructs the frame with some
fixed width and height.
Now, for the attributes: The initial descriptions of the methods suggest that the
frame must have at private fields that remember the current sentence to display and
the x- and y-coordinates of where to print it. Table 5 summarizes what we have
developed so far.
The new class is named MyWriter, and the table is called its interface specification
or specification, for short. The specification indicates the methods we must write and
it also states the ways that others can use the methods we write, so we will retain
the specification as useful documentation after the class is built. You might compare
Table 5 to the ones that summarized the methods for class Graphics (Table 16,
Chapter 4) and JFrame (Table 20, Chapter 4).
The notion of “specification” comes from real-life: For example, when you buy a
tire for your car, you must know the correct size of tire—the size is the tire’s specification. Your waist and inseam measurement is another example of an specification
that you use when you sew or purchase a new pair of pants. Specifications help you
construct and use objects in real life, and the same is true in computer programming.
Given the specification in Table 5, how do we write its methods? Since MyWriter is
a “customized” graphics window, we can follow the techniques from Chapter 4. This
suggests that we write a class whose paintComponent method paints the sentence on
the window. How will writeSentence ask paintComponent do its work?
The solution is to declare the private field,
181
5.4. CASE STUDY: GENERAL-PURPOSE OUTPUT FRAME
Figure 5.5: specification of an output frame
MyWriter creates a graphics window that displays a sentence.
Constructor:
MyWriter(int w, int h)
Methods:
writeSentence(String s)
repositionSentence(int
new x, int new y)
Private attributes:
sentence
x position
y position
private String sentence;
construct the window with the width of w pixels and
the height of h pixels and display it.
display sentence s in the graphics window
redisplay the existing sentence at the new position,
new x, new y
the sentence that is displayed
the x-coordinate of where the sentence will appear
the y-coordinate of where the sentence will appear
// holds the sentence to be displayed
and have paintComponent display the field’s contents:
public void paintComponent(Graphics g)
{ ...
g.drawString(sentence, ... );
}
Now writeSentence(String s) has this algorithm:
1. assign sentence = s;
2. make paintComponent execute.
We do Step 2 by invoking a prewritten method already contained within class
JPanel: It is called repaint, and it invokes paintComponent with the panel’s the
graphics pen object as the actual parameter, just like the computer’s operating system does whenever a panel must be repainted. The invocation of repaint forces the
panel to repaint, even if it is not iconified or moved or covered. The coding reads:
public void writeSentence(String s)
{ sentence = s;
this.repaint(); // indirectly forces
}
paintComponent
to execute
Because repaint’s coding lives within class JPanel and because class MyWriter
extends JPanel, it means that every MyWriter object will have its own repaint
182
method. To invoke the method within MyWriter, we write this.repaint(), because
the receiver of the invocation is this object—the client object that sends the message
is also the receiver!
The completed class MyWriter appears in Figure 6. The class uses a number of
private fields, which remember the window’s size, the position for displaying the sentence, and the sentence itself (which is initialized to an empty string). The constructor
method uses two parameters to size the window.
The paintComponent method operates as expected, and writeSentence deposits
the string to be displayed in field sentence and forces this object to repaint. Finally,
positionSentence resets the fields that position the sentence and forces the sentence
to be redisplayed. We use the invocation,
this.writeSentence(sentence);
to reinforce that an object can send messages to its own public methods.
Begin Footnote: The Java language allows the this pronoun to be omitted from
invocations of an object’s own methods, e.g., writeSentence(sentence) can be used
in the previous example. End Footnote
Note also that the value of a field can be an argument in an invocation. Finally,
positionSentence could also be written as
public void positionSentence(int new_x, int new_y)
{ x_position = new_x;
y_position = new_y;
this.repaint();
}
which has the same behavior.
Here is a small application that uses class MyWriter to interact with its user:
import javax.swing.*;
/** MyExample2 displays, in a graphics window, a sentence its user types */
public class MyExample2
{ public static void main(String[] args)
{ int width = 300;
int height = 200;
MyWriter writer = new MyWriter(width,height);
writer.positionSentence(50, 80); // set position to my liking
String s = JOptionPane.showInputDialog("Please type some text:");
writer.writeSentence(s); // display s
}
}
When the application starts, the graphics window appears with an empty sentence,
which is immediately repositioned to location, 50, 80. Then the interaction displayed
at the beginning of this section occurs.
Here are the lessons learned from this case study:
5.4. CASE STUDY: GENERAL-PURPOSE OUTPUT FRAME
183
Figure 5.6: graphics window for displaying text
import java.awt.*; import javax.swing.*;
/** MyWriter creates a graphics window that displays a sentence */
public class MyWriter extends JPanel
{ private int width; // the frame’s width
private int height; // the frame’s height
private String sentence = ""; // holds the sentence to be displayed
private int x position; // x-position of sentence
private int y position; // y-position of sentence
/** Constructor MyWriter creates the Panel
* @param w - the window’s width
* @param h - the window’s height */
public MyWriter(int w, int h)
{ width = w;
height = h;
x position = width / 5; // set the sentence’s position
y position = height / 2;
JFrame my frame = new JFrame();
my frame.getContentPane().add(this);
my frame.setTitle("MyWriter");
my frame.setSize(width, height);
my frame.setVisible(true);
}
/** paintComponent paints the panel
* @param g - the ‘‘graphics pen’’ that draws the items */
public void paintComponent(Graphics g)
{ g.setColor(Color.red);
g.drawString(sentence, x position, y position);
}
/** writeSentence displays a new string in the window
* @param s - the sentence to be displayed */
public void writeSentence(String s)
{ sentence = s;
this.repaint(); // indirectly forces paintComponent
}
to execute
/** positionSentence redisplays the existing sentence in a new position
* @param new x - the new horizontal starting position
* @param new y - the new vertical starting position */
public void positionSentence(int new x, int new y)
{ x position = new x;
y position = new y;
this.writeSentence(sentence); // force a rewrite of the existing sentence
}
}
184
1. We designed and wrote a class that can be used as a component of many applications.
2. We designed a specification that helped us write the class.
3. The users of the class can read the specification to understand how to use the
class’s methods; there is no need to read the class’s coding.
4. The coding used a constructor method and private fields to help the public
methods behave correctly; the public methods invoked each other as needed.
Item 3 of the above list is so crucial that you should always write specifications, even
“after the fact,” for the classes you build. Indeed, there should be a close, if not exact,
match between the specification and the Java comments you attach to the class and
its methods, so the specifications really generate no additional effort.
In the next Chapter, we will appreciate two more benefits of designing and writing
classes like MyWriter:
• When an application is built by several people, it is easier for distinct people
to write, and test the pieces of the application if the application is divided into
classes.
• If part an application must be rewritten or replaced, it is easier to do so if the
part is one separate class.
Exercises
1. Explain the behavior of this application that uses class MyWriter:
import javax.swing.*;
public class AnotherExample
{ public static void main(String[] args)
{ MyWriter writer = new MyWriter(300, 200);
String s = JOptionPane.showInputDialog("Please type some text:");
writer.writeSentence(s);
s = JOptionPane.showInputDialog("Try it again:");
writer.writeSentence(s);
writer.repositionSentence(0, 190);
writer.writeSentence(s + s);
}
}
2. Write an application that asks the user to type an integer, computes the square
root of that integer, and uses class MyWriter to display the integer and its
square root, the latter displayed to a precision of 6 decimal places.
5.4. CASE STUDY: GENERAL-PURPOSE OUTPUT FRAME
185
3. Add this new method to class MyWriter:
/** writeSecondSentence displays a sentence, t, underneath the first
* sentence in the window.
* @param t - the second sentence to be displayed */
public void writeSecondSentence(String t)
When you add this method, must you revise writeSentence? repositionSentence?
4. Write a class that satisfies this specification:
TextWriter displays up to three
lines of text in a graphics window
Constructor:
TextWriter(int w, int h)
print1(String s)
reset1(String s)
print2(String s)
reset2(String s)
print3(String s)
reset3(String s)
Constructs the window with width w pixels and
height h pixels and displays a window with three
empty lines of text.
Methods:
Appends s to the end of the Line 1 text and
displays the current values of all three lines of
text.
Sets Line 1 of the text to s and displays the
current values of all three lines of text.
Appends s to the end of the Line 2 text and
displays the current values of all three lines of
text.
Sets Line 2 of the text to s and displays the
current values of all three lines of text.
Appends s to the end of the Line 3 text and
displays the current values of all three lines of
text.
Sets Line 3 of the text to s and displays the
current values of all three lines of text.
Test the class with this application:
import javax.swing.*;
public class TestTextWriter
{ public static void main(String[] args)
{ TextWriter writer = new TextWriter(300, 200);
String s = JOptionPane.showInputDialog("Please type some text:");
186
writer.print1(s);
s = JOptionPane.showInputDialog("Try it again:");
writer.print1(s);
s = JOptionPane.showInputDialog("Once more:");
writer.print3(s);
s = JOptionPane.showInputDialog("Last time:");
writer.reset1(s);
}
}
Next, rewrite Figure 2, Chapter 4, to use a TextWriter object to display its
output.
5.5
Results from Methods: Functions
When a client object sends a message, sometimes the client expects an answer in
reply. We saw this in Figure 2, Chapter 4, where the controller asked the input-view
to read an input string:
String input =
JOptionPane.showInputDialog("Type an integer Celsius temperature:");
When JOptionPane’s showInputDialog method executes, it fetches the string the user
types, and the string is the “reply,” which is deposited into the position where the
readLine message appears. Then, the string is assigned to variable input.
Yet another example appears in the same figure,
int c = new Integer(input).intValue();
where the object, new Integer(input), is sent the message, intValue(), and replies
with the integer that is assigned to c.
“Replies” from methods are called results. The idea comes from mathematics,
where one speaks of the result from calculating a formula. Indeed, we can take the
well-used temperature conversion formula, f = (9.0 / 5.0) * c + 32, and encode it
as a method that returns a result; see Figure 7. The method illustrates the technique
for returning a result:
• In the method’s header line, replace the keyword, void, by the data type of the
result the method should return. (Here, it is double.) We now note that all the
previous uses of the term, void, meant “returns no result.”
• At the end of the method’s body, insert a return E statement, where E computes
to the value that will be returned. E can be any expression whose data type
matches the one listed in the header line as the result’s data type.
Indeed, the temperature-conversion method can be written just this tersely:
5.5. RESULTS FROM METHODS: FUNCTIONS
187
Figure 5.7: temperature conversion function
/** celsiusIntoFahrenheit translates degrees Celsius into Fahrenheit
* @param c - the degrees in Celsius
* @return the equivalent degrees in Fahrenheit */
public double celsiusIntoFahrenheit(double c)
{ double f = ((9.0 / 5.0) * c) + 32;
return f;
}
public double celsiusIntoFahrenheit(double c)
{ return ((9.0 / 5.0) * c) + 32; }
• As part of our documentation policy, we include the line,
* @return the equivalent degrees in Fahrenheit
in the comments at the head of the method to describe the result computed.
A method that returns a result is sometimes called a function. A function’s commentary includes a line labelled @return, which explains the nature of the result returned
by the function.
If the celsiusIntoFahrenheit was included in some class, say, class TemperatureConvertor,
then we might use it to convert temperatures as follows:
import javax.swing.*;
/** ConvertATemperature converts one Celsius temperature into Fahrenheit */
public class ConvertATemperature
{ public static void main(String[] args)
{ String input =
JOptionPane.showInputDialog("Type an integer Celsius temperature:");
int c = new Integer(input).intValue(); // convert input into an int
TemperatureConvertor convert = new TemperatureConvertor();
double f = convert.celsiusIntoFahrenheit(c);
MyWriter writer = new MyWriter(300, 200);
writer.writeSentence(c + " Celsius is " + f + " Fahrenheit");
}
}
The two statements in the middle of the main method can be compressed into one, if
desired:
188
double f = new TemperatureConvertor().celsiusIntoFahrenheit(c);
With a bit of work, we might collect together other conversion formulas for temperatures and save them in class TemperatureConvertor—see the Exercises that follow. As always, by writing functions and collecting them in classes, we can save and
reuse the formulas in many applications. Indeed, in the Java package, java.lang, one
finds class Math, which is exactly such a collection of commonly used mathematical
functions. In addition, the next Chapter shows other crucial uses of functions.
What forms of results can a function return? The answer is: any value that has
a data type can be the result of a function—use the data-type name in the function’s
header line, and use the value in the return statement. Therefore, we have the same
freedom as we have with parameters when we define results of functions—primitive
values as well as objects can be function results.
The Java compiler will let an application “discard” the result of a function, e.g.,
import javax.swing.*;
public class IgnoreConversion
{ public static void main(String[] args)
{ String input =
JOptionPane.showInputDialog("Type an integer Celsius temperature:");
int c = new Integer(input).intValue();
TemperatureConvertor convert = new TemperatureConvertor();
convert.celsiusIntoFahrenheit(c);
System.out.println("The End");
}
}
The penultimate statement invokes the celsiusIntoFahrenheit function, the function
computes and returns a result, but the result is ignored and execution proceeds to
the final statement.
This technique is more commonly used with a constructor method, which is a
“function” that automatically returns the address of the constructed object. For
example, class CelsiusToFahrenheitFrame in Figure 14, Chapter 4, is self-contained,
so its start-up application merely reads:
public class Convert
{ public static void main(String[] args)
{ new CelsiusToFahrenheitFrame(); }
}
The sole statement in main invokes the constructor method and ignores the returned
address. Indeed, remember that a constructor method must always return the address
of the object constructed, and for this reason a constructor can never return any other
value as its result—you will never see a return-data-type listed in the header line of
a constructor method.
189
5.5. RESULTS FROM METHODS: FUNCTIONS
Finally, remember that it is good programming policy to make the return statement the final statement of a function. For example, the Java compiler will allow the
following function to compiler and execute:
/** square incorrectly computes a number’s square
* @param num - the number
* @return an incorrect compution of num * num */
public int square(int num)
{ int result = 0;
return result;
result = num * num;
}
Because execution proceeds from the first statement forwards, the badly placed return
statement forces the function to quit prematurely and return the current value of
result, which is 0—the third statement never executes.
Execution Trace
We finish the section with a few steps of the execution trace of the example that
uses the temperature-conversion function in Figure 7. Say that the main method has
reached this point in its execution:
ConvertATemperature
main
{ int c ==
18
...
1> TemperatureConvertor convert = new TemperatureConvertor();
double f = convert.celsiusIntoFahrenheit(c);
...
}
As seen before, the initialization constructs the object that holds the function:
ConvertATemperature
main
{ int c ==
18
...
1> TemperatureConvertor convert ==
a1
double f = convert.celsiusIntoFahrenheit(c);
...
}
a1 : TemperatureConvertor
public double celsiusIntoFahrenheit(double c) {...}
...
The invocation binds the value of the actual parameter to the formal parameter, as
usual, and the message to the object at a1 starts execution of the function, which
190
proceeds to its result:
ConvertATemperature
a1 : TemperatureConvertor
main
{ int c ==
celsiusIntoFahrenheit
18
{ double c ==
18.0
...
1> TemperatureConvertor convert ==
double f =
a1
double f ==
68.0
2> return f;
?
}
1> f = AWAIT RESULT;
...
...
}
Notice that the variables, c and f, local to celsiusIntoFahrenheit, are unrelated to
the local variables in main—no problems are introduced, no inadvertent connections
are made, merely because the two program components chose similar names for their
local variables.
Finally, the return f statement computes 68.0 and inserts it into the position
where the invocation appeared. The function resets for later invocations:
ConvertATemperature
main
{ int c ==
18
...
1> TemperatureConvertor convert ==
double f =
a1
?
1> f = 68.0;
...
}
a1 : TemperatureConvertor
public double celsiusIntoFahrenheit(double c) {...}
...
Because function invocation operates the same way as ordinary method invocation,
the execution trace reinforces the intuition that a method whose “result type” is void
“returns” no result at all.
Exercises
1. What does this application print?
public class Counter
{ private int count;
public Counter(int i)
{ count = i; }
5.5. RESULTS FROM METHODS: FUNCTIONS
191
public int countA()
{ count = count + 1;
return count;
}
public double countB()
{ return count + 1.5; }
public String countC()
{ return "*" + count; }
}
public class TestCounter
{ public static void main(String[] args)
{ Counter c = new Counter(3);
int x = c.countA();
System.out.println(x + " " + c.countB());
System.out.println(c.countC() + c.countA());
}
}
2. Place the function, celsiusIntoFahrenheit, seen in this section into a class,
class TemperatureConvertor. Add this method to the class:
/** fahrenheitIntoCelsius translates degrees Fahrenheit into Celsius
* @param f - the degrees in Fahrenheit, a double
* @return the equivalent degrees in Celsius, a double */
(Hint: use algebra to compute the conversion formula.) Next, text the class
with this application:
public class ConvertTemps
{ public static void main(String[] args)
{ TemperatureConvertor calculator = new TemperatureConvertor();
int temp = new Integer(args[0]).intValue(); // get command-line input
double ftemp = calculator.celsiusIntoFahrenheit(temp);
System.out.println(temp + "C is " + ftemp + "F");
System.out.println("Verify: " + ftemp + "F is "
+ calculator.fahrenheitIntoCelsius(ftemp) + "C");
double ctemp = calculator.fahrenheitIntoCelsius(temp);
System.out.println(temp + "F is " + ctemp + "C");
System.out.println("Verify: " + ctemp + "C is "
+ calculator.celsiusIntoFahrenheit(ctemp) + "F");
}
}
192
3. Write functions that match each of these specifications:
(a) /** kilometersToMiles converts a kilometers amount into miles
* using the formula:
Miles = 0.62137 * Kilometers
* @param k - the kilometers amount
* @return the corresponding miles amount */
public double kilometersToMiles(double k)
Insert the function into an application that reads a kilometers value from
the user and prints the value in miles.
(b) /** compoundTotalOf computes the compounded total
* that result from a starting principal, p, an interest rate, i,
* and a duration of n compounding periods, using this formula:
* total = p((1 + (i/n))<sup>n</sup>)
* @param p - the starting principal, a dollars, cents, amount
* @param i - the interest rate per compounding period,
*
a fraction (e.g., 0.01 is 1%)
* @param n - the compounding periods (e.g., if compounding is done
*
monthly, then two years is 24 compounding periods)
* @return the total of principal plus compounded interest */
public double compoundTotalOf(double p, double i, int n)
(c) /** isDivisibleByNine checks if its argument is divisible by 9 with no remander.
* @param arg - the argument to be checked
* @return true, if it is divisible by 9; return false, otherwise.
public boolean isDivisibleByNine(int arg)
*/
(d) /** pay computes the weekly pay of an employee
* @param name - the employee’s name
* @param hours - the hours worked for the week
* @param payrate - the hourly payrate
* @return a string consisting of the name followed by "$" and the pay */
public String pay(String name, int hours, double payrate)
4. Here is an application that would benefit from a function. Rewrite it with one.
/** Areas prints the areas of three circles */
public class Areas
{ public static void main(String[] args)
{ System.out.println("For radius 4, area = " + (Math.PI * 4*4));
System.out.println(Math.PI * 8*8);
System.out.println((Math.PI * 19*19) + " is the area for radius 19");
}
}
(Note: Math.PI is Java’s name for the math constant, Pi.)
5.6. PRIVATE METHODS
5.6
193
Private Methods
This Chapter’s introduction mentioned that an object’s methods can be classified as
“public” and “private”: Public methods can be invoked by the general public, but
private methods can be invoked only within the class where they appear.
Why should we bother with writing private methods? Private methods help impose neat internal structure to a class: specific instruction sequences can be collected
together, appropriately named, and they can be invoked multiple times within the
class. We give two examples.
Naming a Subalgorithm with a Private Method
Say that we decide to improve class MyWriter in Figure 6 so that the graphics window
appears with a blue border and a white center:
This adjustment does not change the class’s specification in Figure 5—the programming changes are purely internal. But how do we paint a blue border and a white
center? The algorithm takes a bit of thought:
1. Paint the entire window blue.
2. Calculate the size of a rectangle whose size is slightly smaller than the size of
the entire window.
3. Paint, in the center of the blue window, a white rectangle of this slightly smaller
size.
These steps accomplish the desired result. Because the algorithm is self contained, it
makes sense to give it a name, say, makeBorder, and include it as a private method,
which is used by paintComponent. Figure 8 shows the method and the revised class.
194
Figure 5.8: graphics window with private method
import java.awt.*; import javax.swing.*;
/** MyWriter2 creates a graphics window that displays a sentence */
public class MyWriter2 extends JFrame
{ private int width; // the frame’s width
private int height; // the frame’s height
private String sentence = ""; // holds the sentence to be displayed
private int x position = 50; // x-position of sentence
private int y position = 80; // y-position of sentence
/** Constructor MyWriter2 creates the window and makes it visible
* @param w - the window’s width
* @param h - the window’s height */
public MyWriter2(int w, int h)
{ ... see Figure 6 ... }
/** paintComponent paints the panel
* @param g - the ‘‘graphics pen’’ that draws the items */
public void paintComponent(Graphics g)
{ makeBorder(g); // invoke private method to paint the border
g.setColor(Color.red);
g.drawString(sentence, x position, y position);
}
/** makeBorder paints the frame’s border.
* @param pen - the graphics pen used to paint the border */
private void makeBorder(Graphics pen)
{ pen.setColor(Color.blue);
pen.fillRect(0, 0, width, height);
// paint entire window blue
int border size = 20;
int center rectangle width = width - (2 * border size);
int center rectangle height = height - (2 * border size);
pen.setColor(Color.white);
pen.fillRect(border size, border size, // paint center rectangle white
center rectangle width, center rectangle height);
}
/** writeSentence displays a new string in the window
* @param s - the sentence to be displayed */
public void writeSentence(String s)
{ ... see Figure 6 ... }
/** repositionSentence redisplays the existing sentence in a new position
* @param new x - the new horizontal starting position
* @param new y - the new vertical starting position */
public void repositionSentence(int new x, int new y)
{ ... see Figure 6 ... }
}
5.6. PRIVATE METHODS
195
The private method, makeBorder, is written just like a public method, except
its header line includes the keyword, private. Its parameter, Graphics pen, is the
graphics pen it is given to do painting. The parameter is supplied by method
paintComponent, which invokes makeBorder by merely stating its name—because the
method is a private method, the receiver must be “this” object:
makeBorder(g);
The private method is useful because it maintains the internal structure of the
original class, leaves paintComponent almost exactly the same as before, and keeps
together the “subalgorithm” we wrote for painting the border. If we choose later to
paint the border differently, we need only change the private method.
The construction of makeBorder is driven by this standard motivation for private
methods:
Where a formula or subalgorithm deserves a name of its own, make it into a private
method.
Repeating an Activity with a Private Method
Chapter 4 showed us how to draw geometric figures in a graphics window. Say that
our current passion is stacking “eggs” in a window, like this:
The window’s paintComponent method must draw the three eggs, but our sense of
economy suggests we should write a private method that knows how to draw one
egg and make paint invoke the method three times. Because of our knowledge of
parameters, we specify the method, paintAnEgg, like this:
/** paintAnEgg
paints an egg in 3-by-2 proportion
196
*
*
*
*
*
(that is, the egg’s height is two-thirds of its width)
@param bottom - the position of the egg’s bottom
@param width - the egg’s width
@param pen - the graphics pen that will draw the egg
@return the y-position of the painted egg’s top edge. */
This method’s algorithm goes as follows:
1. Calculate the egg’s height as 2/3 of its width.
2. Calculate the egg’s left edge so that the egg is centered in the window, and
calculate the egg’s top edge by measuring from its bottom.
3. Paint the egg so that its upper left corner is positioned at the left edge, top
edge.
4. Return the value of the top edge, in case it is needed later to stack another egg
on top of this one.
Using the parameters, we readily code the algorithm into the private method that
appears in Figure 9. Method paintComponent merely invokes the private method three
times to stack the three eggs. A small main method is included at the bottom of the
class so that the graphics window can be executed directly.
This example illustrates a classic motivation for writing private methods:
Where there is repetition of a task, write one private method whose body does the
task and invoke the method repeated times.
Stated more bluntly, the above slogan warns you, “If you are using the cut-and-paste
buttons on your text editor to copy statements to do the same task multiple times,
then you should be using a private method instead!”
Exercises
1. Write a testing application that constructs these variations of StackedEggsWriter:
(a) new StackedEggsWriter(30, 0, 500);
(b) new StackedEggsWriter(30, -10, 30);
(c) new StackedEggsWriter(-300, 300, 30);
You might find it helpful to maximize the window to view some of the results.
2. Add public methods setEggSize1(int size), setEggSize2(int size), and setEggSize3(int
size) to class StackedEggsWriter. Of course, each method resets the size of
the respective egg displayed and repaints the window. Test the modified class
with this application:
5.6. PRIVATE METHODS
Figure 5.9: repeated invocations of a private method
import java.awt.*;
import javax.swing.*;
/** StackedEggsWriter displays three eggs, stacked */
public class StackedEggsWriter extends JPanel
{ private int frame width;
private int frame height;
// the sizes (widths) of the three eggs stacked:
private int egg1 size;
private int egg2 size;
private int egg3 size;
/** Constructor StackedEggsWriter stacks three 3-by-2 eggs in a window
* @param width - the width of the panel
* @param height - the height of the panel
* @param size1 - the width of the bottom egg
* @param size2 - the width of the middle egg
* @param size3 - the width of the top egg */
public StackedEggsWriter(int width, int height,
int size1, int size2, int size3)
{ frame width = width;
frame height = height;
egg1 size = size1;
egg2 size = size2;
egg3 size = size3;
JFrame my frame = new JFrame();
my frame.getContentPane().add(this);
my frame.setTitle("StackedEggsWriter");
my frame.setSize(frame width, frame height);
my frame.setVisible(true);
}
/** paintComponent fills the window with the eggs
* @param g - the graphics pen */
public void paintComponent(Graphics g)
{ g.setColor(Color.yellow);
g.fillRect(0, 0, frame width, frame height); // paint the background
// lay the first egg at the bottom of the frame:
int egg1 top = paintAnEgg(frame height, egg1 size, g);
// stack the two remaining eggs on top of it:
int egg2 top = paintAnEgg(egg1 top, egg2 size, g);
int egg3 top = paintAnEgg(egg2 top, egg3 size, g);
}
...
197
198
Figure 5.9: repeated invocations of a private method (concl.)
/** paintAnEgg paints an egg in 3-by-2 proportion (the egg’s height
* is two-thirds of its width)
* @param bottom - the position of the egg’s bottom
* @param width - the egg’s width
* @param pen - the graphics pen that will draw the egg
* @return the y-position of the painted egg’s top edge. */
private int paintAnEgg(int bottom, int width, Graphics pen)
{ int height = (2 * width) / 3;
int top edge = bottom - height;
int left edge = (frame width - width) / 2;
pen.setColor(Color.pink);
pen.fillOval(left edge, top edge, width, height);
pen.setColor(Color.black);
pen.drawOval(left edge, top edge, width, height);
return top edge;
}
/** Test the window: */
public static void main(String[] args)
{ int total width = 300;
int total height = 200;
new StackedEggsWriter(total width, total height, 50, 90, 140);
}
}
5.6. PRIVATE METHODS
199
import javax.swing.*;
public class StackSomeEggs
{ public static void main(String[] args)
{ StackedEggsWriter writer = new StackedEggsWriter(0, 0, 0);
String s = JOptionPane.showInputDialog("Type size of bottom egg (an int):");
writer.setEggSize1(new Integer(s).intValue());
s = JOptionPane.showInputDialog("Type size of middle egg (an int):");
writer.setEggSize2(new Integer(s).intValue());
s = JOptionPane.showInputDialog("Type size of top egg (an int):");
writer.setEggSize3(new Integer(s).intValue());
}
}
3. Modify the paintBorder method in Figure 8 so that it paints a white-filled circle
with diameter equal to the window’s height in the center of the window in front
of a yellow background.
4. This class can benefit from a private method; insert it.
import java.awt.*;
import javax.swing.*;
/** Circles draws three concentric circles */
public class Circles extends JPanel
{ public Circles()
{ JFrame my_frame = new JFrame();
my_frame.getContentPane().add(this);
my_frame.setTitle("TextWriter");
my_frame.setSize(200, 200);
my_frame.setVisible(true);
}
public void paintComponent(Graphics g)
{ int x_pos = 100; // x-position of center of circle
int y_pos = 100; // y-position of center of circle
int diameter = 60; // diameter of circle to draw
g.setColor(Color.black);
int radius = diameter / 2;
g.drawOval(x_pos - radius, y_pos - radius, diameter, diameter);
diameter = diameter + 20;
radius = diameter / 2;
g.drawOval(x_pos - radius, y_pos - radius, diameter, diameter);
diameter = diameter + 20;
200
radius = diameter / 2;
g.drawOval(x_pos - radius, y_pos - radius, diameter, diameter);
}
}
5.7
Summary
We now summarize the main points of this chapter:
New Constructions
Here are examples of the new constructions encountered in this chapter:
• public method (from Figure 1):
/** printLadybug prints a ladybug */
public void printLadybug()
{ System.out.println(" ‘m\’"); // the ’ must be written as \’
System.out.println(" (|) sahr");
System.out.println();
}
• formal parameter (from Figure 3):
/** printBeeWithName prints a bee with a name attached to its stinger.
* @param name - the name attached to the stinger */
public void printBeeWithName(String name)
{ System.out.println(" ,-.");
System.out.println(" \\_/");
System.out.println(">{|||}-" + name + "-");
System.out.println(" / \\");
System.out.println(" ‘-^ hjw");
System.out.println();
}
• function (from Figure 7):
/** celsiusIntoFahrenheit translates degrees Celsius into Fahrenheit
* @param c - the degrees in Celsius
* @return the equivalent degrees in Fahrenheit */
public double celsiusIntoFahrenheit(double c)
{ double f = ((9.0 / 5.0) * c) + 32;
return f;
}
5.7. SUMMARY
201
• private method (from Figure 9):
/** paintAnEgg paints an egg in 3-by-2 proportion (the egg’s height
* is two-thirds of its width)
* @param bottom - the position of the egg’s bottom
* @param width - the egg’s width
* @param pen - the graphics pen that will draw the egg
* @return the y-position of the painted egg’s top edge. */
private int paintAnEgg(int bottom, int width, Graphics pen)
{ int height = (2 * width) / 3;
int top_edge = bottom - height;
int left_edge = (FRAME_WIDTH - width) / 2;
pen.setColor(Color.pink);
pen.fillOval(left_edge, top_edge, width, height);
pen.setColor(Color.black);
pen.drawOval(left_edge, top_edge, width, height);
return top_edge;
}
New Terminology
• method: a named sequence of statements; meant to accomplish a specific task
or responsibility (e.g., writeSentence) A method has a header line (e.g., public
void writeSentence(String s) and a body (e.g., { sentence = s; repaint();
} )
• invoking a method: sending a message that includes a method’s name (e.g.,
writer.printBee()). The message requests that the receiver object (writer)
execute the method named in the message (printBee).
• client: the object that sends a message (an invocation).
• receiver: the object that receives a message; it executes the method named in
the message.
• actual parameters: arguments that are listed with the method’s name when
invoking the method (e.g., "Lucy" in writer.printBeeWithName("Lucy"))
• formal parameters: variable names that are listed in the method’s header line
(e.g., String name in the header line, public void printBeeWithName(String
name). When the method is invoked, the actual parameters are assigned (or
bound) to the formal parameters (e.g., the invocation, writer.printBeeWithName("Lucy"),
assigns "Lucy" to variable name.)
202
• result of a method: an “answer” calculated by a method and given back to its
client (e.g., return f in the body of celsiusToFahrenheit in Figure 7.
• function: a method that returns a result
• scope of a formal parameter: the statements where the parameter can be referenced and assigned. This is normally the method’s body where the parameter
is defined (e.g., the scope of formal parameter s of writeSentence consists of
the two statements, sentence = s; this.repaint();).
• (interface) specification: a list of a class’s methods and their behaviors; can
also list the class’s private attributes (e.g., Table 5 is the specification for class
MyWriter in Figure 6).
Points to Remember
• A method can be labelled private (for the use of only the other methods within
the class in which it appears, e.g., paintAnEgg) or public (for the use of other
objects, e.g., writeSentence).
• Private methods are written in response to two situations:
1. Where there is repetition of a task, write one private method whose body
does the task and invoke the method repeated times.
2. Where a fundamental concept, formula, or subalgorithm deserves a name
of its own, make it into a private method.
• Public methods are written in response to this situation: A skill or responsibility
that other objects depend upon should be written as a public method.
• Often, we design classes and methods hand in hand, because a class possesses
a collection of related “behaviors,” where the methods encode the behaviors.
• A specification lists the names and describes the responsibilities of a class’s
public methods. Specifications are written for each class because:
1. It becomes easier to use and reuse a class in several different applications.
2. It becomes easier for distinct people to design, write, and test the distinct
classes in an application;
3. It becomes easier to rewrite or replace one class without rewriting all the
others;
• Actual parameters bind to formal parameters in order—the first actual parameter binds to the first formal parameter, the second actual parameter binds to
the second formal parameter, and so on.
5.8. PROGRAMMING PROJECTS
203
• Any value that can be assigned to a variable can be an actual parameter that
binds to a formal parameter.
5.8
Programming Projects
1. Return to the Programming Projects for Chapter 4. For any of the projects that
you completed, revise the application so that it displays its output information
in a graphics window that you designed first with a specification and then coded.
For example, if you built the application that helps a child learn multiplications,
then the application might display an input view that asks,
When the child replies, with say, 33, the answer appears in a newly created
graphics window:
2. For practice with private methods, write this application that counts votes for
a small election:
When started, the application asks Candidate 1 to type her name, followed
by her address. The application asks Candidate 2 to do the same. Next, the
204
application asks 5 voters to vote for either Candidate 1 or Candidate 2. The
application finishes by displaying the candidates’ names, addresses, and total
vote counts.
3. Answers have more impact when displayed visually. Write an application that
displays the distances one can travel on a full tank of gasoline at different
velocities. Use this formula to calculate distance travelled for a positive velocity,
v:
distance = (40 + 0.05v − (0.06v)2 ) ∗ capacity
where capacity is the size of the automobile’s fuel tank. (Note: This simplistic
formula assumes that the car has a one-gear transmission.)
The input to the application is the fuel tank’s size; the output are the distances
travelled (and the time taken to do so) for velocities 20, 40, 60, 80, and 100 miles
per hour. Display the answers graphically, so that the answers are pictures like
this:
For a 10-gallon fuel tank:
/A CAR\
40 m.p.h.: =================== -o--o-/A CAR\
60 m.p.h.: =============== -o--o--
362.4 miles in 9.06 hours
300.4 miles in 5.007 hours
4. Recall that the formula to calculate the distance, d, that an automobile travels
starting from initial velocity, Vsub 0 , and acceleration, a, is defined
d = V0 t + (1/2)a(t2 )
Write an application that asks the user to submit values for V0 and a; the
application produces graphical output showing the distances travelled for times
0, 1, ..., 10.
5. Write a general purpose currency convertor program. The program is started
by giving it three program arguments: the name of the currency the user holds
and wishes to sell, the name of the currency the user wishes to buy, and the
conversion rate from the first currency to the other. For example, the application
might be given these arguments:
USDollar Euro 0.9428
to tell it that one US Dollar can buy 0.9428 of one Euro.
Once it is started, the application asks the user to type the amount of currency
the user wishes to sell. When the user submits this information, the application
computes the amount of currency purchased and displays the answer as two
proportionally sized coins:
205
5.8. PROGRAMMING PROJECTS
IMAGE OF TWO BALLOONS, LABELLED AS:
200 Dollars
=
188.56 Euros
6. Write a class that helps a user display a bar graph that displays up to 6 separate
bars. Here is an example of such a graph: class BarGraphWriter:
The class should have these methods:
206
class BarGraphWriter
helps a user draw bar graphs
Methods
setAxes(int x pos, int
y pos, String top label,
int y height)
setBar1(String label, int
height, Color c)
setBar2(String label, int
height, Color c)
setBar3(String label,
int height, Color c),
setBar4(String label,
int height, Color c),
setBar5(String label,
int height, Color c),
setBar6(String label, int
height, Color c)
draw the x- and y-axes of the graph. The pair,
x pos, y pos, state the coordinates on the window where the two axes begin. The height of
the y-axis, stated in pixels, is y height. (The
length of the x-axis will be exactly long enough
to display 6 bars.) The label placed at the top
of the y-axis is top label. (The label placed at
the bottom of the y axis is always 0.) See the
picture above.
draw the first bar in the graph, where the label
underneath the bar is label, the height of the
bar, in pixels, is height, and the bar’s color is
c. See the picture above.
draw the second bar in the graph, where the arguments are used in the same way as in setBar1
draw the third through sixth bars of the graph
(a) Here is the application that drew the above graph:
import java.awt.*;
public class TestGraph
{ public static void main(String[] a)
{ BarGraphWriter e = new BarGraphWriter();
e.setTitle("Days in first four months of the year");
e.setAxes(20, 120, "30", 90);
// x- and y-axes start at 20, 120
// graph is 90 pixels high; top of graph is labelled "30"
int scale_factor = 3;
e.setBar1("Jan", 31 * scale_factor, Color.red);
// Jan has 31 days
e.setBar2("Feb", 28 * scale_factor, Color.white); // etc.
e.setBar3("Mar", 31 * scale_factor, Color.blue);
e.setBar4("Apr", 30 * scale_factor, Color.red);
}
}
Test your coding of class BarGraphWriter with TestGraph.
207
5.8. PROGRAMMING PROJECTS
(b) Here is table of interesting statistics about the first six planets:
Distance
from Sun
(astronomical
units)
Mass
(relative to
Earth)
Length
of
day
(hours)
Length
of year
(in
Earth
days)
Weight
of 1kg.
object
Mercury
0.387
Venus
0.723
Earth
1.00
Mars
1.524
Jupiter
5.203
Saturn
9.539
0.05
0.81
1.00
0.11
318.4
95.3
2106.12
718
23.93
24.62
9.83
10.03
88.0 days
224.7
days
365.3
days
687 days
11.86
years
29.46
years
0.25
0.85
1.00
0.36
2.64
1.17
Plot some or all of these statistics in bar graphs.
(c) Alter the MakeChange application in Figure 3, Chapter 3, so that it reads
its input interactively and displays the amounts of change as a four-bar
graph. (Think of these as “stacks” of coins!)
(d) Write an application that helps the user plot a bar graph of her own.
The application first asks the user for the title of the graph. Next, the
application asks for the largest value that will be plotted as a bar—this
will be used as the label on the graph’s y-axis. Then, the application asks
the user for the values of the six bars to be drawn. (If the user does not
want one of the six bars to appear, she types 0 for its value and newline
by itself for the bar’s label.) The application displays the bar graph as its
answer.
Notice that the application decides for itself the location of the x- and
y-axes. Here is how the interaction between the application and its user
might go (the user’s responses are stated in italics):
Please type the title of your graph: <em>Days in the months</em>
Please type the value of the largest bar you will draw: <em>31</em>
208
Please type the name of the first bar: <em>Jan</em>
Please type the value of the first bar: <em>31</em>
... etc. ...
7. Another form of graph is a sequence of plotted points, connected by lines. Here
is a plot of the equation, y = x*x, where the values of y for x equals 0,1,...,5 are
plotted and connected:
Write a class, PointGraphWriter, that generates a graph of exactly 6 plotted
points. The class should meet this specification:
209
5.8. PROGRAMMING PROJECTS
class PointGraphWriter
setAxes(int x pos, int
y pos, int axis length,
String x label, String
y label)
setPoint1(int height)
setPoint2(int height)
setPoint3(int
setPoint4(int
setPoint5(int
setPoint6(int
height),
height),
height),
height)
helps a user draw a graph of 6 plotted points
Methods
draw the the vertical and horizontal axes of the
graph, such that the intersection point of the
axes lies at position x pos, y pos. Each axis has
the length, axis length pixels. The beginning
labels of both the x- and y-axes are 0; the label
at the top of the y-axis is y label, and the label
at the end of the x-axis is x label.
plot the first point of the graph, so that it appears at the 0-position on the x-axis and at the
height position on the y-axis.
plot the second point of the graph, so that its xposition is at one-fifth of the length of the x-axis
and at the height position on the y-axis.
plot the third and subsequent points of the
graph, so that they are equally spaced along
the x-axis and appear at the specified height
on the y-axis. Each point is connected to its
predecessor by a straight line.
Test the class you write on these applications:
(a) the graph seen above. Here is the application that does this; test your
coding of class PointGraphWriter with it.
public class TestPlot
{
public static void main(String[] a)
{ PointGraphWriter e = new PointGraphWriter();
e.setTitle("Graph of y = x*x");
e.setAxes(50, 110, 90, "5", "30");
// axes start at position 50,110; the axes have length 90 pixels
// x-axis is labelled 0..5
// y-axis is labelled 0..30
int scale_factor = 3;
e.setPoint1(0 * scale_factor);
// 0*0 = 0
e.setPoint2(1 * scale_factor);
// 1*1 = 1
e.setPoint3(4 * scale_factor); // 2*2 = 4
e.setPoint4(9 * scale_factor); // etc.
e.setPoint5(16 * scale_factor);
e.setPoint6(25 * scale_factor);
}
}
210
(b) the values of y, for x equals 0, 2, 4, 6, 8, 10. (Note: set y’s scale to the
range 0..100.)
i.
ii.
iii.
iv.
y = x2 + 2x + 1
y = 90 − (0.8x)2
y = 20x − (0.5x)3
y = 0.1(x3 ) + x2 − x
(c) Write an application that helps the user plot a graph of her own. The
application asks the user the maximum values for the x- and y- axes, and
then the application asks the user for the values of the six points to be
plotted. The application displays the plotted graph as its answer.
(d) Use class PointGraphWriter to plot the daily high temperatures at your
home for the past six days.
(e) Use class PointGraphWriter to plot the weekly share value over the past
6 weeks of a stock of your choosing
(f) Recall that the formula to calculate the distance, d, that an automobile
travels starting from initial velocity, V0 , and acceleration, a, is defined
d = V0 t + (1/2)a(t2 )
Write an application that asks the user to submit values for V0 and a; the
application produces as its output a plotted graph that shows the distances
travelled for times 0, 2, 4, ..., 10.
8. Here is an example of a “pie chart”:
The chart was generated from an output-view object with this specification:
211
5.8. PROGRAMMING PROJECTS
class PieChartWriter
helps a user draw a pie chart of at most 6
“slices”
Methods
setSlice1(String label, int
amount, Color c)
setSlice2(String label,
int amount, Color c),
setSlice3(String label,
int amount, Color c),
setSlice4(String label,
int amount, Color c),
setSlice5(String label,
int amount, Color c),
setSlice6(String label,
int amount, Color c)
draw the first slice of the chart, such that amount
indicates the amount of the slice, and c is the
slice’s color. The label is printed to the right
of the pie, and it is printed in the color, c.
draw the subsequent slices and their labels.
Use the class to plot
(a) the chart seen above. Here is the application that does this; test your
coding of class PieChartWriter with it.
import java.awt.*;
public class TestPieChart
{ public static void main(String[] args)
{ PieChartWriter p = new PieChartWriter();
p.setTitle("How I spend my day");
p.setSlice1("Sleep: 7 hours", 7, Color.black);
p.setSlice4("Recreation: 9 hours", 9, Color.gray);
p.setSlice2("In class: 3 hours", 3, Color.blue);
p.setSlice3("Homework: 5 hours", 5, Color.red);
}
}
(b) these percentages about the income and outlays of the United States government in Fiscal Year 1997:
INCOME:
personal income taxes: 46%
social security and medicare taxes: 34%
corporate income taxes: 11%
excise and customs taxes: 8%
borrowing to cover deficit: 1%
212
OUTLAYS:
social security and medicare: 38%
national defense: 20%
social programs: 18%
interest on national debt: 15%
human and community development: 7%
general government: 2%
(c) the statistics regarding the masses of the planets in the table seen two
exercises earlier.
(d) how you spend your monthly budget
5.9
Beyond the Basics
5.9.1 Naming Variables, Methods, and Classes
5.9.2 Generating Web Documentation with javadoc
5.9.3 Static Methods
5.9.4 How the Java Compiler Checks Typing of Methods
5.9.5 Formal Description of Methods
5.9.6 Revised Syntax and Semantics of Classes
These optional sections build on the core materials in the chapter and show
• how to invent names for methods, classes, parameters, and variables
• how to use the javadoc program to create API documentation for the classes
you write
• how the main method can use static private methods
• how to state precisely the syntax and semantics of method declaration and invocation
5.9.1
Naming Variables, Methods, and Classes
Here are the guidelines used in this text for devising names for variables, methods,
and classes. The guidelines are devised so that we can read a name and deduce almost
immediately whether it is a name of a variable, a method, or a class.
213
5.9. BEYOND THE BASICS
• The name of a class should begin with an upper-case letter, and the first letter
of each individual word in the name should be upper case also. Examples are
MyWriter and GregorianCalendar. Underscores, , and dollar signs, $, although
legal, are discouraged.
• The name of a method should begin with a lower-case letter, and the first letter
of each individual word in the name should be upper-case, e.g., writeSentence.
Underscores and dollar signs are discouraged. If the method returns no result
(its return type is void), then use an imperative verb or predicate phrase for
its name, (e.g., printBee or writeSentence). If the method is a function (its
return type is non-void), then use a phrase that describes the result or how to
compute the result (e.g., celsiusIntoFahrenheit).
• The names of formal parameters and variables should be should be descriptive
but succinct. In this text we use all lower-case letters for variables, where the the
underscore character separates individual words in a variable name, e.g., answer
and left edge. This distinguishes variable names from method and class names.
A commonly used alternative is to use the same spelling for method names as
for variable names—do as you please.
• When a field variable receives an initial value that never changes, it might be
spelled with all upper-case letters and underscores, e.g., FRAME WIDTH.
Begin Footnote: Java provides a special variant of a field variable, called a final
variable, which is a field whose value is set once and never changed thereafter.
Final variables are studied in Chapter 9. End Footnote
5.9.2
Generating Web Documentation with
javadoc
In Chapter 2, we observed that Java’s packages, like java.lang and java.util, are
documented with HTML-coded web pages. The web pages are called the API (Application Programming Interface) documentation and were generated automatically
from the Java packages themselves by the tool, javadoc.
The current chapter suggested that every class should have a specification. Further, information from the specification should be inserted into the comments that
prefix the class’s methods. Every class’s specification should be documented, ideally
with an an HTML-coded Web page. We obtain the last step for free if the class’s
comments are coded in “javadoc style,” because we can use the javadoc program to
generate the Web pages.
javadoc is a program that reads a Java class, locates all comments bounded by the
format /** ... */, and reformats them into Web pages. For example, we can apply
javadoc to class MyWriter. by typing at the command line javadoc MyWriter.java
(Note: The previous command works if you use the JDK. If you use an IDE, consult
214
the IDE’s user guide for information about javadoc.) Here is the page created for the
class:
The comments from MyWriter have been attractively formatted into a useful description and annotated with additional information about the built-in graphics classes
that were included by inheritance with MyWriter If we scroll the page downwards, we
5.9. BEYOND THE BASICS
215
encounter a summary of the class’s public methods:
Now, a user of MyWriter need not read the program to learn its skills—the API
documentation should suffice!
If we wish more information about one of the methods, say, writeSentence, we
216
jump to the link named writeSentence and view the following:
The commentary from the writeSentence method has been extracted and formatted
in a useful way.
The format of comments that javadoc reads is as follows:
• A class should begin with a comment formatted with /** ... */. The comment must begin with a one-sentence description that summarizes the purpose
5.9. BEYOND THE BASICS
217
of the class. The comment can extend over multiple lines, provided that each
line begins with *. If desired, lines of the form, * @author ... and * @version
... , can be included in this comment to indicate the author of the class and
date or version number of the class.
• Prior to each method in the class, there is a comment stating the purpose of
the method, the purposes of its parameters, and the purpose of the result that
the method returns.
– The comment begins with /**, and each subsequent line begins with *.
The first sentence of the comment must state the purpose of the method.
– Each parameter is documented on one or more lines by itself, beginning
with * @param
– The method’s result is documented on one or more lines by itself, beginning
with * @return
– The comment is terminated with the usual */
– If a method can possibly “throw” an exception (e.g., a division by zero, as
seen in Chapter 3), a comment line labelled @throw can be used to warn
its user.
• If desired, comments of the form, /** ... */, can be included before the fields
and private methods of the class.
One-line comments of the form, // ..., are ignored by javadoc.
Important: javadoc will not normally include commentary about private fields
and methods in its web pages, because these components are not meant for public
use. If you desire documentation of a class’s private components, you obtain this by
using the -private option, e.g., javadoc -private TemperaturesWriter.java.
javadoc is best used to document a package of classes; we organize applications
into packages in in Chapter 9.
Exercise
Apply javadoc to class TemperaturesWriter in Figure 10, once with and once without the -private option. Do the same for class DialogReader in Figure 14.
5.9.3
Static Methods
An application is typically designed as a collection of classes, where each class contains
methods and private field variables. A class’s methods and fields work together to
perform the class’s responsibilities—a good example is class MyWriter in Figure 6.
218
Occasionally, one writes a method that does not depend on field variables and can
stand by itself, that is, the method need not be owned by any particular object and
need not be coded in any particular class. For historical reasons, such a method is
called static.
The main method, which starts every Java application, is the standard example of
a static method: main need not belong to any particular object or class. In this text,
for each application we typically write a separate “start up” class that holds main.
But main need not be placed in a class by itself—it can be inserted in any one of an
application’s classes, as we saw in Figure 9, where a trivial method was inserted into
class StackedEggsWriter for convenient start-up.
In Java, a static method like main is labelled static in its header line. The label,
static, means that the method can be invoked without explicitly constructing an object
that holds the method. This is indeed what happens when we start a Java application.
Here is a picture in computer storage after the StackedEggsWriter class from
Figure 9 is started:
StackedEggsWriter
main
{ // new StackedEggsWriter() constructed a StackedEggsWriter object
1> }
a1 : StackedEggsWriter
private int FRAME WIDTH ==
private int FRAME HEIGHT==
300
[this is the object
that is constructed]
200
... // other fields are here
public void paintComponent( ... ) { ...
private int paintAnEgg( ... ) { ... }
}
The static portion embedded within class StackedEggsWriter is extracted and kept
separate from the new StackedEggsWriter() objects that are subsequently constructed.
The new StackedEggsWriter() object never contains the static portion of the class.
Technically, the static portion of class StackedEggsWriter is not an “object” like
the objects created by new StackedEggsWriter() (in particular, it does not have an
address), but it does occupy storage and we will continue to draw it to look like an
object.
There can be other static methods besides main. For example, here is a rewrite of
the temperature-conversion application, where main relies on its own private method
to convert the input temperature:
import java.text.*;
import javax.swing.*;
/** CelsiusToFahrenheit2 converts an input Celsius value to Fahrenheit.
*
input: the degrees Celsius, an integer read from a dialog
*
output: the degrees Fahrenheit, a double */
5.9. BEYOND THE BASICS
219
public class CelsiusToFahrenheit2
{ public static void main(String[] args)
{ String input =
JOptionPane.showInputDialog("Type an integer Celsius temperature:");
int c = new Integer(input).intValue(); // convert input into an int
double f = celsiusIntoFahrenheit(c);
DecimalFormat formatter = new DecimalFormat("0.0");
System.out.println("For Celsius degrees " + c + ",");
System.out.println("Degrees Fahrenheit = " + formatter.format(f));
}
/** celsiusIntoFahrenheit translates degrees Celsius into Fahrenheit
* @param c - the degrees in Celsius
* @return the equivalent degrees in Fahrenheit */
private static double celsiusIntoFahrenheit(int c)
{ return ((9.0 / 5.0) * c) + 32; }
}
The private method, celsiusIntoFahrenheit, must itself be labelled static, because
the private method is itself invoked without explicitly constructing an object that
holds it. As a rule, all methods invoked by a static method must themselves be
labelled static.
There is another use of static methods in Java: We might argue that some
methods, like the square-root, exponentiation, sine, and cosine methods, really do
not need to be held within an explicitly constructed object. Of course, one might also
argue that it is reasonable to construct an object that has the abilities to compute
precisely these operations!
The Java designers followed the first argument, and they wrote class Math, which
resides in the package java.lang. Inside class Math are the static methods, sqrt,
pow, sin, and cos, mentioned above. Because these methods are labelled static, we
invoke them differently than usual: Rather than constructing a new Math() object
and sending sqrt messages to it, we invoke the method directly:
double d = Math.sqrt(2.0);
Instead of an object name, an invoked static method is prefixed by the name of the
class where the method is coded. This is analogous to starting an application by
typing the name of the class where the main method is coded.
Other examples of static methods are showInputDialog and showMessageDialog
from class JOptionPane. Indeed, as you utilize more of Java’s libraries, you will
encounter classes holding just static methods (or classes with a mixture of static and
normal—nonstatic—methods). Please remember that a static method is invoked by
prefixing it with its class name; a normal method is invoked by constructing an object
from the class and sending the object a message to invoke its method.
220
In this text, the only static method we ever write will be main.
Finally, we note that Java allows fields to be labelled static and public as well!
We will not develop this variation at this time, other than to point out that class
Math uses it to give a convenient name for the mathematical constant, Pi: When you
write,
System.out.println("Pi = " + Math.PI);
you are using a public, static field, named PI, that is declared and initialized within
class Math.
Static fields make later appearances in Chapters 8 and 10.
5.9.4
How the Java Compiler Checks Typing of Methods
The data typing information in a method’s header line helps the Java compiler validate
both the well formedness of the method’s body as well as the correctness of invocations
of the method. When the compiler reads a method like
public double celsiusIntoFahrenheit(double c)
{ double f = ((9.0 / 5.0) * c) + 32;
return f;
}
the compiler verifies that the method’s body uses parameter c the way doubles should
be used. Also, all statements of the form return E must contain expressions, E, whose
data type matches the information in the header line. In the above example, the
compiler does indeed verify that f has type double, which matches the data type that
the header line promises for the function’s result.
When the compiler encounters an invocation, e.g., convert.celsiusIntoFahrenheit(68),
the compiler verifies that
• the data type of convert is a class that has a celsiusIntoFahrenheit method.
• the data type of the actual parameter, 68, is compatible with the data type
of the corresponding formal parameter of celsiusIntoFahrenheit. (More precisely, the data type of the actual parameter must be a subtype of the formal
parameter’s type; here, an actual parameter that is an integer is acceptable to
a formal parameter that is a double.)
• the answer, if any, returned by the method can be used at the position where
the invocation is located.
But most important, when the Java compiler studies an invocation, the compiler does
not reexamine the body of invoked method; it examines only the method’s header line.
When the compiler examines an entire class, it analyses the class’s fields and methods, one by one. When it concludes, the compiler collects an interface specification for
221
5.9. BEYOND THE BASICS
the class, based on the information it extracted from the header lines of the class’s
public components. The information is used when the compiler examines another
class that references this first one. In this way, the Java compiler can systematically
analyze an application built from multiple classes.
A more precise description of data-type checking of methods follows in the next
section.
5.9.5
Formal Description of Methods
Because method writing is fundamental to programming, here is a formal description
of the syntax and semantics of method definition and invocation. The steps behind
type checking and executing method invocations are surprisingly intricate, mainly
because Java allows one class to extend (inherit) another’s structure.
The material that follows is provided as a reference and most definitely is nonrequired reading for the beginner.
Method Definition
The format of method introduced in this chapter extends the syntax of methods that
appeared at the end of Chapter 2. We now have this form:
METHOD ::=
VISIBILITY static?
METHOD_BODY
RETURN_TYPE
METHOD_HEADER
Recall that ? means that the preceding phrase is optional.
VISIBILITY ::= public | private
RETURN_TYPE ::= TYPE | void
METHOD_HEADER ::= IDENTIFIER ( FORMALPARAM_LIST? )
A method can be public or private and can optionally return a result. The syntax of
TYPE remains the same from Chapter 3.
FORMALPARAM_LIST ::= FORMALPARAM [[ , FORMALPARAM ]]*
FORMALPARAM ::= TYPE IDENTIFIER
METHOD_BODY ::= { STATEMENT* }
A method can have a list of parameters, separated by commas. (Recall that * is read
as “zero or more” and the double brackets are used for grouping multiple phrases.)
The syntax of STATEMENT is carried over from Chapter 2, but one form of statement
changes:
INVOCATION ::=
[[ RECEIVER . ]]? IDENTIFIER ( ARGUMENT_LIST? )
because the RECEIVER can be omitted when an object invokes one of its own methods.
Also, a new statement form is included:
222
RETURN ::=
return EXPRESSION ;
A method definition is well typed if
• All of the FORMALPARAMs in a method’s header line are distinctly named identifiers.
• The STATEMENTs in the method’s body are well typed, assuming use of these
extra variables:
– each formal parameter, TYPE IDENTIFIER, defines a variable, IDENTIFIER of
type TYPE.
– each field, private TYPE IDENTIFIER, in the class in which this method
appears defines a variable, IDENTIFIER of type TYPE.
In case a field has the same name as a local variable or formal parameter, the
latter takes precedence in usage.
• In the method’s body, the E component of every return E statement has the
data type specified as the method’s RETURN TYPE. (If RETURN TYPE is void, no
return E statements are allowed.)
The semantics of a method definition is that the name, formal parameters, and
body of the method are remembered for later use.
Method Invocation
Method invocations change little from their format in Chapter 2:
INVOCATION ::=
[[ RECEIVER . ]]? IDENTIFIER ( ARGUMENT_LIST? )
In this section, we consider invocations of only normal (non-static) methods; see the
subsection below for comments about invocations of static methods.
Say that the Java compiler must check the data types within an invocation that
looks like this:
[[ RECEIVER . ]]?
NAME0 ( EXPRESSION1, EXPRESSION2, ..., EXPRESSIONn)
As noted in the preceding section, the data-type checking proceeds in several steps:
1. Determine the data type of RECEIVER: if RECEIVER is omitted, then use the class
name where the invocation appears; if RECEIVER is a variable name, then use the
variable’s data type (which must be a class name); if RECEIVER is an arbitrary
expression, calculate its data type (which must be a class name).
Let C0 be the data type that is calculated.
223
5.9. BEYOND THE BASICS
2. Locate the method, NAME0: Starting at class C0, locate the best matching method
with the name, NAME0. (The precise definition of “best matching method” is
given below.) The best matching method will have its own METHOD HEADER, and
say that the header line has this form:
VISIBILITY TYPE0 NAME0(TYPE1 NAME1, TYPE2 NAME2, ..., TYPEn NAMEn)
3. Attach the header-line information to the invocation: The Java compiler attaches the data-type names, TYPE1, TYPE2, ..., TYPEn, to the actual parameters, EXPRESSION1, EXPRESSION2, ..., EXPRESSIONn, for use when the method
is executed by the Java Virtual Machine.
If the located method, NAME0, is a private method (that is, VISIBILITY is
private) and was found within class Ck, then the label, private Ck, is attached to the invocation as well. If NAME0 is a public method, then the label,
public, is attached.
Therefore, when NAME0 is a public method, the compiler annotates the invocation
to look like this:
[[ RECEIVER . ]]? NAME0 (EXPRESSION1: TYPE1, EXPRESSION2: TYPE2,
..., EXPRESSIONn:TYPEn) public;
When NAME0 is a private method, the invocation looks like this:
[[ RECEIVER . ]]? NAME0 (EXPRESSION1: TYPE1, EXPRESSION2: TYPE2,
..., EXPRESSIONn:TYPEn) private Ck;
4. Return the result data type: The overall data type of the method invocation is
TYPE0. (If TYPE0 is void, then the invocation must appear where a statement is
expected.)
We will see below how the compiler’s annotations are used to locate and execute an
invoked method.
Here is an example. Given this class,
public class Test
{ public static void main(String[] args)
{ FourEggsWriter w = new FourEggsWriter();
String s = setTitle(2);
w.setTitle(s);
}
private static String setTitle(int i)
{ return ("New " + i); }
}
// see Figure 2
224
the compiler must analyze the invocation, setTitle(2). Since there is no prefix on
the invocation, the data type of the receiver is this class, Test. A search of class
Test locates the private method, setTitle, whose formal parameter’s type matches
the actual parameter type. This is the best matching method. The compiler annotate
the invocation to read,
setTitle(2: int) private Test;
and it notes that the result will be a String, which is acceptable.
Next, the compiler analyzes w.setTitle(s). The prefix, w, has type FourEggsWriter,
so that class is searched for a public method named setTitle. None is found,
but since class FourEggsWriter extends JFrame, the compiler searches class JFrame
and finds the best matching method there, JFrame’s setTitle. The invocation is annotated as
w.setTitle(s: String) public;
Definition of Best Matching Method
As the two previous examples indicate, the “best matching method” for an invocation
is the method, NAME0, whose n formal parameters have data types that match the
data types of the n actual parameters. But we should state this more precisely, if
only because the concept is extended in Chapter 9.
Here is a more precise definition:
Again, let
[[ RECEIVER . ]]?
NAME0 ( EXPRESSION1, EXPRESSION2, ..., EXPRESSIONn)
be the invocation that is analyzed; we assume that only non-static methods are
invoked. Let C0 be the data type of the RECEIVER. (If RECEIVER is omitted, we take C0
to be the name of the class where the invocation appears.) If the invocation appears
within class C0 also, then the best matching method is the method the compiler finds
by searching(all public and private methods of class C0); Otherwise, the best
matching method is the method found by searching(all public methods of class
C0).
The algorithm for searching(some methods of class C0) is defined as follows:
1. Within some methods of class C0, find the method whose header line has the
name and parameters,
NAME0(TYPE1 NAME1, TYPE2 NAME2, ..., TYPEn NAMEn)
such that
• There are exactly n formal parameters to match the n actual parameters.
5.9. BEYOND THE BASICS
225
• The data type of each actual parameter, EXPRESSIONi, is a (sub)type of
TYPEi, the data type of the corresponding formal parameter, NAMEi.
If the method is found, the search is successful.
2. If there is no such method, NAME0, that fits these criteria, and if class C0
extends C1, then the best matching method comes from searching(all public
methods of class C1). But if class C0 extends no other class, there is no best
matching method, and the search fails.
We return to the earlier example; consider
w.setTitle(s);
The compiler finds the best matching method for the invocation; since the prefix, w,
has type FourEggsWriter, but the invocation appears within class Test, the compiler does searching(all public methods of class FourEggsWriter). The compiler
finds no method in FourEggsWriter that matches, but since FourEggsWriter extends
JFrame, it does searching(all public methods of class JFrame). There, the compiler locates the method; its header line is
public void setTitle(String title)
The above definition of best matching method will serve us well until we encounter
the difficult issue of method overloading based on parameter type; this is studied in
Chapter 9.
Executing Method Invocations
Finally, we consider the execution semantics of method invocation. Thanks to the
compiler’s annotations, the invocation has this form:
[[ RECEIVER ]]?
NAME0(EXPRESSION1: TYPE1, EXPRESSION2:TYPE2,
..., EXPRESSIONn:TYPEn) SUFFIX
(Recall that SUFFIX is either private Ck or public.) For simplicity, we consider
invocations of only normal (non-static) methods.
Execution proceeds in the following steps:
1. Calculate the address of the receiver object: Compute RECEIVER to get the address of an object. (If RECEIVER is absent, use the address of the object in which
this invocation is executing.) Call the address, a. Let C0 be the run-time data
type that is attached to the object at address a.
2. Select the method in the receiver:
• If the SUFFIX is private Ck, select the private method from class Ck,
whose header line has the form:
226
private ... NAME0(TYPE1 NAME1, TYPE2 NAME2, ..., TYPEn NAMEn)
This method must exist for object a, because the Java compiler located it
during the type-checking phase.
• If the SUFFIX is public, then use data type C0 to search for the method:
Starting with the public methods from class C0, search for a method
whose header line has the form,
public ... NAME0(TYPE1 NAME1, TYPE2 NAME2, ..., TYPEn NAMEn)
If the method is found, select it. Otherwise, search the public methods that
come from class C1, where class C0 extends C1; repeat the search until
the method is found. The method must be found because the compiler
found it during type checking.
3. Evaluate the actual parameters: Each of EXPRESSION1, EXPRESSION2, ..., EXPRESSIONn
is evaluated in turn to its result. Call the results r1, r2, ..., rn.
4. Bind the parameters: Variables are created with the names TYPE1 NAME1, TYPE2
NAME2, ..., TYPEn NAMEn, and these cells are assigned the values r1, r2, ..., rn,
respectively.
5. Execute the method: The statements in the body of method NAME0 are executed
with the new variables. Any reference to a name, NAMEi, is evaluated as a lookup
into the cell for variable NAMEi. Execution of the body terminates when the end
of the body is reached or when a statement of the form return EXPRESSION is
encountered: the EXPRESSION is evaluated to its result, r, and r is returned as
the result.
Steps 4 and 5 are the same as as executing this code inside object PREFIX:
{ TYPE1 NAME1 = r1;
TYPE2 NAME2 = r2;
...
TYPEn NAMEn = rn;
BODY
}
Step 2 is necessary so that there is no confusion when a programmer uses the
same name, p, for a private method in one class and a public method in another
class. Also, the “searching” that one does to select a public method is necessary to
resolve overridden methods. (For example, class JFrame has a useless paint method,
but in Figure 2 class FourEggsWriter has a newer paint method. When we create
a FourEggsWriter object, it has two paint methods, but the newer one is always
selected.)
5.9. BEYOND THE BASICS
227
Static Methods
A static method can be invoked by making the RECEIVER part of an invocation,
RECEIVER NAME0( ... ) be the name of a class, e.g., Math.sqrt(2). Or, if the
RECEIVER part is omitted, and the invocation appears in the body of a static method,
then the invocation is treated as an invocation of a static method.
When the Java compiler encounters an invocation of a static method, it searches
for the best matching method only amongst the static methods. As usual, the search
for the best matching method begins at the class named by the RECEIVER. When the
Java compiler locates the best matching static method, it uses the information in the
method’s header line to annotate the invocation as seen above. The SUFFIX part of
the annotation is static Ck, where Ck is the class where the best matching method
was located.
When the Java Virtual Machine executes the invocation, the suffix, static Ck
tells it to execute the static method in Ck.
5.9.6
Revised Syntax and Semantics of Classes
Classes now have fields and constructor methods, so here are the augmentations to
the “Syntax” and “Semantics” sections in Chapter 2.
The syntax of a class becomes
public class IDENTIFIER EXTEND?
{ FIELD*
CONSTRUCTOR
METHOD*
}
EXTEND ::= extends IDENTIFIER
FIELD ::= private DECLARATION
CONSTRUCTOR ::= public METHOD_HEADER
{ STATEMENT* }
Data-type checking is performed on the fields, constructor method, and methods of
the class. No two fields may be declared with the same name. Two methods may be
declared with the same name if they have different quantities or data types of formal
parameters; this issue is explored in Chapter 9.
The semantics of a class is that it is remembered for creating objects.
An object is constructed from a class, class C0, by an expression of the form
new C0( EXPRESSION1, EXPRESSION2, ..., EXPRESSIONn )
The semantics of object construction goes as follows:
1. Construct an object in computer storage: class C0 is located, and copy of its
components are allocated in storage. If C0 extends another class, C1, then C1’s
components are copied into the object as well.
228
2. Execute all the field declarations.
3. Execute the constructor method: The phrase, C0(EXPRESSION1, EXPRESSION2,
..., EXPRESSIONn), is executed like a method invocation, as described in the
previous section.
4. Remove all constructor methods from the object—they are no longer needed.
5. Return as the result the address of the object.
Chapter 6
Control Structure: Conditional Statements and Software Architecture
6.1 Control Flow and Control Structure
6.2 Conditional Control Structure
6.2.1 Nested Conditional Statements
6.2.2 Syntax Problems with Conditionals
6.3 Relational Operations
6.4 Uses of Conditionals
6.5 Altering Control Flow
6.5.1 Exceptions
6.5.2 System Exit
6.5.3 Premature Method Returns
6.6 The Switch Statement
6.7 Model and Controller Components
6.8 Case Study: Bank Accounts Manager
6.8.1 Collecting Use-Case Behaviors
6.8.2 Selecting a Software Architecture
6.8.3 Specifying the Model
6.8.4 Writing and Testing the Model
6.8.5 Specifying the View Components
6.8.6 A Second Look at the Software Architecture
6.8.7 Writing the View Classes
6.8.8 Controller Construction and Testing
6.8.9 Testing the Assembled Application
6.8.10 Multiple Objects from the Same Class
230
6.9 More about Testing Classes and Methods
6.9.1 Testing Individual Methods
6.9.2 Testing Methods and Attributes Together
6.9.3 Testing a Suite of Methods
6.9.4 Execution Traces
6.10 Summary
6.11 Programming Projects
6.12 Beyond the Basics
Now that we have basic skills at designing a program’s component structure, we concentrate on writing more complex applications that react more intelligently to their
input data. The applications use control structures to do so:
• We study program control flow and a new statement form, the conditional statement, which enables a method to ask questions about the values of its arguments.
Based on the answers to the questions, the method can control the computation
to a path best suited to the arguments’ values.
• We study logical relational operators and learn the logic behind conditional statements.
• We use our knowledge of control flow to develop application building in more
detail, so that the flow of control between components is clear. Specifically,
we divide an application into three subassemblies: model, view, and controller,
where the view takes resposibility for data input and output, the controller takes
responsibility for control flow, and the new component, the model, takes responsibility for computing answers.
This Chapter complements the previous chapter by showing that control and component structure are complementary and equally important to program development.
6.1
Control Flow and Control Structure
The order in which a program’s statements execute is called its control flow. Of course,
by writing statements in a sequential order and inserting method invocations, a programmer specifies a program’s control flow. When a programmer writes statements
in a sequential ordering,
6.2. CONDITIONAL CONTROL STRUCTURE
231
STATEMENT1;
STATEMENT2;
...
STATEMENTn;
this decides the control flow that STATEMENT1 executes first, followed by STATEMENT2
and so on. A method invocation,
RECEIVER.METHOD(ARGUMENTS);
decides the control flow that execution pauses at the place of invocation so that the
statements named by METHOD in RECEIVER execute next.
Sequencing and invocation are control structures because they order or “structure”
a program’s control flow. A programmer must develop intutitions about control flow,
and the execution traces presented in previous chapters were intended to foster these
intuitions.
One deficiency of the two control structures just seen is that they are unable to
change the control flow based on the values of input data or the occurrence of errors.
To remedy this limitation, we must learn a new control structure.
6.2
Conditional Control Structure
Although we have accomplished an impressive amount of class building, we must
make our classes’ methods more “intelligent.” Specifically, we wish to enable our
methods to ask questions about the arguments they receive so that the methods can
choose a control flow best suited to the arguments’ values.
Real-life algorithms often contain instructions that take a course of action based
on the answer to a question, for example,
• When driving to the airport, “if the time is after 4 p.m., then avoid heavy traffic
by taking the frontage road to the airport exit; otherwise, take the freeway,
which is more direct.”
• When preparing a Bearnaise sauce, “if the taste requires more emphasis, then
add a pinch of salt.”
The answers to these questions are determined while the algorithm executes. Similarly, a method might ask questions of the actual parameters it receives when it is
invoked. Such questions are posed with a conditional statement, also known as an
if-statement. A conditional statement asks a question, and depending whether the
answer is “yes” (true) or “no” (false), one of two possible statement sequences is
executed.
The syntax of the conditional statement is
232
if ( TEST )
{ STATEMENTS1 }
else { STATEMENTS2 }
where the TEST is an expression that must evaluate to a boolean-typed answer, that
is, true or false. (An example of such an expression is 4 > (2 + 1), which computes
to true; see Chapter 3 for many other examples.) If TEST evaluates to true, then
STATEMENTS1 are executed and statements S2 are ignored. If TEST evaluates to false,
STATEMENTS2 are executed and STATEMENTS1 are ignored.
Here is a method that uses a conditional:
/** printPolarity prints whether its argument is negative or not.
* @param i - the argument */
public void printPolarity(int i)
{ System.out.print("The argument is ");
if ( i < 0 )
{ System.out.println("negative."); }
else { System.out.println("nonnegative."); }
System.out.println("Finished!");
}
The method cannot forecast the value of the argument sent to it, so it uses a conditional statement that asks a question about the argument. If the method is invoked
with, say, printPolarity(3 - 1), the command window displays
The argument is nonnegative.
Finished!
which shows that the test, i < 0, evaluated to false and the statement labelled
by else was executed. The conditional statement is written on several lines to aid
readability; the statements preceding and following the conditional execute as usual.
In contrast, the invocation, printPolarity(-1), causes
The argument is negative.
Finished!
to print. This shows that the method chooses its computation based on the value of
its actual parameter—there are two possible control flows through the method, and
the appropriate control flow is selected when the method is invoked.
Some people like to visualize a conditional statement as a “fork in the road” in their
program; they draw a picture of if ( TEST ) { STATEMENTS1 } else { STATEMENTS2
6.2. CONDITIONAL CONTROL STRUCTURE
233
} like this:
TEST?
true
STATEMENTS1
false
STATEMENTS2
The two paths fork apart and join together again, displaying the two possible control flows. The picture also inspires this terminology: We call statements STATEMENTS1
the then-arm and call statements STATEMENTS2 the else-arm. (Many programming languages write a conditional statement as if TEST then STATEMENTS1 else STATEMENTS2;
the keyword, then, is omitted from the Java conditional but the terminology is preserved.)
Occasionally, only one plan of action is desired, and a conditional statement written as
if ( TEST )
{ STATEMENTS }
which is an abbreviation of if ( TEST ) { STATEMENTS } else { }.
An arm of a conditional statement can hold multiple statements, as we see in the
application in Figure 1, which translates hours into seconds. The algorithm upon
which the application is based is simple but significant, because it asks a question:
1. Read an integer, representing a quantity of hours, from the user.
2. If the integer is nonnegative, then translate the hours into seconds.
Else, the integer is not nonnegative (that is, it is negative), so display an error
message.
The application uses a conditional statement to ask the question whether the
user typed an appropriate input value. The programmer can safely assume that the
statements in the then-arm execute only when hours has a nonnegative value—the
if-statement’s test provides a precondition that “guards” entry of control flow into
the then-arm.
The then-arm consists of two statements, showing that sequential control can
“nest” within conditional control. Further, one of the statements sends a message,
showing that invocation control can nest within conditional control. It is sensible for
conditional control to nest within conditional control as well, as we see in the next
section.
234
Figure 6.1: application that converts hours into seconds
import javax.swing.*;
/** ConvertHours translates a time in hours into its equivalent in seconds.
* Input: a nonnegative integer
* Output: the converted time into seconds. */
public class ConvertHours
{ public static void main(String[] args)
{ int hours = new Integer(
JOptionPane.showInputDialog("Type hours, an integer:")).intValue();
if ( hours >= 0 )
{ // at this point, hours is nonnegative, so compute seconds:
int seconds = hours * 60 * 60;
JOptionPane.showMessageDialog(null,
hours + " hours is " + seconds + " seconds");
}
else { // at this point, hours must be negative, an error:
JOptionPane.showMessageDialog(null,
"ConvertHours error: negative input " + hours);
}
}
}
Exercises
1. What will these examples print?
(a) double d = 4.14;
int i = 3;
if ( i == d )
{ System.out.println("Equal"); }
else { i = (int)d; }
if ( i != 3 )
{ System.out.println("Not equal"); }
(b) public static void main(String[] args)
{ System.out.println( f(2, 3) );
int i = 2;
System.out.println( f(i+1, i) );
}
private static String f(int x, int y)
{ String answer;
if ( x <= y )
{ answer = "less"; }
235
6.2. CONDITIONAL CONTROL STRUCTURE
else { answer = "not less"; }
return answer;
}
2. Say that variable x is initialized as int x = 1. For each of the following statements, locate the syntax errors that the Java compiler will detect. (If you are
uncertain about the errors, type the statements in a test program and try to
compile them.)
(a) if ( x ) { } else { x = 2; }
(b) if x>0 { x = 2 }
(c) if ( x = 0 ) { x = 2; }; else {}
3. Use conditional statements to write the bodies of these methods:
(a) /** printEven prints whether its argument is even or odd:
* It prints "EVEN" when the argument is even.
* It prints "ODD" when the argument is odd.
* @param a - the argument */
public void printEven(int a)
(b) /** minus subtracts its second argument from its first, provided that
* the result is nonnegative
* @param arg1 - the first argument, must be nonnegative
* @param arg2 - the second argument
* @return (arg1 - arg2), if arg1 >= arg2;
* return -1, if arg1 is negative or if arg2 > arg1
public int minus(int arg1, int arg2)
*/
(Hint: place a conditional statement inside a conditional.)
(c) /** div does integer division on its arguments
* @param arg1 - the dividend
* @param arg2 - the divisor
* @return (arg1 / arg2), provided that arg2
* return 0 and print an error message, if
public int div(int arg1, int arg2)
6.2.1
is nonzero;
arg2 == 0 */
Nested Conditional Statements
Conditionals can nest within conditional statements. This can be useful when there
are multiple questions to ask and asking one question makes sense only after other
questions have been asked. Here is a simple example.
Perhaps we revise the change-making application from Chapter 3 so that it verifies
that the dollars and cents inputs are reasonable: the dollars should be nonnegative
236
and the cents should range between 0 and 99. If any of these conditions are violated,
the computation of change making must not proceed.
This situation requires several questions to be asked and it requires that we nest
the questions so that the change is computed only when all the questions are answered
correctly. The algorithm for doing displays a nested structure:
1. Read the dollars, an integer
2. If dollars are negative, then generate an error message.
Else dollars are nonnegative:
(a) If cents are negative, then generate an error message.
Else cents are nonnegative:
i. If cents are greater than 99, then generate an error message.
Else cents are in the range 0..99:
A. Compute the total of the dollars and cents
B. Extract the appropriate amount of quarter, dime, nickel, and penny
coins.
We write the nested structure as several nested conditional statements; see Figure 2.
Each question is expressed in a conditional statement, and the nested is reflected
by the indentation of the statements. Brackets are aligned to help us see the control
structure.
When writing algorithms like the one for change making, some programmers like
to sketch a picture of the questions and the possible control flows. The picture,
called a flow chart, can prove helpful when there are numerous questions to ask and
the dependencies of the questions are initially unclear. Figure 3 shows the flowchart
corresponding to the change-making algorithm. From the flowchart, one can calculate
the possible control flows and consider what input data values would cause execution
of each flow; this is useful for testing the method. (See the Exercises below and the
Section on testing at the end of this Chapter.)
One disadvantage of nested conditionals is difficult readability—when reading a
statement in the middle of nested conditionals, a programmer can quickly forget which
questions led to the statement. For this reason, some programmers prefer to “unnest”
the conditionals by inserting a boolean variable that remembers the answers to the
conditionals’ tests.
We can unnest the conditionals Figure 2 with this technique; see Figure 4. The
boolean, ok, remembers whether the input-checking tests have been answered favorably. If so, calculation of change executes. The conditionals are simplified into if-then
statements (there are no else-arms; recall that if ( TEST ) { S } abbreviates if (
TEST ) { S } else { }). Nesting is greatly reduced, but the formal relationship between the original algorithm and the one implicit in Figure 4 is not easy to state—by
6.2. CONDITIONAL CONTROL STRUCTURE
237
Figure 6.2: change-making with nested conditionals
import javax.swing.*;
/** MakeChangeAgain calculates change in coins for a dollars, cents amount.
*
Input: two numbers supplied at the command line:
*
a dollars amount, a nonnegative integer;
*
a cents amount, an integer between 0 and 99.
*
Output: the coins */
public class MakeChangeAgain
{ public static void main(String[] args)
{ int dollars = new Integer(args[0]).intValue();
if ( dollars < 0 )
{ JOptionPane.showMessageDialog(null,
"MakeChangeAgain error: negative dollars: " + dollars);
}
else // dollars amount is acceptable, so process cents amount:
{ int cents = new Integer(args[1]).intValue();
if ( cents < 0 )
{ JOptionPane.showMessageDialog(null,
"MakeChangeAgain error: negative cents: " + cents);
}
else { if ( cents > 99 )
{ JOptionPane.showMessageDialog(null,
"MakeChangeAgain error: bad cents: " + cents);
}
else // dollars and cents are acceptable, so compute answer:
{ int money = (dollars * 100) + cents;
System.out.println("quarters = " + (money / 25));
money = money % 25;
System.out.println("dimes = " + (money / 10));
money = money % 10;
System.out.println("nickels = " + (money / 5));
money = money % 5;
System.out.println("pennies = " + money);
}
}
}
}
}
238
Figure 6.3: flowchart for change making
read dollars;
dollars negative?
error
read cents;
cents negative?
error
cents > 99?
error
total dollars and cents;
extract coins from total
considering all control flows we can determine whether the two applications behave
exactly the same way.
Exercises
1. What will this example print?
int i = 1;
if ( i < 0 )
{ System.out.println("a"); }
else { System.out.println("b");
if ( i == 1 )
{ System.out.println("c"); }
System.out.println("d");
}
System.out.println("e");
2. Use Figure 3 to devise test cases such that each control flow through MakeChangeAgain
is executed by at least one test case. Test the application with your test cases.
3. (a) Write this method; use nested conditionals
/** convertToSeconds converts an hours, minutes amount into the
* equivalent time in seconds.
* @param hours - the hours, a nonnegative integer
* @param minutes - the minutes, an integer in the range 0..59
* @return the time in seconds; in case of bad arguments, return -1 */
public int convertToSeconds(int hours, int minutes)
6.2. CONDITIONAL CONTROL STRUCTURE
Figure 6.4: unnesting conditional statements
import javax.swing.*;
/** MakeChangeAgain calculates change in coins for a dollars, cents amount.
*
Input: two numbers supplied at the command line:
*
a dollars amount, a nonnegative integer;
*
a cents amount, an integer between 0 and 99.
*
Output: the coins */
public class MakeChangeAgain2
{ public static void main(String[] args)
{ boolean ok = true; // remembers whether input data is acceptable
int dollars = new Integer(args[0]).intValue();
int cents = new Integer(args[1]).intValue();
if ( dollars < 0 )
{ JOptionPane.showMessageDialog(null,
"MakeChangeAgain error: negative dollars: " + dollars);
ok = false; // the error negates acceptability
}
if ( ok )
// dollars are acceptable, so consider cents:
{ if ( cents < 0 )
{ JOptionPane.showMessageDialog(null,
"MakeChangeAgain error: negative cents: " + cents);
ok = false;
}
if ( cents > 99 )
{ JOptionPane.showMessageDialog(null,
"MakeChangeAgain error: bad cents: " + cents);
ok = false;
}
}
if ( ok )
// dollars and cents are acceptable, so compute answer:
{ int money = (dollars * 100) + cents;
System.out.println("quarters = " + (money / 25));
money = money % 25;
System.out.println("dimes = " + (money / 10));
money = money % 10;
System.out.println("nickels = " + (money / 5));
money = money % 5;
System.out.println("pennies = " + money);
}
}
}
239
240
(b) Next, rewrite the previous method with no nested conditionals—use a
boolean variable to remember the status of the conversion.
4. The applications in Figures 2 and 4 do not behave exactly the same. Find a test
case that demonstrates this, and explain what changes must be made to Figure
4 so that it behaves exactly the same as Figure 2 with all possible inputs.
6.2.2
Syntax Problems with Conditionals
Java’s form of conditional statement has a problematic syntax that allows serious
errors to go undetected by the Java compiler.
For example, when an arm of a conditional is a single statement, it is legal to omit
the brackets that enclose the arm. This can prove disastrous—say that we have this
conversion method, and we forget the brackets around the else-arm:
public int convertHoursIntoSeconds(int hours)
{ int seconds;
if ( hours >= 0 )
{ int seconds = hours * 60 * 60; }
else
JOptionPane.showMessageDialog(null, "error: returning 0 as answer");
seconds = 0;
return seconds;
}
Because of the missing brackets, the Java compiler decides that the else-arm is just
one statement, and the compiler inserts this bracketing:
if ( hours >= 0 )
{ int seconds = hours * 60 * 60; }
else { JOptionPane.showMessageDialog(null, "error: returning 0 as answer"); }
seconds = 0;
which is certainly not what was intended.
Just as bad is an accidental omission of the keyword, else. The missing else in
the same example,
if ( hours >= 0 )
{ int seconds = hours * 60 * 60; }
{ JOptionPane.showMessageDialog(null, "error: returning 0 as answer");
seconds = 0;
}
is not noticed by the compiler, which assumes that the conditional has only a thenarm. As a result, the statements following the then-arm are always executed, which
is again incorrect.
Another problem that can arise when brackets are absent is the famous “dangling
else” example. Consider this example, deliberately badly formatted to reveal no
secrets:
6.3. RELATIONAL OPERATIONS
241
if ( E1 )
if ( E2 )
S1
else S2
After some thought, we can correctly conclude that the then-arm that mates to the
test, if ( E1 ), is indeed the second conditional statement, if ( E2 ) S1, but an
unresolved question is: Does else S2 belong to if ( E1 ) or if ( E2 )? The Java
compiler adopts the traditional answer of associating the else-arm with the most
recently occurring test, that is, else S2 mates with if ( E2 ). You should not rely
on this style as a matter of course; the price of inserting brackets is small for the
clarity gained.
Another undetected error occurs when an extra semicolon is inserted, say, just
after the test expression. This statement from Figure 7 is altered to have an extra
semicolon:
if ( minute < 10 );
{ answer = answer + "0"; }
Because of the semicolon, the compiler treats the conditional as if it has no then- or
else- arms! That is, the compiler reads the above as follows:
if ( minute < 10 ) { } else { }
{ answer = answer + "0"; }
This makes the conditional worthless.
Once you are certain your conditional statement is correctly written, you should
test it. Remember that a conditional statement contains two possible paths of execution and therefore should be tested at least twice, with one test that causes execution
of the then-arm and one test that causes execution of the else-arm.
6.3
Relational Operations
In Chapter 3, we encountered comparison operations for writing boolean-typed expressions; for review, here are some samples:
• i >= 3 (i is less-than-or-equals 3)
• 2 < (x + 1.5) (2 is less than x plus 1.5)
• y != 2 (y does not equal 2)
• 5 == 3*2 (5 equals 3 times 2)
242
Such expressions can be used as tests within conditional statements.
Sometimes we wish to insert two comparison operations within one test expression.
For example, the change-making applications in Figures 2 and 4 would benefit from
asking, “cents < 0 or cents > 99”? as one question, rather than two.
To do so, we may use relational operations, which operate on boolean arguments
and produce boolean answers. For example, the logical disjunction, “or,” is written
in Java as the relational operation, ||, so we can state,
if ( (cents < 0) || cents > 99) )
{ JOptionPane.showMessageDialog(null,
"MakeChangeAgain error: bad cents: " + cents);
...
}
This statement’s test calculates to true if the first phrase, cents < 0, computes to
true. If it computes to false, then the second phrase is computed—if cents > 99
computes to true then the test computes to true. If both phrases compute to false,
then so does the test. In this way, two related questions can be efficiently asked at
the same point in the program.
In a similar manner, we might use logical “and” (conjunction) to write an expression that asks whether integer variable minutes falls in the range 0..59:
(minutes >= 0) && (minutes <= 59)
The symbol, &&, denotes “and.” A small execution trace of this expression would be
helpful; assume that minutes holds 64:
(minutes >= 0) && (minutes <= 59)
=> (64 >= 0) && (minutes <= 59)
=> true && (minutes <= 59)
=> true && (64 <= 59)
=> true && false => false
Since one of the arguments to && resulted in false, the test’s result is false.
Table 5 lists the commonly used relational operations and their calculation rules.
The semantics in the Table should be followed exactly as written; consider this example:
(2 != 1) || (x == 3.14)
=> true || (x == 3.14)
=> true
Since the first argument to the disjunction, ||, resulted in true, there is no need to
compute the result of the second argument, so the answer for the expression is true.
We may write expressions with multiple relational operations, e.g.,
(x == 2) || (x == 3) || (x == 5) || (x == 7)
243
6.3. RELATIONAL OPERATIONS
Figure 6.5: logical operations
Operator
E1 && E2
Semantics
conjunction (“and”):
true && true => true
true && false => false
false && E2 => false
disjunction (“or”):
E1 || E2
false || false => false
false || true => true
true || E2 => true
negation(“not”):
!E
!true => false
!false => true
asks whether integer x is a prime less than 10, and
(y >= 0) && (((y % 3) == 0) || (y < 3))
asks whether integer y is nonnegative and is either a multiple of 3 or has value 0, 1,
or 2. Also, a negation operator always inverts the result produced by its argument,
e.g.,
!(cents >= 0
&&
cents <= 99)
returns true exactly when cents’s value falls outside the range 0..99. For example,
say that cents is 12:
!(cents >= 0 && cents <= 99)
=> !(12 >= 0 && cents <= 99)
=> !(true && cents <= 99)
=> !(true && 12 <= 99)
=> !(true && true) => !true
=>
false
Relational operations can be used to compress the nesting structure of conditionals. Indeed, if we study the flowchart in Figure 3, we notice that the three tests are
244
Figure 6.6: change making with logical relational operations
import javax.swing.*;
/** MakeChangeAgain calculates change in coins for a dollars, cents amount.
*
Input: two numbers supplied at the command line:
*
a dollars amount, a nonnegative integer;
*
a cents amount, an integer between 0 and 99.
*
Output: the coins */
public class MakeChangeAgain3
{ public static void main(String[] args)
{ int dollars = new Integer(args[0]).intValue();
int cents = new Integer(args[1]).intValue();
if ( (dollars < 0) || (cents < 0) || (cents > 99) )
{ JOptionPane.showMessageDialog(null,
"MakeChangeAgain error: bad inputs: " + dollars + " " + cents);
}
else // dollars and cents are acceptable, so compute answer:
{ int money = (dollars * 100) + cents;
System.out.println("quarters = " + (money / 25));
money = money % 25;
System.out.println("dimes = " + (money / 10));
money = money % 10;
System.out.println("nickels = " + (money / 5));
money = money % 5;
System.out.println("pennies = " + money);
}
}
}
used to determine whether to generate an error dialog or to compute change. We
might be so bold as to combine all three tests into one with relational operations;
see Figure 6 for the result, where less precise error reporting is traded for a simpler
control structure.
Relational operations are evaluated after arithmetic operations and comparison
operations, and for this reason, it is acceptable to omit some of the parentheses in
the above example:
if ( dollars < 0
||
cents < 0
||
cents > 99 )
Exercises
1. Calculate the answers for each of the following expressions. Assume that int x
= 2 and double y = 3.5.
245
6.4. USES OF CONDITIONALS
(a) (x > 1) && ((2*x) <= y)
(b) !(x == 1)
(c) (x >= 0 && x <= 1) || (1 <= y)
(d) x > 0 && x < 10 && (y == 3)
2. Given this method,
public int minus(int arg1, int arg2)
{ int answer;
if ( arg1 < 0 || arg2 > arg1 )
{ answer = -1; }
else { answer = arg1 - arg2; }
return answer;
}
what results are returned by minus(3, 2)?
minus(4, -5)?
minus(2, 3)?
minus(-4, -5)?
3. For each of these methods, write a body that contains only one conditional
statement. (The conditional’s test uses relational operations.)
(a) /** isSmallPrime says whether is argument is a prime number less than 10
* @param i - the argument
* @return true, if the argument is as prime less than 10;
* return false, otherwise. */
public boolean isSmallPrime(int i)
(b) /** divide does division on its two arguments
* @param x - a nonnegative value
* @param y - a value not equal to zero
* @return (x / y), if the above conditions on
* return 0, otherwise. */
public double divide(double x, double y)
6.4
x
and
y
hold true;
Uses of Conditionals
Two main uses of conditionals are
• to equip a method to defend itself against nonsensical input data and actual
parameters
• to help a method take a plan of action based on the values of the data and
parameters.
246
Here is an example that employs both these uses.
We desire a method, twelveHourClock, which converts a time from a twentyfour hour clock into the corresponding value on a twelve-hour clock. (For example,
twelveHourClock(16,35) would return "4:35 p.m." as its result.) Such a method
must be intelligent enough to understand “a.m.” and “p.m.” as well as defend itself
against nonsensical arguments (e.g., twelveHourClock(-10,99)).
The algorithm for the method might go as follows:
1. If either of the values of the hour and minutes are nonsensical, then set the
answer to an error message. Otherwise, perform the remaining steps:
(a) Calculate the correct hour, taking into account that hours of value 13..23
are reduced by 12 and that the 0 hour is written as 12; append this to the
answer.
(b) Calculate the correct minute, remembering that a minute value of 0..9
should be written with a leading 0 (e.g., 2 is written 02); append this to
the answer.
(c) Calculate whether the time is a morning (a.m.) time or an afternoon (p.m.)
time; append this to the answer.
2. Return the answer.
Figure 7 shows the method based on this algorithm. The first conditional examines
the parameters for possible nonsensical values. (As stated in Table 1, || denotes “or.”)
If the test computes to true, an error message is constructed.
Otherwise, the the function assembles the twelve-hour time, symbol by symbol,
within the string variable, answer, as described by the algorithm we just studied. It is
essential that the brackets for each conditional’s arms be placed correctly, otherwise
the method will behave improperly—it helps to align the matching brackets and
indent the arms, as displayed in the Figure.
Figure 7 is fascinating because the nested conditional structures are essential to
computing the correct result. Study the Figure until you understand every detail.
We finish with a second use of conditionals—responding to input dialogs. Recall
that a Java input dialog presents the user with a text field for typing input, and two
6.4. USES OF CONDITIONALS
247
Figure 6.7: method that uses conditionals to convert time
/** twelveHourClock converts a 24-hour-clock time into a 12-hour-clock time.
* @param hour - the hour time, in the range 0..23
* @param minute - the minutes time, in the range 0..59
* @return the 12-hour-clock time, formatted as a string. */
public String twelveHourClock(int hour, int minute)
{ String answer = ""; // accumulates the formatted time, symbol by symbol
if ( hour < 0 || hour > 23 || minute < 0 || minute > 59 )
{ answer = "twelveHourClock error: " + hour + "." + minute; }
else { if ( hour >= 13 ) // hours 13..23 are reduced by 12
{ answer = answer + (hour - 12); }
else { if ( hour == 0 ) // hour 0 is written as 12
{ answer = answer + "12"; }
else { answer = answer + hour; }
}
answer = answer + ":";
if ( minute < 10 ) // display minutes 0..9 as 00..09
{ answer = answer + "0"; }
answer = answer + minute;
if ( hour < 12 ) // morning or afternoon?
{ answer = answer + " a.m."; }
else { answer = answer + " p.m."; }
}
return answer;
}
buttons:
When the user presses OK, the text entered into the text field is returned to the
application that constructed the dialog. But if the user presses Cancel, the text is
lost, and a special value, called null, is returned instead.
With the help of a conditional statement, we can check whether a user has pressed
OK or Cancel on the dialog:
248
String input = JOptionPane.showInputDialog
("Type some input and press a button:");
if ( input == null ) // did the user press CANCEL?
{ ... take appropriate action to cancel the transaction ... }
else { ... process the input ... }
Here is an example that shows this technique: We might insert the twelveHourClock
method into a class TimeConvertor and use it with a main method that uses input
dialogs. The algorithm for main goes like this:
1. Construct an input dialog that asks the user for the hours, an integer between
0 and 23.
2. If the user pressed Cancel, then quit. Otherwise:
3. Construct another dialog that asks for the minutes, an integer between 0 and
59.
4. If the user pressed Cancel, then use 0 for the minutes amount and continue.
5. Invoke twelveHourClock to convert the time and display it.
The example algorithm means to show us how we can take different actions based on
a press of the Cancel button. Here is how the method checks the button presses:
public static void main(String[] args)
{ TimeConvertor time = new TimeConvertor(); // holds the method in Figure 7
String input = JOptionPane.showInputDialog
("Type hours in range 0..23:");
if ( input == null ) // did the user press Cancel?
{ JOptionPane.showMessageDialog(null, "Request cancelled"); }
else { int hours = new Integer(input).intValue();
int minutes = 0;
input = JOptionPane.showInputDialog
("Type minutes in range 0..59:");
if ( input != null ) // did the user press OK?
{ minutes = new Integer(input).intValue(); }
String answer = time.twelveHourClock(hours, minutes);
JOptionPane.showMessageDialog(null, "Time is " + answer);
}
}
6.5. ALTERING CONTROL FLOW
249
Exercises
1. Test twelveHourClock with each of these times: 9,45; 23,59; 0,01; 50,50; -12,-12;
24,0.
2. Use twelveHourClock in an application that obtains the current time (using a
GregorianCalendar object), converts it into a string (using twelveHourClock),
and displays the string in the command window.
3. Write a method that meets this specification:
/** translateGrade converts a numerical score to a letter grade.
* @param score - a numerical score that must be in the range 0..100
* @return a letter grade based on this scale:
*
100..90 = "A"; 89..80 = "B"; 79..70 = "C"; 69..60 = "D"; 59..0 = "F" */
public String translateGrade(int score)
Use the method in an application that asks the user to type a numerical score
and prints the corresponding letter grade.
4. Improve the MakeChangeAgain application so that
(a) answers of zero coins are not displayed. For example, the change for 0
dollars and 7 cents should display only
nickels = 1
pennies = 2
(b) if only one of a kind of coin is needed to make change, then a singular (and
not plural) noun is used for the label, e.g., for 0 dollars and 46 cents, the
application prints
1 quarter
2 dimes
1 penny
6.5
Altering Control Flow
When we reason about conditional statements, we assume that there is a control flow
through the then-arm which reaches the end of the conditional and there is a control
flow through the else-arm which reaches the end of the conditional. But the Java
language allows a programmer to invalidate this assumption by altering the control
flow.
In the usual case, you should not alter the normal control flow of conditionals,
statement sequences, and method invocations—such alterations make it almost impossible for a programmer to calculate possible control flows and analyze a program’s
250
behavior. But there are rare cases, when a fatal error has occurred in the middle of a
computation, where terminating the flow of control is tolerable. We briefly consider
techniques for premature termination due to an error.
6.5.1
Exceptions
Even though it might be well written, a program can receive bad input data, for
example, a sequence of letters might be received as input when a number was required:
int i = new Integer(args[0]).intValue();
If the program’s user enters abc as the program argument, the program will halt with
this fatal error, and we say that an exception is thrown:
java.lang.NumberFormatException: abc
at java.lang.Integer.parseInt(Integer.java)
at java.lang.Integer.<init>(Integer.java)
at Test.main(Test.java:4)
The message notes that the exception was triggered at Line 4 and that the origin of
the error lays within the Integer object created in that line.
Throwing an exception alters the usual control flow—the program stops execution
at the program line where the exception arises, and control is “thrown” out of the
program, generating the error message and terminating the program.
Exceptions are thrown when potentially fatal errors arise, making it too dangerous
to continue with the expected flow of control. For example, integer i in the above
example has no value if "abc" is the input, and it is foolish to proceed with execution
in such a case. Here is another situation where a fatal error might arise:
/** convertHoursIntoSeconds converts an hours amount into seconds
* @param hours - a nonnegative int
* @return the quantity of seconds in hours */
public int convertHoursIntoSeconds(int hours)
{ int seconds = 0;
if ( hours < 0 )
{ JOptionPane.showMessageDialog(null,
"ConvertHours error: negative input " + hours);
// should control proceed?
}
else { seconds = hours * 60 * 60; }
return seconds;
}
Unfortunately, if this method is invoked with a negative actual parameter, it returns
an incorrect answer, meaning that the then-arm of the conditional has failed in its
goal of computing the conversion of hours into seconds. This is potentially disastrous,
6.5. ALTERING CONTROL FLOW
251
Figure 6.8: method with thrown exception
/** convertHoursIntoSeconds converts an hours amount into seconds
* @param hours - a nonnegative int
* @return the quantity of seconds in hours */
public int convertHoursIntoSeconds(int hours)
{ int seconds = 0;
if ( hours < 0 )
{ String error = "convertHoursIntoSeconds error: bad hours " + hours;
JOptionPane.showMessageDialog(null, error);
throw new RuntimeException(error);
}
else { seconds = hours * 60 * 60; }
return seconds;
}
because the client object that invoked the method will proceed with the wrong answer
and almost certainly generate more errors.
In such a situation, premature termination might be acceptable. A programmer
can force an exception to be thrown by inserting a statement of this form:
throw new RuntimeException(ERROR_MESSAGE);
where ERROR MESSAGE is a string. The statement terminates the control flow and
throws an exception, which prints on the display with the line number where the
exception was thrown and the ERROR MESSAGE. Figure 8 shows the method revised to
throw an exception when the actual parameter is erroneous.
This technique should be used sparingly, as a last resort, because its usual effect
is to terminate the application immediately.
It is possible for an application to prevent termination due to an exception by
using a control structure called an exception handler. Exception handlers are easily
overused, so we delay their study until Chapter 11.
6.5.2
System Exit
The java.lang package provides a method that, when invoked, immediately terminates an application and all the objects constructed by it. The invocation is
System.exit(0);
This statement does not generate an error message on the display, and like throwing
exceptions, should be used sparingly.
252
6.5.3
Premature Method Returns
A less drastic means of terminating a method in the middle of its execution is to
insert a return statement. As noted in the previous chapter, a return statement
causes the flow of control to immediately return to the position of method invocation;
any remaining statements within the invoked method are ignored. Here are two small
examples. The first is a method that returns rather than continue with a dubious
computation:
public void printSeconds(int hours)
{ if ( hours < 0 )
{ return; }
int seconds = hours * 60 * 60;
System.out.println(hours + " is " + seconds + " seconds");
}
Java allows the return statement to be used without a returned value when a method’s
header line’s return type is void.
Second, here is a method that immediately returns a useless answer when an error
arises:
public int convertToSeconds(int hours)
{ if ( hours < 0 )
{ return -1; }
int seconds = hours * 60 * 60;
return seconds;
}
This example shows that a method may possess multiple return statements.
Premature returns can be easily abused as shortcuts for reducing nested conditionals, and you are encouraged to use relational operators for this purpose instead.
6.6
The Switch Statement
Say that you have written a nested conditional that asks questions about the value
of an integer variable, i and alters variable j:
if ( i == 2 )
{ System.out.println("2"); }
else { if ( i == 5 )
{ System.out.println("5");
j = j + i;
}
else { if ( i == 7 )
{ System.out.println("7");
6.6. THE SWITCH STATEMENT
253
j = j - i;
}
else { System.out.println("none"); }
}
}
The nesting is annoying, because the nested conditional merely considers three possible values of i.
Java provides a terser alternative to the nested if-else statements, called a switch
statement. Here is the above example, rewritten with the switch:
switch ( i )
{ case 2: { System.out.println("2");
break;
}
case 5: { System.out.println("5");
j = j + i;
break;
}
case 7: { System.out.println("7");
j = j - i;
break;
}
default: { System.out.println("none"); }
}
The above switch statement behaves just like the nested conditional.
The syntax we use for the switch statement is
switch ( EXPRESSION )
{ case VALUE1 : { STATEMENTS1
break;
}
case VALUE2 : { STATEMENTS2
break;
}
...
case VALUEn : { STATEMENTSn
break;
}
default : { STATEMENTSn+1 }
}
where EXPRESSION must compute to a value that is an integer or a character. (Recall
that Java characters are saved in storage as integer.) Each of VALUE1, VALUE2, ...,
254
must be integer or character literals (e.g., 2 or ’a’), and no two cases can be labelled
by the same constant value.
The semantics of the above variant of switch goes as follows:
1. EXPRESSION is computed to its value, call it v.
2. If some VALUEk equals v, then the accompanying statements, STATEMENTSk, are
executed; if no VALUEk equals v, then statements STATEMENTSn+1 are executed.
A switch statement is often used to decode an input value that is a character.
Imagine that an application reads Celsius (’C’), Fahrenheit (’F’), Kelvin (’K’), and
Rankine (’R’) temperatures—it likely reads a character code, call it letter, that
denotes the temperature scale, and it is probably simplest to process the character
with a statement of the form,
switch ( letter )
{ case ’C’: { ...
break;
}
case ’F’: { ...
break;
}
case ’K’: { ...
break;
}
case ’R’: { ...
break;
}
default: { System.out.println("TempConverter error: bad code"); }
}
to process the possible temperature scales.
Unfortunately, Java’s switch statement has a primitive semantics that behaves
badly when a break statement is omitted—more than one case might be executed!
For this statement,
switch ( i )
{ case 2: { System.out.println("2");
}
case 5: { System.out.println("5");
j = j + i;
break;
}
default: { System.out.println("no"); }
}
6.7. MODEL AND CONTROLLER COMPONENTS
255
Figure 6.9: a simple model-view-controller architecture
CONTROLLER
INPUT VIEW
OUTPUT VIEW
MODEL
when i holds 2, the control flow executes the statement, System.out.println("2"),
and because of the missing break, control “falls into” the next case and executes
System.out.println("5") and j = j + i as well! The break that follows sends the
control to the end of the switch.
An alternative to the switch statement is the following reformatted nested conditional, where certain bracket pairs are carefully omitted and indentation is made to
look like that of the switch:
if (
{
else
{
i == 2 )
System.out.println("2"); }
if ( i == 5 )
System.out.println("5");
j = j + i;
}
else if ( i == 7 )
{ System.out.println("7");
j = j - i;
}
else { System.out.println("none"); }
The Java compiler will read the above nested conditional the same way as the one at
the beginning of the Section.
6.7
Model and Controller Components
Virtually all the applications constructed so far in this text used a simple architecture,
the one seen in Figure 1, Chapter 4, where a controller asks an input-view for data,
computes answers from the data, and sends those answers to an output-view. It is
time to work with a more sophisticated architecture, of the form presented in Figure
9.
The controller decides the control flow of the application, the view(s) deal exclusively with input and output transmission, and the new component, the model, does
the “modelling” of the problem and the “computing” of its answer. (In the Figure,
the arrows suggest that the the controller sends messages to the input-view, asking
for data; the controller then instructs the model to compute upon the data; and the
256
controller next awakens the output-view and tells it to display the answers. Sometimes the model and output-view communicate directly—this is why the Figure also
contains an arc between output-view and model. Also, it is common for the input and
output views to be combined into one component, which we do ourselves in Chapter
10.)
We call the structure in Figure 3 a model-view-controller architecture.
(Footnote: The architecture in Figure 3 is a simplication of the model-viewcontroller architecture used in Smalltalk applications; the differences are discussed
in Chapter 10. End Footnote.)
Why should we bother to build a program in so many pieces? There are three
main answers:
1. The classes that generate the objects can be reused—one example is class
MyWriter from Figure 6, Chapter 5, which is an output view that can be reused
by many applications.
2. The pieces organize an application into manageably sized parts with specific
duties. It is a disaster if we build a complex application within one or two classes,
because the classes become too complicated to understand. If we organize a
program into its input-output parts, a model part, and a controller part, then
we have a standardized architecture whose parts can be written and tested
one at a time. Also, the complete program can be seen as an assembly of
standardized parts into a standard architecture.
3. The architecture isolates the program’s components so that alterations to one
part do not necessitate rewriting the other parts. In particular, one component
can be removed (“unplugged”) from the architecture and replaced by another,
which is “plugged” into the former’s place.
In the ideal situation, the building of an application should be a matter of
• selecting already written classes, perhaps extending them slightly, to generate
the input-output- iews
• selecting an already written class and perhaps extending it to generate the
model
• writing a new class to generate the controller that connects together the parts
of the application.
This approach requires that one maintain a “library” of classes for application building. The Java packages, java.lang, java.util, javax.swing, etc., are meant to be a
starter library—and one that you should start studying—but you should start collecting your own classes as well. And the notion mentioned above of “extending” an
6.7. MODEL AND CONTROLLER COMPONENTS
257
existing class refers to inheritance, which we employed in previous chapters to build
a variety of graphics windows.
As noted earlier, a model has the responsibility for “modelling” and “computing”
the problem to be solved. This modelling is done with the methods and the field
variables declared within the model class. (We call the field variables the model’s
attributes or its internal state.)
For example, a bank-account manager program uses a model that models an account ledger—the model has field variables to remember the account’s balance, and
the model owns methods for depositing and withdrawing funds, computing interest,
and displaying the balance.
A model for a chess game uses fields to model the
playing board and pieces, and the model possesses methods that move pieces and
compute interactions of pieces (e.g., “captures”). And, a model of a weather prediction program models planet Earth by using field variables for land masses, air and
water currents, and by using methods that compute the interaction of the fields.
A model is somewhat like the “chip” or “board” from a hand-held calculator (or
other electronic appliance), because the model contains internal state, it contains
operations that use the state, but it does not contain the calculator’s input buttons
or display panel (the “views”) or the calculator’s wiring and switches that instruct
the chip what to do (the “controller”).
In contrast, an application’s controller takes responsibility for controlling the order
in which the computation proceeds. The controller uses the input data to determine
the appropriate control flow and to invoke the appropriate methods of the model to
produce the output to be displayed. We use the techniques learned in the first part
of the Chapter to write the controllers that appear in the sections to follow.
6.7.1
Designing an Application with a Model-View-Controller
Architecture
When someone asks us to design and build an application, we must gather information about what the application must do so that we can design the application’s
architecture appropriately and build components that make the application come to
life.
To design and build an application, we should follow these five steps:
1. Collect use-case behaviors:
First, we ask the program’s user what the program is supposed to do; specifically, we ask the user how the program should behave in all of the cases when
the user pushes a button, types some input data, and so on. (For example, if
we are programming a text-editing program, we would ask the user to describe
all the desired behaviors of the program — entering text, pressing the “Save
File” button, pressing the “Undo” button, and so on. For each behavior, we
would list the program’s response.)
258
Each activity is called a use-case behavior, or use case, for short. The collection
of use cases helps us design the program’s user interface (the view) so that all
the desired buttons, text fields, dialogs, and displays are present. The use cases
also help us design the program’s model so that the model’s class has methods
to do all the desired computations.
2. Select a software architecture that can realize the use-case behaviors:
If the application has a rich collection of use cases (e.g., a text-editing program),
then a model-view-controller architecture is an appropriate choice; if there are
only a few use cases, showing that the program’s of computations are limited to
just one or two forms (e.g., a Celsius-to-Fahrenheit convertor), then a simpler
architecture, say, one that uses only a controller and a view, might be used.
Extremely simple use cases warrant a one-component architecture.
3. For each of the architecture’s components, specify classes:
The case study in Chapter 5 showed us how to write a specification for a class.
We use this specification technique to design the classes for an application’s
model, whose methods are responsible for doing computations. Similarly, we
design one or more classes for the view, which displays the results. (See again
the case study in Chapter 5, which specified an output-view class.) Finally, we
specify the controller component’s main method and describe how the controller
activates the methods in model and the view.
4. Write and test the individual classes:
From each specification of each class, we write the Java coding of the class’s
attributes and methods. We test the classes one at a time; we can do this
by writing small main methods that invoke the methods within the class. (The
section, “Testing Methods and Classes,” presented later in this chapter, presents
the details.) Or, if when using an IDE, we can type method invocations to the
IDE, which sends them to the class and lets us see the results.
5. Integrate the classes into the architecture and test the system:
Finally, we assemble the classes into a complete program and test the program
to see if it performs the actions specified in the original set of use-case behaviors.
Beginning programmers often jump directly from collecting use-case behaviors to
writing and testing an entire application. But this short-cut makes it more difficult
to detect the source of program errors and much more time consuming to repair them.
We now consider a case study that employs the methodology.
6.8. CASE STUDY: BANK ACCOUNTS MANAGER
6.8
259
Case Study: Bank Accounts Manager
A bank-accounts manager is a program that helps a user track her bank account balance — the user enters the deposits and withdrawals, and the program maintains and
displays a running balance. Say that we must design and build such an application,
following the methodology just described. To better understand what the program
must do, we begin by collecting use-case behaviors.
6.8.1
Collecting Use-Case Behaviors
After some discussions with the potential users of the bank-account manager, perhaps
we determine that the program’s input is a sequence of commands, each of which must
be one of the following forms:
• a request to deposit, accompanied by a dollars, cents numerical amount
• a request to withdraw, accompanited by a dollars, cents amount
• a request to quit the program
Here is a sample use case: a user submits a deposit of $50.50 into an input dialog
as follows:
(The d denotes “deposit,” of course.) The program responds to the deposit command
by displaying the transaction and the current balance in the output view. The deposit
260
of $50.50 would display
A second use case might illustrate a withdrawal of $30.33. Again, a command, say,
D 30.33, would be typed into an input dialog, and the program’s output view would
show this:
Other use cases, illustrating both proper and improper behavior, should be considered:
• entering a quit command;
• attempting to withdraw more money that the account holds in its balance;
• attempting to deposit or withdraw a negative amount of money;
and so on. More and more use cases give us more insight into how the write the
application.
6.8. CASE STUDY: BANK ACCOUNTS MANAGER
6.8.2
261
Selecting a Software Architecture
The use-case behaviors make clear that the application must maintain a bank account,
and there must be methods for depositing and withdrawing from the account. This
strongly suggests that we include in the application’s architecture a model class that
holds the bank account and its methods.
The use cases also indicate that an output-view component must display the
window that shows the account’s balance. Finally, a controller is needed to generate
input dialogs, send messages to the model to update the account, and send messages
to the output view to display the results. This gives us the standard model-viewcontroller architecture, like the one displayed in Figure 9.
6.8.3
Specifying the Model
The model of the bank account must be specified by a class that remembers the the
bank account’s balance and owns methods for depositing and withdrawing money.
The account’s balance will be remembered by a field variable — an attribute.
Table 10 presents the specification for class BankAccount, the model. We see the
attribute and the two methods. Note also that
• Because the class’s constructor method requires an argument, we list the constructor as part of the interface specification.
• The attribute is listed as a private variable.
• Because methods deposit and withdraw return results that are booleans, we list
the data type of the result after the name of the method. (This is the standard
convention, but when we code the method in Java, we place the data type of
the result before the name of the method.)
The model’s attribute, balance, remembers the account’s balance. The attribute is an
int variable, holding the balance, expressed in cents, in the account (e.g., 10 dollars
is saved as 1000 cents); this eliminate troubles with fractions that arise when one
maintains money with a double value.
Since balance is a private variable, it cannot be used directly by the client objects
that use BankAccounts. Indeed, methods deposit and withdraw must be used to
alter balance’s value. Methods that alter the values of attributes are called mutator
methods. Again, the specifications of deposit and withdraw are suffixed by a colon
and boolean, which asserts that the methods return a boolean-typed result.
6.8.4
Writing and Testing the Model
The algorithms for each of the model’s methods are straightforward. For example,
the one for deposit(int amount): boolean goes
262
Figure 6.10: specification of class BankAccount
BankAccount models a single bank
account
Constructor method:
BankAccount(int
initial balance)
Attribute
private int balance
Methods
deposit(int amount):
boolean
withdraw(int amount):
boolean
initializes the bank account with initial balance
the account’s balance, in cents; should always be a
nonnegative value
add amount, a nonnegative integer, to the account’s
balance. If successful, return true, if unsuccessful,
leave balance alone and return false
attempt to withdraw amount, a nonnegative integer, from the balance. If successful, return true, if
unsuccessful, leave balance alone and return false
1. If amount is a nonnegative, then add amount to the account’s balance.
2. Otherwise, issue an error message and ignore the deposit.
3. Return the outcome.
The class based on the specification appears in Figure 11.
Because attribute balance is labelled private, deposits and withdrawals are handled by methods deposit and withdraw—this prevents another malicious object from
“stealing money” from the account! The comment attached to balance states the
representation invariant that the field’s value should always be nonnegative; the representation invariant is a “pledge” that all methods (especially the constructor method
and withdraw) must preserve.
In addition to the methods listed in Table 10, the class contains an extra method,
getBalance, which returns the account’s balance. When an attribute, like balance,
is listed in a specfication, it is a hint that a public method, like getBalance, will be
included in the class to report the attribute’s current value. A method that does
nothing more than report the value of an attribute is called an accessor method.
The model class has the behaviors needed to maintain a bank account, but it does
not know the order in which deposits and writhdrawals will be made—determining
this is the responsibility of the controller part of the application.
Now that the model is written, we should test it. As hinted in the previous
paragraph, we test the model by constructing an object from it and sending a sequence
263
6.8. CASE STUDY: BANK ACCOUNTS MANAGER
Figure 6.11: model class for bank account
import javax.swing.*;
/** BankAccount manages a single bank account */
public class BankAccount
{ private int balance; // the account’s balance
// representation invariant:
balance >= 0
always!
/** Constructor BankAccount initializes the account
* @param initial amount - the starting account balance, a nonnegative. */
public BankAccount(int initial amount)
{ if ( initial amount >= 0 )
{ balance = initial amount; }
else { balance = 0; }
}
/** deposit adds money to the account.
* @param amount - the amount of money to be added, a nonnegative int
* @return true, if the deposit was successful; false, otherwise */
public boolean deposit(int amount)
{ boolean result = false;
if ( amount >= 0 )
{ balance = balance + amount;
result = true;
}
else { JOptionPane.showMessageDialog(null,
"BankAcccount error: bad deposit amount---ignored");
}
return result;
}
...
264
Figure 6.11: model class for bank account (concl.)
/* withdraw removes money from the account, if it is possible.
* @param amount - the amount of money to be withdrawn, a nonnegative int
* @return true, if the withdrawal was successful; false, otherwise */
public boolean withdraw(int amount)
{ boolean result = false;
if ( amount < 0 )
{ JOptionPane.showMessageDialog(null,
"BankAcccount error: bad withdrawal amount---ignored"); }
else if ( amount > balance )
{ JOptionPane.showMessageDialog(null,
"BankAcccount error: withdrawal amount exceeds balance");
}
else { balance = balance - amount;
result = true;
}
return result;
}
/* getBalance reports the current account balance
* @return the balance */
public int getBalance()
{ return balance; }
}
of method invocations to the object. The method invocations should mimick to some
degree the possible use cases that motivated the application’s design.
One simple, direct way to test the model is to write a main method that includes
a sequence of method invocations. Here is one sample:
public static void main(String[] args)
{ BankAccount tester = new BankAccount(0); // construct a test object
// now, try the getBalance method:
System.out.println("Initial balance = " + tester.getBalance());
// try a deposit:
boolean test1 = tester.deposit(5050); // we deposit 5050 cents
System.out.println("Deposit is " + test1 + ": " + tester.getBalance());
// try a withdrawal:
boolean test2 = tester.withdraw(3033);
System.out.println("Withdrawal is " + test2 + ": " + tester.getBalance());
6.8. CASE STUDY: BANK ACCOUNTS MANAGER
265
// try a mistake:
boolean test3 = tester.withdraw(6000);
System.out.println("Withdrawal is " + test3 + ": " + tester.getBalance());
// continue in this fashion....
}
The main method can be inserted into class BankAccount and started from that class,
or it can be placed into a small controller class of its own.
As noted earlier, your IDE probably lets you construct the BankAccount object
directly by selecting a menu item, and you can likely test the new object’s methods
by selecting another IDE menu item and entering the method invocations one at a
time, e.g.,
tester.getBalance();
and then
test.deposit(5050);
and so on.
6.8.5
Specifying the View Components
Table 12 lists the interface specifications for the input and output components. We
use an output view to build the window that displays the account’s balance, and
for the first time, we write an input-view class whose responsibility is to disassemble
input commands, like
D
50.50
into their parts: a text string, "D", and an integer, 5050. Because BankWriter’s
constructor method takes no arguments, we omit it from the table.
Within BankWriter are two methods, both named showTransaction—we say that
the method name is overloaded. Notice that the header lines of the two same-named
methods have different forms of formal parameters: The first of the two methods
is invoked when a showTransaction message with a string parameter and an integer parameter is sent to the BankWriter object; the second method is used when a
showTransaction message is sent with just a string parameter.
6.8.6
A Second Look at the Software Architecture
Once the specifications of the various components are written, it is standard to insert
the class, method, and attribute names from the specifications into the architectural
diagram. Figure 13 shows this for the bank-account manager. This picture is called
266
Figure 6.12: specification for view components
BankReader reads bank transactions from the user
Methods:
readCommand(String message):
char
readAmount():
int
BankWriter writes transactions
for a bank account
Constructor method:
BankWriter(String title,
BankAccount b)
Methods:
showTransaction(String
message, int amount)
showTransaction(String
message)
reads a new command line, prompting the user with
message. It returns the first character of the user’s
response.
returns the integer value included in the most recently read input line
initializes the writer, where title is inserted in the
window’s title bar, and b is the bank account whose
transactions is displayed
displays the result of a monetary bank transation,
where message lists the transaction and amount is
the numerical amount
displays the result of a monetary bank transation,
where message lists the transaction.
Figure 6.13: bank-account manager
AccountManager
main(...)
BankReader
readCommand(): char
readAmount(): int
AccountController
processTransactions()
BankWriter
showTransaction(String message,
int amount)
showTransaction(String message)
BankAccount
private int balance
deposit(int amount): boolean
withdraw(int amount): boolean
getBalance(): int
6.8. CASE STUDY: BANK ACCOUNTS MANAGER
267
a class diagram, because it diagrams the classes and how they are connected.
The model and view classes have already been developed; the AccountController
uses a processTransactions method to control the flow of execution of the methods in the view and model components. Finally, we use a separate start-up class,
AccountManager, to construct and connect the application’s components.
Figure 13 displays a rich collaboration between the application’s components.
A collaboration between components is sometimes called a coupling; for example,
the BankWriter is “coupled” to the BankAccount, which means the former cannot
operate without the latter. Also, changes to BankAccount might affect the operation
of BankWriter.
It is best not to have one component to which many others are coupled—if such a
component’s methods are changed, then many other components must be changed as
well. In the Figure, we see that the model, BankAccount, is the most sensitive component in this regard. Also, since the controller is coupled to the most components, it
is the most likely to be rewritten if there are changes to any part of the application.
An ideal software architecture will minimize the coupling between components so
that changes in one component do not trigger revisions of many other components.
6.8.7
Writing the View Classes
As stated by its specification, the input-view will read a line of input and disassemble
it into a command letter and a numerical amount. To do this disassembly, we can
use some of the methods on strings that were first presented in Table 5, Chapter 3.
We review the methods that will prove useful to us now:
• For a string, S, S.trim() examines S and returns a result string that looks like
S but with leading and trailing blanks removed. (Note: S is unchanged by this
method—a new string is constructed and returned.) A standard use of trim
goes like this:
String input = " some data
input = input.trim();
";
This in effect removes the leading and trailing blanks from input, leaving the
variable with the value "some data". The trim method is useful because users
often carelessly add leading and trailing spaces to the input text they type, and
the spaces sometimes interfere when checking for equality of strings, e.g,
if ( input.equals("some data") )
{ ... }
• A second issue with text input is the case in which letters are typed. For
example, if a user is asked to type F or C as an input letter, the user is likely to
268
type a lower case f or c instead. For this reason, the toUpperCase method can
be used to convert all input letters to upper case, e.g.,
input = input.toUpperCase();
assigns to input the string it formerly held, but converted to all upper-case
letters. (Numerals, punctuation, and other symbols are left unchanged.)
There is also the method, toLowerCase, which operates similarly: S.toLowerCase()
returns a string that looks like S except that all letters are translated to lower
case.
• Often, a specific character (say, the leading one) must be extracted from an
input string; use the charAt method to do this. For example,
char letter = input.charAt(0);
extracts the leading character from the string, input. (Recall that the characters
in a string are indexed 0, 1, 2, etc.) As noted in Chapter 3, characters can be
saved in variables declared with data type char, such as the variable letter
seen above.
Recall from Chapter 3 that a literal character is written with single quotes
surrounding it, e.g., ’a’, or ’F’, or ’4’. Characters can be compared with the
comparison operations on integers, e.g., ’a’ == ’A’ computes to false.
The character in variable, letter, can be examined to see if it is, say, ’F’, by
the if-statement,
if ( letter == ’F’ )
{ ... }
• Finally, a segment (substring) can be extracted as a new string by means of the
substring method. For example, S.substring(start, end) examines string
S and constructs a new string that consists of exactly the characters from S
starting at index start and extending to index end - 1.
For example,
String first_two = input.substring(0, 3);
assigns to first two a string consisting of the first two characters from string
input. Another example is
String remainder = input.substring(1, input.length());
6.8. CASE STUDY: BANK ACCOUNTS MANAGER
269
Figure 6.14: input-view class for bank account manager
import javax.swing.*;
/** BankReader reads bank transactions from the user */
public class BankReader
{ private String input line; // holds the most recent input command line
/** Constructor BankReader initializes the input reader */
public BankReader()
{ input line = ""; }
/** readCommand reads a new command line
* @param message - the prompt to the user
* @return the first character of the command */
public char readCommand(String message)
{ // read the input line, trim blanks and convert to upper case:
input line = JOptionPane.showInputDialog(message).trim().toUpperCase();
return input line.charAt(0); // return the leading character
}
/** readAmount returns the numerical value included in the input command line
* @return the amount, converted entirely into cents; if there is
*
no amount to return, announce an error and return 0. */
public int readAmount()
{ int answer = 0;
// extract substring of input line that forgets the initial character:
String s = input line.substring(1, input line.length());
s = s.trim(); // trim leading blanks from substring
if ( s.length() > 0 ) // is there a number to return?
{ double dollars cents = new Double(s).doubleValue();
answer = (int)(dollars cents * 100); // convert to all cents
}
else { JOptionPane.showMessageDialog(null,
"BankReader error: no number for transaction---zero used");
}
return answer;
}
}
270
which assigns to remainder a string that looks like input less its leading character. (Recall that length() returns the number of characters in a string.)
Table 5 from Chapter 3 lists additional methods for string computation.
Figure 14 displays the input-view class matching the specification.
The BankReader uses readCommand to receive a new input command and return its
character code (e.g., "D" for “deposit”); a second method, readAmount, extracts the
numerical amount embedded in the command. Of course, the initial character code
is not part of the numerical amount, so the statement,
String amount = input_line.substring(1, input_line.length());
uses the substring method to extract from input line that subpart of it that starts
at character 1 and extends to the end of the string. The statement,
answer = (int)(dollars_cents * 100);
makes the fractional number, dollars cents, truncate to an integer cents amount by
using the cast, (int), into an integer. Finally, if the input line contains no number
to return, the method returns zero as its answer, since bank transactions involving
zero amounts are harmless.
Next, Figure 15 presents the output-view class. The constructor for class BankWriter
gets the address of the BankAccount object for which the BankWriter displays information. (Remember from Chapter 5 that the address of an object can be a parameter to a method.) The BankAccount’s address is used in the paint method, at
bank.getBalance(), to fetch the account’s current balance.
A private method, unconvert, reformats the bank account’s integer value into
dollars-and-cents format. The method uses the helper object, new DecimalFormat("0.00"),
where the string, "0.00", states the amount should be formatted as a dollars-cents
amount.
Also within BankWriter are the two methods both named showTransaction. The
first of the two methods is invoked when a showTransaction message with a string
parameter and an integer parameter is sent to the BankWriter object; the second
method is used when a showTransaction message is sent with just a string parameter.
Overloading a method name is a cute “trick” and is sometimes used to great
advantage (indeed, the method name, println, of System.out is overloaded), but it
can be confusing as well. We take a closer look at overloading in Chapter 9.
We can test the two new classes in the same way that we tested the model, class
BankAccount: By using an IDE or writing a main method, we construct example inputand output-view objects from the two classes, and we send messages that invoke all
the objects’ methods with proper and improper arguments.
6.8. CASE STUDY: BANK ACCOUNTS MANAGER
Figure 6.15: output-view class for bank-account manager
import java.awt.*;
import javax.swing.*;
import java.text.*;
/** BankWriter writes bank transactions */
public class BankWriter extends JPanel
{ private int WIDTH = 300; // width and depth of displayed window
private int DEPTH = 200;
private BankAccount bank; // the address of the bank account displayed
private String last transaction = ""; // the transaction that is displayed
/** Constructor BankAccount initializes the writer
* @param title - the title bar’s text
* @param b - the (address of) the bank account displayed by the Writer */
public BankWriter(String title, BankAccount b)
{ bank = b;
JFrame my frame = new JFrame();
my frame.getContentPane().add(this);
my frame.setTitle(title);
my frame.setSize(WIDTH, DEPTH);
my frame.setVisible(true);
}
public void paintComponent(Graphics g)
{ g.setColor(Color.white);
g.fillRect(0, 0, WIDTH, DEPTH); // paint the background
g.setColor(Color.black);
int text margin = 50;
int text baseline = 50;
g.drawString(last transaction, text margin, text baseline);
g.drawString("Current balance = $" + unconvert(bank.getBalance()),
text margin, text baseline + 20);
}
/** unconvert reformats a cents amount into a dollars,cents string */
private String unconvert(int i)
{ double dollars cents = i / 100.00;
return new DecimalFormat("0.00").format(dollars cents);
}
...
271
272
Figure 6.15: output-view class for bank-account manager (concl.)
/** showTransaction displays the result of a monetary bank transation
* @param message - the transaction
* @param amount - the amount of the transaction */
public void showTransaction(String message, int amount)
{ last transaction = message + " " + unconvert(amount);
this.repaint();
}
/** showTransaction displays the result of a bank transation
* @param message - the transaction */
public void showTransaction(String message)
{ last transaction = message;
this.repaint();
}
}
6.8.8
Controller Construction and Testing
The last piece of the puzzle is the controller, AccountController, which fetches the
user’s transactions, dispatches them to the model for computation, and directs the
results to the output view. The controller’s algorithm for processing one transaction
goes as follows:
1. Read the command.
2. If the command is ’Q’, then quit processing.
3. Otherwise:
(a) If the command is ’D’, then read the amount of the deposit, make the
deposit, and display the transaction.
(b) Else, if the command is ’W’, then read the amount of the withdrawal, make
the withdrawal, and display the transaction.
(c) Else, the command is illegal, so complain.
Nested conditionals will be used to implement this algorithm, which appears in Figure
16. The Figure displays several noteworthy programming techniques:
• A separate, “start up” class, AccountManager, creates the application’s objects,
connects them together, and starts the controller. Notice how the model object
6.8. CASE STUDY: BANK ACCOUNTS MANAGER
273
is created in the main method, before the output-view, so that the latter can be
given the address of the former:
account = new BankAccount(0);
writer = new BankWriter("BankWriter", account);
• Within the processTransactions method, this style of nested conditional is
used to process the D and W commands:
if (
{
else
{
else
{
else
TEST1 )
S1 }
if ( TEST2 )
S2 }
if ( TEST3 )
S3 }
...
The brackets around the else-arms are absent; this is allowed when there is only
one statement in a conditional statement’s else-arm (in this case, it is another
conditional). The above format is an abbreviation for this heavily indented and
bracketed construction:
if ( TEST1 )
{ S1 }
else { if ( TEST2 )
{ S2 }
else { if ( TEST3 )
{ S3 }
else ...
}
}
As a rule, omitting an else-arm’s brackets is dangerous, and you should do so
only when writing a sequence of nested ifs in the above format.
• When a deposit or withdrawal command is processed, the writer object is sent a
showTransaction message that has two parameters; when an illegal command is
received, writer is sent a showTransaction message that has just one parameter.
Because showTransaction is an overloaded method name in writer, the two
forms of messages go to distinct methods. (Review Figure 15.)
• After processTransactions processes a command, it sends a message to itself to
restart itself and process yet another command. When a method restarts itself,
it is called a recursive invocation. Recursive invocation is the simplest way to
make a method repeat an activity, but in the next chapter we study more direct
techniques for repetition.
274
• processTransactions’s recursive invocations continue until a Q command is received, at which time the method does nothing, in effect halting the invocations
of the method.
Testing the controller must be done a bit differently that testing the other components, because the controller relies on other classes to do much of the work. When
we examine Figure 5, we see that the controller depends on BankReader, BankWriter,
and BankAccount. How do we test the controller? There are two approaches:
1. Assuming that we do not have the finished forms of the other classes, we write
“dummy classes,” sometimes called stubs, to stand for the missing classes. For
example, we might use this dummy class to help us test class AccountController:
public class BankAccount
{ // there are no attributes
public BankAccount(int initial_amount) { }
// does nothing
public boolean deposit(int amount)
{ System.out.println("deposit of " + amount);
return true; // the method must return some form of result
}
public boolean withdraw(int amount)
{ System.out.println("withdrawal of " + amount);
return true;
}
public int getBalance()
{ return 0; }
}
The dummy class contains only enough instructions so that the Java compiler
can process it and the controller can construct a dummy object and send messages to it. A typical dummy class is little more that the class’s specification
plus a few println and return statements. This makes it easy to write dummy
classes quickly and do tests on just of the controller.
2. You can complete one (or more) of the components used by the controller and
connect it to the controller for testing the latter. In effect, you are starting the
final development stage, testing the completed application, while you test the
controller.
This approach can make controller testing more demanding, because errors
might be due to problems in the controller’s coding or due to problems in
6.8. CASE STUDY: BANK ACCOUNTS MANAGER
275
Figure 6.16: controller for bank account manager
/** AccountController maintains the balance on a bank account. */
public class AccountController
{ // fields that remember the view and model objects:
private BankReader reader; // input-view
private BankWriter writer; // output-view
private BankAccount account; // model
/** Constructor AccountController builds the controller
* @param r - the input-view object
* @param w - the output-view object
* @param a - the model object */
public AccountController(BankReader r, BankWriter w, BankAccount a)
{ reader = r;
account = a;
writer = w;
}
/** processTransactions processes user commands until a Q is entered */
public void processTransactions()
{ char command = reader.readCommand("Command (D,W,Q) and amount:");
if ( command == ’Q’ ) // quit?
{ }
// terminate method by doing nothing more
else { if ( command == ’D’ ) // deposit?
{ int amount = reader.readAmount();
boolean ok = account.deposit(amount);
if ( ok )
{ writer.showTransaction("Deposit of $", amount); }
else { writer.showTransaction("Deposit invalid ", amount); }
}
else if ( command == ’W’ ) // withdraw?
{ int amount = reader.readAmount();
boolean ok = account.withdraw(amount);
if ( ok )
{ writer.showTransaction("Withdrawal of $", amount); }
else { writer.showTransaction("Withdrawal invalid ", amount); }
}
else { writer.showTransaction("Illegal command: " + command); }
this.processTransactions();
// send message to self to repeat
}
}
}
276
Figure 6.16: controller for bank account manager (concl.)
/** AccountManager starts the application that maintains a bank account. */
* Inputs: a series of commands of the forms,
*
D dd.cc (deposit),
*
W dd.cc (withdraw), or
*
Q
(quit), where dd.cc is a dollars-cents amount
* Outputs: a display of the results of the transactions */
public class AccountManager
{ public static void main(String[] args)
{ // create the application’s objects:
BankReader reader = new BankReader();
BankAccount account = new BankAccount(0);
BankWriter writer = new BankWriter("BankWriter", account);
AccountController controller =
new AccountController(reader, writer, account);
// start the controller:
controller.processTransactions();
}
}
the interaction of the components. Often, a mixture of dummy classes and a
completed classes, added one at a time, can ease this difficulty.
6.8.9
Testing the Assembled Application
Once all the classes are specified, written, and thoroughly tested individually, it is
time to assemble them and perform integration testing. As noted in the previous
section, integration testing can be done incrementally, by starting with the controller
component and dummy classes and then replacing the dummy classes by finished
classes one at a time. A more bold approach is to combine all the finished classes and
go straight to work with testing.
As stated earlier, integration testing should attempt, at least, all the use-case
behaviors that motivated the design of the application.
Exercises
1. Here are some small exercises regarding testing:
(a) Write a tester-controller that creates a BankAccount object, deposits 1000,
withdraws 700, and asks for the balance. Print the balance in the command
window.
6.8. CASE STUDY: BANK ACCOUNTS MANAGER
277
(b) Write a tester-controller that creates a BankReader object and asks the
object to read a command. The controller copies the command code and
amount into the command window.
(c) Extend the test you did on the BankAccount to include a BankWriter object.
Use the BankWriter’s showTransaction method to display the results of the
deposit and the withdrawal.
(d) Finally, write a tester that uses a BankReader to read a deposit command,
places the deposit in a BankAccount, and tells a BankWriter to display the
result.
2. Add this method to class BankAccount:
/** depositInterest increases the account with an interest payment,
*
calculated as (interest_rate * current_balance)
* @param rate - the interest rate, a value between 0.0 and 1.0
* @return the amount deposited into the account; if the deposit cannot
* be performed, return 0 */
public int depositInterest(double rate)
and add the input command, I dd.d, which allows the user to increase the
account’s current balance by dd.d%.
3. The constructor for class BankAccount allows a new bank account to begin with
a nonnegative value. Modify the controller, class AccountManager, so that on
startup, it asks the user for an initial value to place in the newly created bank
account.
6.8.10
Multiple Objects from the Same Class
We finish the bank-account case study with a small but crucial modification: Since
many people have multiple bank accounts, we modify the account-manager application to manage two distinct bank accounts simultaneously—a “primary account” and
a “secondary account.”
The architecture in Figure 12 stays the same, but we annotate its arcs with numbers to make clear that the one controller now collaborates with two models. As a
consequence, each model is mated to its own output view, meaning there are two
output views in the complete program. See Figure 17.
In the Figure, read the numbering of the form
1
A
m
B
as stating, “each 1 A-object is coupled to (uses or sends messages to) m B-objects.”
(An unannotated arrow is a 1-1 coupling.)
278
Figure 6.17: program architecture with two bank accounts
AccountManager2
main(...)
AccountController2
processTransactions()
1
2
BankAccount
1
BankReader
2
BankWriter
To build this architecture, we reuse all the classes we have and modify the controller. The new controller, AccountManager2, will process the same input commands
as before plus these two new ones:
• P, a request to do transactions on the primary bank account
• S, a request to do transactions on the secondary bank account
Here is a sample execution of the application, where the primary account gets $50.50,
the secondary account gets $10.00, and the primary account loses $30.33:
Command
Command
Command
Command
Command
Command
Command
(P,S,D,W,Q):P
(P,S,D,W,Q):d 50.50
(P,S,D,W,Q):s
(P,S,D,W,Q):D10.00
(P,S,D,W,Q):p
(P,S,D,W,Q):W 30.33
(P,S,D,W,Q):q
6.8. CASE STUDY: BANK ACCOUNTS MANAGER
279
Just before the user quits, the output views look like this:
The modified program uses two models, called primary account and secondary account;
each model has its own output-view, primary writer and secondary writer, respectively.
It is crucial that the controller remember which of the two accounts is active and is
the target of the transactions. For this purpose, the controller uses the fields private
BankAccount account and private BankWriter writer to hold the addresses of the
account and its view that are active. The fields are initialized to the (addresses of
the) primary account objects.
Figure 18 presents the modified controller. Within processTransactions, conditional statements are used to detect the new commands, P and S, and adjust the
values in the account and writer fields accordingly.
When you start this application, you will see two graphics windows appear on the
display—one for the primary account and one for the secondary account. (Perhaps
the two windows will first appear on top of each other; use your mouse to move one
of them. Or, you can use the setLocation method—f.setLocation(x ,y) positions
the upper left corner of frame f at coordinates x, y on the display.)
When the program is started, it creates the following configuration in computer
280
Figure 6.18: controller for managing two bank accounts
/** AccountController2 maintains the balance on two bank accounts, a primary
* account and a secondary account.
* inputs: P (use primary account), S (use secondary account),
*
D dd.cc (deposit), W dd.cc (withdraw), Q (quit)
* outputs: the transactions displayed on two windows */
public class AccountController2
{ private BankReader reader; // input view
// the primary bank-account: model and its output-view:
private BankAccount primary account;
private BankWriter primary writer;
// the secondary bank-account: model and its output-view:
private BankAccount secondary account;
private BankWriter secondary writer;
// fields that remember which model and view are active for transactions:
private BankAccount account;
private BankWriter writer;
// invariant: these fields must belong to the primary or secondary account
/** AccountController2 initializes the bank accounts */
public AccountController2(BankReader r, BankAccount a1, BankWriter w1,
BankAccount a2, BankWriter w2)
{ reader = r;
primary account = a1;
primary writer = w1;
secondary account = a2;
secondary writer = w2;
// start processing with the primary account:
account = primary account;
writer = primary writer;
}
/** resetAccount changes the current account to new account and new writer */
private void resetAccount(BankAccount new account, BankWriter new writer)
{ writer.showTransaction("Inactive"); // deactivate current account
account = new account;
// reset the account to the new one
writer = new writer;
writer.showTransaction("Active");
}
...
6.8. CASE STUDY: BANK ACCOUNTS MANAGER
281
Figure 6.18: controller for managing two bank accounts (concl.)
public void processTransactions()
{ char command = reader.readCommand("Command (P,S, D,W,Q):");
if ( command == ’Q’ ) // quit?
{ }
else { if ( command == ’D’ ) // deposit?
{ int amount = reader.readAmount();
account.deposit(amount);
writer.showTransaction("Deposit of $", amount);
}
else if ( command == ’W’ ) // withdraw?
{ int amount = reader.readAmount();
boolean ok = account.withdraw(amount);
if ( ok )
{ writer.showTransaction("Withdrawal of $", amount); }
else { writer.showTransaction("Withdrawal invalid", amount); }
}
else if ( command == ’P’ ) // work with primary account?
{ resetAccount(primary account, primary writer); }
else if ( command == ’S’ ) // work with secondary account?
{ resetAccount(secondary account, secondary writer); }
else { writer.showTransaction("Illegal command"); }
this.processTransactions();
// send message to self to repeat
}
}
}
/** AccountManager2 maintains two bank accounts */
public class AccountManager2
{ public static void main(String[] args)
{ BankReader reader = new BankReader();
// create the models and their views:
BankAccount primary account = new BankAccount(0);
BankWriter primary writer
= new BankWriter("Primary Account", primary account);
BankAccount secondary account = new BankAccount(0);
BankWriter secondary writer
= new BankWriter("Secondary Account", secondary account);
// construct the controller and start it:
AccountController2 controller = new AccountController2(reader,
primary account, primary writer, secondary account, secondary writer);
controller. processTransactions();
}
}
282
storage:
a6 : AccountController2
AccountManager2
...
BankAccount primary account ==
BankWriter primary writer ==
a2
a3
a2 : BankAccount
BankAccount secondary account ==
int balance ==
BankWriter secondary writer ==
public void deposit(...){...}
public boolean withdraw(...){...}
public int getBalance(){...}
BankReader reader ==
a4 : BankAccount
BankWriter writer ==
0
public void deposit(...){...}
public boolean withdraw(...){...}
public int getBalance(){...}
a3 : BankWriter
BankAccount bank ==
...
a5
a1
a2
BankAccount account ==
int balance ==
a4
0
a3
main
{ ...
> processTransactions();
}
a5 : BankWriter
a2
BankAccount bank ==
a4
...
a1 : BankReader
...
Two BankAccount and BankWriter objects appear, and the fields account and writer
hold the addresses of the objects that the user manipulates with deposits and withdrawals. When the user selects the secondary account, the two fields receive the
values a5 and a6, respectively.
Class BankAccount has been used to generate multiple bank account objects, just
as we might use a blueprint to build several houses. In the chapters that follow, we
make good use of this principle to construct multiple objects that work in ensemble
to solve significant problems.
Exercise
Revise AccountManager2 so that it can transfer funds from the primary account to
the secondary account. The new command, >, followed by an amount, withdraws
the amount from the primary account and deposits it into the secondary account.
Next, add the command, < , which transfers money from the secondary account to
the primary one.
6.9. MORE ABOUT TESTING CLASSES AND METHODS
6.9
283
More about Testing Classes and Methods
Complex systems, like cars and televisions, are assembled from separate components
that are designed, manufactured, and tested separately. It is critical for us to design,
write, and test software components (classes) in a similar way. How do we test a
class?
6.9.1
Testing Individual Methods
As suggested by the earlier case study, to test a class, we must test its methods. When
testing an individual method, think of the method as a “little main method,” whose
input arguments arrive by means of actual parameters—we write lots of invocations
of the methods with lots of different actual parameters. Therefore, the techniques
we learned earlier for testing entire applications apply to testing individual methods:
(Reread the section, “Testing a Program that Uses Input,” in Chapter 4, for details.)
Test a method with both “white box” and “black box” techniques that ensure
that each statement in the method is executed in at least one test case.
In particular, when a method contains if-statements, be certain to formulate
enough tests so that every arm of every if-statement is executed by at least one test.
Although this strategy does not guarantee correctness of the entire method, it should
be obvious that an error inside the arm of a conditional will never be detected if the
arm is never executed! You might draw a flowchart of a method to help devise the
test cases that ensure each possible control flow is tested at least once.
6.9.2
Testing Methods and Attributes Together
Unfortunately, we cannot always test each method of a class independently—if a
class’s method references a field variable (an attribute), then the value in the attribute
affects how the method behaves. To complicate matters further, if the attribute is
shared by multiple methods in the class, then the manner in which all the methods
use the field becomes crucial to the correct behavior of each method.
We deal with this situation in two stages: The first is to consider how a single
method interacts with a class’s attrbutes; the second is to consider the order in which
a family of methods use the attributes.
We consider the first stage now: Given one single method, we must identify the
attributes used by the method. For each attribute, we list the range of values that
the method would find acceptable as the attribute’s value. Then, we initialize the
attribute with sample values within this range and do multiple invocations of the
method to observe the interactions between the method and the attribute. At the
same time, whenever the method changes the value of an attribute, we check that the
new value assigned to the attribute is acceptable.
284
For example, we might test the withdraw method of class BankAccount from Figure 11. The method uses the class’s balance attribute, so we consider the acceptable
range of values for that attrbute, and decide that balance should always hold a nonnegative integer. So, we might test the withdraw method by initializing balance to a
nonnegative as follows:
public class BankAccount
{ private int balance = 44;
// pick some value for initialization
// We are not yet testing the constructor method, so leave it empty:
public BankAccount() { }
public boolean withdraw(int amount)
{ boolean result = false;
if ( amount < 0 )
{ ... }
else ...
return result;
}
}
Now we are ready for a round of test invocations of withdraw with a variety of arguments. After each invocation, we check the value of balance and verify that withdraw
assigned an acceptable value.
As we systematically test a class’s methods one by one, we will reach a consensus
regarding the attribute’s range of values that is acceptable to all the class’s methods.
This helps us establish the representation invariant for each attribute and eases the
second stage of method testing.
6.9.3
Testing a Suite of Methods
Because methods typically share usage of a class’s attributes, the order in which a
class’s methods are invoked can affect the behavior of the methods themselves. For
example, it is potentially a problem if a new BankAccount object is constructed with an
initial balance of zero, and we invoke the withdraw method before doing any deposits!
A more subtle problem arises when several methods share an attribute, and one
method inserts an unacceptable value into the attribute; this negatively impacts the
other methods, which assume that the attribute always holds acceptable values.
We might tackle this situation by generating test cases that include all possible
sequences of method invocations, but this is daunting. A better solution is to rely on
the representation invariants that we formulated based on the testing of the individual
methods:
For each attribute, we list a representation invariant. For each method we validate
that, if the method starts execution when all attributes have values that are
6.9. MORE ABOUT TESTING CLASSES AND METHODS
285
acceptable to their representation invariants, then when the method completes,
all attributes hold values that are acceptable to their representation invariants.
Recall that a representation invariant is a property about a field that must always
be preserved, no matter what updates are made to the field. Such an invariant
typically states some “reasonable range of values” that a field may have. If all of
a class’s methods pledge to preserve the representation invariant, then each method
can be tested individually, where, as part of the testing, the representation invariant
is verified as preserved by the method.
For example, in class BankAccount the representation invariant for balance states
that balance’s value is always nonnegative; each method promises to preserve this
invariant. To test the deposit method, we first set balance to a nonnegative value,
so that the representation invariant is true. Then, we test deposit with an actual
parameter. After the test, we verify that deposit behaved properly and that balance
retains a nonnegative value. Similarly, the withdraw method and the class’s constructor method must be tested to verify that they preserve balance’s representation
invariant.
In this way, we alter our approach to testing a suite of methods—we do not worry
about the order in which the methods are invoked; instead, we validate that, regardless
of the order in which a class’s methods are invoked, all attributes’ values are safely
maintained to be acceptable to the representation invariants.
6.9.4
Execution Traces
When one method is invoked, it might invoke other methods. In this situation, you
might require extra assistance in understanding exactly what one method does in
terms of other methods; a good way of obtaining this understanding is by generating
a mechanical execution trace during a test invocation of a method.
In this text, we have seen handwritten execution traces from time to time. Indeed,
the best way to learn about execution patterns of an invoked method is to write
an execution trace by hand! But if you lack pencil and paper, it is possible to
make method generate its own execution trace by this simple trick: insert println
statements into its body and the bodies of the methods it invokes—have each method
print, at entry to its body, the values of its parameters and the field variables it uses.
When the method’s body finishes, it prints the values of fields and important local
variables.
Of course, if you are using an IDE you can use the IDE’s “debugger” to generate
execution traces, and you do not have to insert the println statements. Instead,
you tell the debugger to insert “breakpoint” lines at the statements where you wish
to see the values of parameters and fields. Then, you start the program, and the
debugger lets the program execute until a breakpoint line is encountered. At the
breakpoint line, the debugger pauses the program, and you can ask the debugger to
286
print variables’ values. Once you have seen all the values you desire, you tell the
debugger to resume execution.
Using the println technique, we can alter deposit to generate its own execution
trace as follows:
public void deposit(int amount)
{ System.out.println("deposit ENTRY: amount = " + amount
+ " balance = " + balance);
balance = balance + amount;
System.out.println("deposit EXIT: balance = " + balance);
}
The trace information proves especially useful to understanding the order in which
methods are invoked and the values the methods receive and return.
When one method invokes another, we can adapt the “dummy class” (“stub”)
technique so that we can test one method even if the methods invoked are not yet
coded—we write “dummy” methods for the methods not yet coded. (The dummy
methods must respect the representation invariants, of course.) For example, if we
have written deposit but not the other methods of BankAccount, we might build the
following class for testing:
public class BankAccount
{ private int balance; // representation invariant:
balance >= 0
public BankAccount(int initial_balance)
{ balance = 0; } // dummy --- this assignment makes the invariant true
public void deposit(int amount)
{ balance = balance + amount; }
public boolean withdraw(int amount)
{ return true; } // dummy
public int getBalance() { return 0; } // dummy
}
The bodies of the yet-to-be-coded methods, BankAccount, withdraw, and getBalance,
are represented by harmless, dummy methods that preserve balance’s representation
invariant.
6.10
Summary
Here are the main points from this chapter:
6.10. SUMMARY
287
New Constructions
• conditional statement (from Figure 1):
if ( hours >= 0 )
{ JOptionPane.showMessageDialog(null,
hours + " hours is " + seconds + " seconds");
}
else { JOptionPane.showMessageDialog(null,
"ConvertHours error: negative input " + hours);
}
• relational operator (from Figure 6):
if ( (dollars < 0) || (cents < 0) || (cents > 99) )
{ ... }
• thrown exception (from Figure 8):
if ( hours < 0 )
{ String error = "convertHoursIntoSeconds error: bad hours " + hours;
JOptionPane.showMessageDialog(null, error);
throw new RuntimeException(error);
}
• switch statement:
switch ( letter )
{ case ’C’: { ...
break;
}
case ’F’: { ...
break;
}
case ’K’: { ...
break;
}
case ’R’: { ...
break;
}
default: { System.out.println("TempConverter error: bad code"); }
}
288
• overloaded method name (from Figure 15):
/** showTransaction displays the result of a monetary bank transation
* @param message - the transaction
* @param amount - the amount of the transaction */
public void showTransaction(String message, int amount)
{ last_transaction = message + " " + unconvert(amount);
repaint();
}
/** showTransaction displays the result of a bank transation
* @param message - the transaction */
public void showTransaction(String message)
{ last_transaction = message;
repaint();
}
New Terminology
• control flow: the order in which a program’s statements execute
• control structure: a statement form that determines the control flow
• conditional statement (“if statement”): a control structure that asks a question
(test) and selects a flow of control based on the answer (e.g.,
if ( hour >= 13 )
{ answer = answer + (hour - 12); }
else { if ( hour == 0 ) ... }
as seen above; the test is hour >= 13). The possible control flows are the conditional’s then-arm (e.g., answer = answer + (hour - 12)) and the else-arm
(e.g., if ( hour == 0 ) ...). When the test results in true, the then-arm is
executed; when the test results in false, the else-arm is executed.
• nested conditional: a conditional statement placed within an arm of another
conditional statement (e.g., the previous example).
• relational operator: an operator that lets us write two comparisons in one expression (e.g., the conjunction operator, &&, in letter != ’F’ && letter != ’C’).
The result from computing the expression is again true or false. The standard
relational operators are conjunction (&&), disjunction (||), and negation (!).
• exception: an error that occurs during program execution (e.g., executing 12/0
throws a division-by-zero exception). Normally, an exception halts a program.
6.10. SUMMARY
289
• recursive invocation: an invocation where a method sends a message to restart
itself.
• model: a component in an application that “models” the programming problem
and computes its answer (e.g., class BankAccount is models a customer’s bank
account and is used as the central component of the bank-account-manager
application in Figure 12).
• attribute: a private field variable inside a model class. The field holds information about what the model’s “internal state.”
• model-view-controller architecture: a design of application divided into these
components: a model for modelling the problem and computing the answer; a
view for reading input and displaying output; and a controller for determining
control flow.
• coupling: a “collaboration” where one class depends on another class to do
its work; drawn as an arrow connecting the two classes (e.g., in Figure 12,
BankWriter is coupled to BankAccount).
• accessor method: a method that merely reports the value of an object’s fields
(attributes) but does not alter any fields (e.g., getBalance in Figure 11)
• mutator method: a method that alters the values of an object’s fields (e.g.,
deposit in Figure 11)
• overloaded method name: a method name that is used to name two different
methods in the same class (e.g., showTransaction in Figure 15). When a message
is sent containing an overloaded method name, the receiver object uses the
quantity and data types of the actual parameters to select the appropriate
method to execute.
Points to Remember
• A conditional statement is written so that each of its arms are dedicated to
accomplishing a goal; the conditional’s test expression provides information
that ones uses to validate that the arms achieve the goal: arms:
if ( TEST )
{ // here, assume TEST is true
STATEMENTS1 // use the assumption to reach some goal, G
}
else { // here, assume !TEST is true
STATEMENTS2 // use the assumption to reach some goal, G
}
// regardless of outcome of TEST, goal G is achieved
290
• In the case of a fatal error that arises in the middle of an execution, a programmer can alter the control flow with a thrown exception (normally, this
terminates execution with an error message), a system exit (this always terminates execution without an error message), or a premature return statement
(this forces the method to quit and immediately return to the client’s position
of invocation).
• The benefits for building an application in the model-view-controller architecture are
– The classes can be reused in other applications.
– The application is organized into standardized, recognized, manageably
sized parts that have specific duties.
– The parts are isolated so that alterations to one part do not force rewriting
of the other parts.
6.11
Programming Projects
1. Locate the current exchange rates for 3 different currencies (e.g., dollar, euro,
and yen). Write a currency converter tool that lets its user input an amount in
one currency and receive as output the equivalent amount in another currency
that the user also requests.
2. Consider yet again temperature conversions, and review the conversion formulas
between Celsius and Fahrenheit from the previous chapter.
Another temperature scale is Kelvin, which is defined in terms of Celsius as
follows:
K = C + 273.15
(For example, 10 degrees Celsius is 283.15 degrees Kelvin.)
Write a class TempCalculator with these methods:
6.11. PROGRAMMING PROJECTS
celsiusIntoFahrenheit(double
c): double
fahrenheitIntoCelsius(double
f): double
celsiusIntoKelvin(double c):
double
kelvinIntoCelsius(double k):
double
fahrenheitIntoKelvin(double
f): double
kelvinIntoFahrenheit(double
k): double
291
translate the Celsius temperature, c, into its
Fahrenheit value and return it as a double
translate the Fahrenheit temperature, f, into its
Celsius value and return it as a double
translate the Celsius temperature, c, into its
Kelvin value and return it as a double
translate the Kelvin temperature, k, into its
Celsius value and return it as a double
translate the Fahrenheit temperature, c, into its
Kelvin value and return it as a double
translate the Kelvin temperature, k, into its
Fahrenheit value and return it as a double
Write an application that asks the user for a temperature and its scale and asks
for a scale to which the temperature should be converted. The application does
the conversion and displays the result.
3. Rebuild the change-making program of Figure 3, Chapter 3, so that it uses a
model that fits this specification:
Constructor
Money(int initial amount)
Construct a new object such that it holds
initial amount of money.
Attribute
private int amount
the amount of money not yet converted into
change
Method
extract(int coins value):
int
extract the maximum quantity possible of the
coin with the value, coins value, from the
amount of money. Reduce the value of amount
accordingly, and return as the answer the number of coins that were extracted.
4. Write a model, class Mortgage, that models and monitors the money paid
towards a house mortgage. The class should match this specification:
292
Constructor
Mortgage(double p, double i,
int y)
Construct a new object based on the starting
principal, p, the loan’s interest rate, i, and the
number of years, y, needed to repay the loan.
(Note: interest, i, is expressed as a fraction,
e.g., 10% is 0.10.)
Attributes
private double
monthly payment
private double
remaining principal
private double total paid
the loan’s monthly payment, as calculated from
the starting principal, interest rate, and duration. (See the formula following this Table.)
how much is left to repay of the loan
the total amount of monthly payments paid so
far
Methods
makeMonthlyPayment()
make one monthly payment towards the loan,
increasing the total paid and reducing the
remaining principal. (See the formula following the Table.)
The following formulas will be helpful to you in writing the class:
• The formula for calculating the correct annual payment:
∗p∗i
payment = (1+i)
(1+i)y −1
monthlyP ayment = payment/12.0
y
for principal, p, interest rate, i, and years, y.
• The reduced principal due to a monthly payment:
newP rincipal = ((1 + (i/12))remaining principal) − payment
where i and payment are as above.
Once you complete the model, use it in an application that lets a user submit a
starting principal, interest rate, and loan duration. The application replies with
the monthly payment. Then, each time the user presses the button on a dialog,
the application makes a monthly payment and displays the remaining principal
and the total paid so far. The user continues to press the dialog’s button until
the loan is paid in full.
5. The distance travelled by an automobile, moving at initial velocity, V0 , at acceleration, a, for time, t, is: distance = V0 t + (1/2)a(t2 ) Use this formula to
6.11. PROGRAMMING PROJECTS
293
“model” a moving automobile: class Auto must have internal state that remembers (at least) the auto’s initial velocity and acceleration, and it must have
a method that returns the auto’s current position (distance).
Once you complete the model, use it in an application that lets a user submit a
starting velocity and acceleration. Then, each time the user presses the button
on a dialog, the application adds one unit to the time and displays the distance
travelled by the auto.
6. Write a model class that represents a die (that is, a cube whose sides are numbered 1 to 6). The class will likely have just one method, throw(), and no attributes. (Hint: use Math.random to write throw.) Then, write an output-view
class that displays a die after it has been thrown. Finally, write a controller
that lets a user throw two dice repeatedly.
7. Use the class from the previous exercise to build a game called “draw the house.”
The objective is to throw a die repeatedly, and based on the outcome, draw parts
of a house. A throw of 6 lets one draw the building, a square; a throw of 5 lets
one draw the roof; a throw of 4 lets one draw the door; and a throw of 3 lets one
draw a window. (There are two windows.) The finished house looks something
like this:
/\
/ \
----| _ |
|x| |x|
-----
Of course, the building must be drawn before the roof, and the roof must be
drawn before the doors and windows.
In addition to the class that represents the die, write a model class that represents the state of a partially built house. Then, write an output view that
displays the house, and write a controller that enforces the rules of the game.
8. Make a version of the game in the previous Project that lets two players compete
at building their respective houses.
9. Write an interface specfication for a model class that represents a vending machine. In exchange for money, the machine distributes coffee or tea. When you
design the specification, consider the machine’s attributes first: the quantities
of coffee, tea, and excess change the machine holds. (Don’t forget to remember
the amount of money a customer has inserted!) When you design the machine’s
methods, consider the machine’s responsibilities to give coffee, tea, and change,
294
to refund a customer’s coins when asked, and to refuse service if no coffee or
tea is left.
Two of the methods might be
insertMoney(int amount)
askForCoffee():
boolean
the customer inserts the amount of money into
the vending machine, and the machine remembers this.
the customer requests a cup of coffee. If the customer has already inserted an adequate amount
of money, and the machine still has coffee in it,
the machine produces a cup of coffee, which it
signifies by returning the answer, true. If the
machine is unable to produce a cup of coffee,
false is returned as the answer.
Based on your specification, write a model class.
Next, write an output-view of the vending machine and write an input-view
and controller that help a user type commands to “insert” money and “buy”
coffee and tea from the machine.
10. Write a model class that has methods for computing an employee’s weekly pay.
The methods should include
• pay calculation based on hours worked and hourly payrate,
• overtime pay calculation, based on overtime as 1.5 times the regular payrate
for hours worked over 40,
• deduction of payroll tax. Use this table:
– 20% of pay, for 0-30 hours of work
– 25% of pay, for 31-40 hours of work
– 28% of pay, for 41 or more hours of work
• and deduction of retirement benefits (use 5% of total pay).
Use the model to write a payroll application that prints an employee’s pay
receipt that lists gross pay, all deductions, and net pay.
11. Write an application that lets a user move a “mouse” within a box.
|
|
--------------------------__
|
/ .\
|
295
6.12. BEYOND THE BASICS
|
|
|
|
|
|
-----
|
|
|
|
|
|
---------------------------
The input commands to the program consist of F (move forwards one mouselength), L (turn left), R (turn right). Write a model that remembers the position
of the mouse in the box and the direction the mouse is pointed; the model will
have methods that let the mouse move forwards and turn left and right. Then,
write an output view that draws a picture of the mouse within the box. Finally,
write a controller that transfers the commands to the model.
12. Extend the previous Project so that there are two mice in the box, and each
mouse is controlled by a player. (The players take turns moving their respective
mice.) Invent a game, e.g., one mouse tries to “catch” the other.
6.12
Beyond the Basics
6.12.1 The Logic within the Conditional Statement
6.12.2 Interface Specifications and Integration
These optional sections develop topics related to conditional statements and class
building:
• how to reason about the correctness of a program in terms of the flows of control
within a conditional statement
• writing class interface specifications that contain preconditions and postconditions to help with testing and integrating the classes
6.12.1
The Logic of the Conditional Statement
Relational operations are of course logical connectives, as anyone who has studied
symbolic logic will readily testify. Indeed, we can use the laws of symbolic logic
to establish standard logical equivalences. For example, for arbitrary boolean-typed
expressions, P and Q, it is always the case that the computation of
!(P || Q)
yields the same results as that of
296
!P && !Q
Indeed, here are valuable logical equivalences that simplify relational expressions:
!(P || Q) is equivalent to
!(P && Q) is equivalent to
!!P is equivalent to P
P && (Q && R) is equivalent
P || (Q || R) is equivalent
!P && !Q
!P || !Q
to
to
P && Q && R
P || Q || R
is equivalent to
is equivalent to
(P && Q) && R
(P || Q) || R
By reading and employing these equivalences from left to right, you can minimize
the occurrences of parentheses and negations in a logical expression. For example,
the difficult-to-understand expression, !(x < 0 || (y <= 10 && y != 1)), simplifies
as follows:
!(x < 0 || (y <= 10 && y != 1))
is equivalent to !(x < 0) && !(y <= 10 && y != 1)
is equivalent to x >= 0 && (!(y <= 10) || !( y != 1))
is equivalent to x >= 0 && (y > 10 || y == 1)
Since we used relational operations to compress the structure of nested conditionals, we should not be surprised to learn that there is a connection between symbolic
logic and the conditional statement. The connection can be stated as follows:
if ( TEST )
{ // here, assume TEST is true
STATEMENTS1 // use the assumption to reach some goal, G
}
else { // here, assume !TEST is true
STATEMENTS2 // use the assumption to reach some goal, G
}
// regardless of outcome of TEST, goal G is achieved
That is, the then-arm’s statements can assume the logical truth of the test when the
statements execute. Similarly, the else-arm’s statements can assume the falsity of the
test. Both arms should be dedicated to completing an overall, common goal.
For example, consider this simple method, whose goal is to compute the absolute
value of an integer:
public int abs(int n)
{ int answer;
if ( n < 0 )
{ answer = -n; }
else { answer = n; }
return answer;
}
297
6.12. BEYOND THE BASICS
The goal of the conditional statement is to assign the absolute value of n into answer.
To validate that the goal is achieved, we annotate the conditional and study its arms:
if ( n < 0 )
// assume n < 0:
{ answer = -n; }
// goal: answer holds |n|
else // assume !(n < 0), that is,
{ answer = n; }
// goal: answer holds |n|
n >= 0:
The assumptions derived from the test help us deduce that both arms achieve the
goal.
Here is a more significant example: Consider the crucial step in Figure 7, where
a 24-hour time is translated into a twelve-hour time—the calculation of the hours
amount. The nested conditional that does this is extracted from Figure 7:
//
//
//
if
assume !( hour < 0 || hour > 23 || minute < 0 || minute > 59 )
that is, !(hour < 0) && !(hour > 23) && !(minute < 0) && !(minute > 59)
that is, hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59
( hour >= 13 )
{ answer = answer + (hour - 12); }
else { if ( hour == 0 )
{ answer = answer + "12"; }
else { answer = answer + hour; }
}
// goal: append correct hour to answer
Because this conditional statement is itself is nested within the else-arm of the outermost conditional, we can assume that it executes only when the outer conditional’s
test computes to false. Because of the logical equivalences stated above, we uncover
that the hours value falls in the range 0..23, which is crucial to the computation that
follows.
The overall goal of the nested conditional is to append to answer the correct twelvehour representation of hours. We can verify the goal by considering the outcomes of
the first test:
// assume
hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59
if ( hour >= 13 )
// assume that hour >= 13
// more precisely, assume hour >= 13 && hour >= 0 && hour <= 23
// that is, hour >= 13 && hour <= 23
{ answer = answer + (hour - 12); }
// goal: append correct hour to answer
298
else // assume that !(hour >= 13), that is,
// more precisely, assume hour < 12 &&
// that is, hour >= 0 && hour < 12
{ if ( hour == 0 )
{ answer = answer + "12"; }
else { answer = answer + hour; }
}
// goal: append correct hour to answer
hour < 12
hour >= 0
&&
hour <= 23
The conditional’s arms are commented with the outcomes of the test. Most importantly, we may carry the assumption that held true at the beginning of the conditional
into its two arms and use it to deduce precise information about the range of values
hour might possess.
Consider the then-arm:
// assume hour >= 13 && hour <= 23
{ answer = answer + (hour - 12); }
// goal: append correct hour to answer
The assumption tells us exactly what we must know to validate that subtraction by
12 is the correct conversion to achieve the goal.
The else-arm can be analyzed similarly:
// assume hour >= 0 && hour <
{ if ( hour == 0 )
{ answer = answer + "12";
else { answer = answer + hour;
}
// goal: append correct hour to
12
}
}
answer
Our analysis of the else-arm’s test produces these additional assumptions:
if ( hour == 0 )
// assume hour == 0 && hour >= 0 &&
// that is, hour == 0
{ answer = answer + "12"; }
// goal: append correct hour to answer
hour < 12
else // assume !(hour == 0) && hour >= 0 &&
// that is, hour > 0 && hour < 12
{ answer = answer + hour; }
// goal: append correct hour to answer
hour < 12
Again, the logical assumptions help us validate that for both control flows the goal
is achieved. In summary, all cases are validated, and the nested conditional achieves
its goal.
6.12. BEYOND THE BASICS
299
Logical reasoning like that seen here is a powerful tool that helps a programmer
understand control flow and validate correct behavior prior to testing. Indeed, logical
reasoning helps one write a conditional statement correctly, because the test part
of the conditional should ask exactly the precise question that gives one the logical
information needed to validate that the conditional’s arms achieve the overall goal.
Exercises
1. Use the logical equivalences stated at the beginning of the Section to simplify
these expressions so that they possess no negation operations at all. Assume
that x and y are integer variables.
(a) !(x < 0 || x > 59)
(b) !(!(x == 0) && y != 1)
(c) !(x > 0) && !(y != 1 || x >= 0)
(d) !(x == 0 && (x != x) && true)
2. Given this class:
public class Counter
{ private int count;
public Counter()
{ count = 0; }
public boolean increment()
{ count = count + 1;
return true;
}
public boolean equalsZero()
{ return (count == 0); }
}
Say that we have this situation:
Counter c = new Counter();
if ( TEST )
{ ... }
(a) Write a TEST of the form P && Q and show that this expression does not
compute the same result as does Q && P.
300
(b) Similarly demonstrate that P || Q behaves differently than Q || P; also
for P == Q and P != Q.
(c) Table 5 shows that for the expression, P && Q, if P computes to false, then
Q is not computed at all. Explain why this is different from a semantics of
&& where both P and Q must compute to answers before the answer of the
conjunction is computed.
3. Use logical reasoning on the arms of the conditionals to validate that the following methods compute their goals correctly:
(a) /** max returns the larger value of its three arguments
* @param x - the first argument
* @param y - the second argument
* @param z - the third argument
* @return the largest of the three
public int max(int x, int y, int z)
{ int big;
if ( x >= y && x >= z )
{ big = x; }
else { if ( y >= z )
{ big = y; }
else { big = z; }
}
return big;
}
*/
(b) /** max returns the largest value of its three arguments.
* The parameters and returned result are the same as above. */
public int max(int x, int y, int z)
{ int big = x;
if ( y > big )
{ big = y; }
if ( z > big )
{ big = z; }
return big;
}
6.12.2
Interface Specifications and Integration
Testing classes one by one does not ensure that the completed application will behave
as expected—sometimes the interaction between objects goes unexpectedly wrong.
This is not supposed to happen, but the problem can arise when the designer of a
method has not specified clearly (i) the conditions under which an object’s method
should be used and (ii) the form of result the method produces. A standard format,
6.12. BEYOND THE BASICS
301
or interface specification, for methods is needed to help them integrate correctly. The
interface specifications that we used in Tables 10 and 12 in this Chapter are good
starting examples.
Unfortunately, the Java compiler does not force a programmer to write specifications like those in Tables 10 and 12. Indeed, the compiler merely requires that each
method possess a header line that lists the data types of the method’s parameters
and the data type of the returned result, if any. The Java compiler uses the header
line to ensure that every invocation of the method uses the correct quantity and data
typings of actual parameters. The compiler also checks that the result returned from
the method can be correctly inserted into the place of invocation.
But the compiler’s checks are not enough. Say that a programmer has miscoded
the deposit method from class BankAccount in Figure 11 as follows:
public void deposit(int amount)
{ balance = balance * amount; }
Although this coding violates the specification in Table 10, the Java compiler does not
notice the problem. This is a disaster—other programmers, building other classes,
will rely on Table 10 as the correct specification for class BankAccount, meaning that
the BankAccount objects they create and use are faulty.
The point is a programmer is morally bound to build a class that correctly matches
its interface specification. To remind the programmer of her obligation, in this text we
require that each method be prefixed by comments taken from the class’s specification.
Also, once the class is completed, the javadoc program can be used to generated the
Web page description for the class. (See the section, “Generating Web Documentation
with javadoc,” in Chapter 5.)
A well-written interface specification will state concisely the nature of a method’s
parameters, what the method does, and what result, if any, the method produces.
Let’s examine the specfication for method withdraw from Figure 11:
/* withdraw removes money from the account, if it is possible.
* @param amount - the amount of money to be withdrawn, a nonnegative int
* @return true, if the withdrawal was successful; return false, if the
*
amount to be withdrawn was larger than the account’s balance */
public boolean withdraw(int amount)
Notice the remark attached to parameter amount: It states that the actual parameter
must be a nonnegative int. This requirement of the parameter is called a precondition; the correct behavior of the method is guaranteed only when this condition holds
true for the actual parameter. A precondition is like the statement of “acceptable conditions of use” that one finds in a warranty that comes with a household appliance.
(A real-life example: “This television is for indoor use only.”)
The @return comment, which describes the result value, is called the postcondition.
The clients (users) of the method trust the postcondition to state the crucial property
302
of the method’s result. The clients use the postcondition to validate their own work.
(For example, the validation of the controller, AccountManager, depends crucially on
withdraw accomplishing what is promised in its postcondition.)
When a method has no @return clause (that is, its return type is void), then the
first sentence of the comment, which describes the behavior of the method, acts as
the postcondition. The specification for method deposit is a good example:
/** deposit adds money to the account.
* @param amount - the amount of money to be added, a nonnegative int */
public void deposit(int amount)
Because an interface specification is a specification for object connection, it should
state all key information for using the object’s methods; a programmer should not
be required to read the body of a method that someone else has written in order
to invoke it—reading the method’s specification should suffice. This requirement is
essential for creating classes that others can use.
Programmers often call a class’s interface specification its API (application programming interface).
If a large application is developed by a group of people, and if the people agree to
use specifications like the ones in this Chapter, it becomes possible for each person to
design, code, and test a part of the application independently of the other parts. (At
the testing stage, one can write “dummy classes” to represent the unfinished classes
that others are writing.)
The specifications used in this text are somewhat informal, but they are better
than none at all. Indeed, this is why the designers of the Java language developed
the javadoc documentation program—a programmer can generate useful APIs while
writing her programs.
Indeed, since javadoc does not execute the class that it reads, it will generate API
documentation for an incomplete class. For example, if a multi-class application must
be written by a team of people, it is useful to have the API documentation for all
classes available as soon as possible, even before the classes are completed. One can
write the interface specifications, attach empty method bodies to them, and supply
them to javadoc. Here is such a dummy class from which javadoc generates a useful
API:
/** BankAccount manages a single bank account */
public class BankAccount
{
/** Constructor BankAccount initializes the account
* @param initial_amount - the starting account balance, a nonnegative. */
public BankAccount(int initial_amount)
{ }
/** deposit adds money to the account.
6.12. BEYOND THE BASICS
303
* @param amount - the amount of money to be added, a nonnegative int */
public void deposit(int amount)
{ }
/* withdraw removes money from the account, if it is possible.
* @param amount - the amount of money to be withdrawn, a nonnegative int
* @return true, if the withdrawal was successful; false, if the amount
*
to be withdrawn was larger than the account’s balance */
public boolean withdraw(int amount)
{ }
/* getBalance reports the current account balance
* @return the balance */
public int getBalance()
{ }
}
Finally, here is a warning about typing the Java commentary for an interface
specfication: A method’s commentary is enclosed within comment markers, /** and
*/. Don’t forget the */! The following class compiles with no errors, yet it is missing
one of its methods:
class M
{ /** f’s description goes here.
* @param i - parameter info
* @return information
public int f(int i) { return i+1; }
/** comment for g goes here.
* @param x - parameter info */
public void g(String x) { System.out.println(x); }
}
The problem is the missing */ in the commentary for f. Because of the missing comment marker, the code for f is treated as a comment that merges into g’s comments.
Hence, only g is compiled in class M. You will notice the problem only later when you
try to invoke f from another program and you receive a mysterious message from the
compiler stating that f does not exist.
Chapter 7
Patterns of Repetition: Iteration and
Recursion
7.1 Repetition
7.2 While Loops
7.3 Definite Iteration
7.3.1 Definite-Iteration Example: Painting a Bulls-Eye
7.4 Nontermination
7.5 Indefinite Iteration: Input Processing
7.5.1 Indefinite Iteration: Searching
7.6 For-Statements
7.7 Nested Loops
7.8 Writing and Testing Loops
7.9 Case Study: Bouncing Ball Animation
7.10 Recursion
7.10.1 An Execution Trace of Recursion
7.11 Counting with Recursion
7.11.1 Loops and Recursions
7.11.2 Counting with Multiple Recursions
7.12 Drawing Recursive Pictures
7.13 Summary
7.14 Programming Projects
7.15 Beyond the Basics
Repeating an action over and over is called repetition. This Chapter introduces
techniques and applications of repetition in programming. The Chapter is organized
into three distinct parts:
7.1. REPETITION
305
• The first part, Sections 7.1-7.8, introduce the while- and for-statements for
writing standard and classic patterns of repetition, called iteration.
• The second part, Section 7.9, applies repetition in a case study of designing and
building an animation
• The third part, Sections 7.10-7.12, promotes another form of repetition, recursive method invocation, as a technique for solving problems in terms of repeatedly solving simpler subproblems.
The first part of the Chapter is essential reading; the second is strongly recommended;
and the third can be omitted on first reading, if desired.
After studying the Chapter, the reader should be able to identify when a programming problem should be solved by means of repetitive computation, apply the
appropriate repetitive pattern, and write the pattern in Java.
7.1
Repetition
Some jobs must be solved by repeating some step over and over:
• When replacing a flat tire with a spare, you must “place a lug nut at the end
of each bolt, and as long as the nut is loose, rotate it clockwise over and over
until it is finally secure against the wheel.”
• When searching for your favorite show on the television, you must, “while you
haven’t yet found your show, press the channel button repeatedly.”
Both of these “algorithms” rely on repetition to achieve their goals.
Computers became popular partly because a computer program will unfailingly
repeat a tedious task over and over. For example, perhaps you must know the decimal
values of the fractions (reciprocals) 1/2, 1/3, 1/4, and so on, up to 1/20. A painful
way of programming these calculations is manually repeating a division statement 19
times:
public static void main(String[] args)
{ System.out.println("1/2 = " + (1.0/2));
System.out.println("1/3 = " + (1.0/3));
System.out.println("1/4 = " + (1.0/4));
...
System.out.println("1/20 = " + (1.0/20));
}
The computer readily computes the reciprocals, but we should find a simpler way
to request the computations than the “copy and paste” coding technique above. A
better solution is built with a control structure called a while loop.
306
7.2
While Loops
At the beginning of the previous section, we saw two algorithms for tightening a car’s
wheel and finding a television show. We can rewrite the two algorithms in “while-loop
style,” where we begin the algorithm with the word, “while,” followed by a true-false
test, followed by an action to perform as long as the test is true:
• while ( lug nut still loose ) { rotate nut clockwise one turn; }
• while ( favorite TV show not found ) { press the channel button; }
The phrase within the parentheses is the “test expression”; when the test expression is
true, the statement within the set braces is performed once, then the entire statement
repeats (“loops”). Eventually (we hope!) the test expression becomes false, and the
while-loop ceases.
We can write while-loops in Java; the format is
while ( TEST ) { BODY }
where TEST is a boolean-typed expression and BODY is a sequence of (zero or more)
statements.
With Java’s while-loop, we can rewrite the method that prints the reciprocals
from 1/2 to 1/20. The algorithm goes like this:
1. Set variable denominator = 2. The variable remembers which reciprocal to print
next.
2. Do this: while ( denominator <= 20 ) { print 1.0/denominator and add one to
denominator. }
Step 1 initializes the variable that acts as the loop’s counter; Step 2 prints the reciprocals, one by one, as the loop’s counter increases by ones. Here is how the algorithm
is written in Java:
public static void main(String[] args)
{ int denominator = 2;
while ( denominator <= 20 )
{ System.out.println("1/" + denominator + " = " + (1.0 / denominator));
denominator = denominator + 1;
}
}
The while-loop prints the fractional representations of 1/2, 1/3, ..., 1/20 and stops.
Here is a more precise explanation of how the while-loop, while ( TEST ) { BODY
}, executes:
1. The TEST expression is computed.
307
7.2. WHILE LOOPS
2. If TEST computes to true, then the BODY executes and the process repeats, restarting at Step 1.
3. If TEST computes to false, then the BODY is ignored, and the loop terminates.
Repetition by means of a while-loop is called iteration, and one repetition of the loop’s
body is called an iteration; when the loop repeats its body over and over, we say that
it iterates.
Here is the flowchart representation of a a while-loop—it is a graph with a cycle:
TEST?
true
false
BODY
and indeed, this is the origin of the term, “loop.”
Loops fall into two categories: definite iteration, where the number of the loop’s
iterations is known the moment the loop is started, and indefinite iteration, where it
is not. Also, it is possible for a loop’s iterations to be unbounded (that is, the loop
never stops). We study all three behaviors in the examples in the sections that follow.
Exercises
1. What will these while-loops print?
(a) String s = "";
while ( s.length() != 5 )
{ s = s + ’a’;
System.out.println(s);
}
(Recall that the length operation returns the length of a string; for exam-
ple,
String s = "abcde";
int i = s.length();
assigns 5 to i.)
(b) int countdown = 10;
while ( countdown != 0 )
{ System.out.println(i);
countdown = countdown - 1;
}
308
(c) int countdown = 5;
int countup = -1;
while ( countdown > countup )
{ countdown = countdown - 1;
System.out.println(countdown + " " + countup);
countup = countup + 1;
}
(d) while ( false )
{ System.out.println("false"); }
(e) String s = "";
while ( s.length() < 6 )
{ s = s + "a";
if ( (s.length() % 2) == 1 )
{ System.out.println(s); }
}
2. Write a while-loop to print the characters, ’a’ to ’z’, one per line. (Hint:
recall that Java allows you to initialize a variable as char c = ’a’ and to do
arithmetic with it, e.g., c = c + 1.)
3. Write a while-loop to print all the divisors of 1000 that fall within the range 2
to 50. (Recall that an integer, i, divides another integer, j, if j % i equals 0.)
(Hint: insert an if-statement in the body of the loop.)
7.3
Definite Iteration
A loop performs definite iteration when the number of the loop’s iterations is known
the moment the loop is started. The example in the previous section displayed definition iteration, since it was clear at the outset that the loop would repeat exactly 19
times. Definite-iteration loops arise often, and it is worth studying their development.
Say that we must construct a method that computes the average score of a student’s exams. The method first asks the student to type the number of exams and
then the method reads the exam scores, one by one, totals them, and computes the
average score.
There is a numerical pattern to computing an average score; if the student has
taken N exams, then the average score is calculated by this formula:
average = (exam1 + exam2 + ... + examN) / N
The ellipsis in the formula suggests that we should repeatedly sum the scores of the
exams until all N scores are totalled; then we do the division. Here is an algorithm
that uses several variables, along with a while-loop, to sum the exams:
1. Assume that variable how many holds the quantity of test scores to be read.
7.3. DEFINITE ITERATION
309
2. Declare a variable total points = 0 to remember the total of all the exams,
and declare variable, count = 0, to remember how many exam scores have been
read already.
3. While count not yet equals how many, do the following:
• Ask the user for the next exam score and add it to total points.
• Increment count by 1.
4. Calculate the average score as total points / how many.
The above algorithm can be refined into this Java-like coding:
int how_many = HOW MANY SCORES TO READ;
double total_points = 0.0;
int count = 0;
while ( count != how_many )
{ int score = READ INTEGER FROM THE USER;
total_points = total_points + score;
count = count + 1;
}
return (total_points / how_many);
The algorithm is presented as a Java method, computeAverage, in Figure 1. At each
iteration of the loop, the method generates a dialog to read the next exam score, and a
println statement helps us see the loop’s progress. Say that the user wishes to average
the scores, 8, 5, 6, and 7. The invocation, System.out.println(computeAverage(4)),
produces this trace information:
By reading the printed trace information, we see that at the beginning (and the
end) of each iteration, the value in total points indeed equals the sum of all the
exam scores read so far, that is,
total_points == exam_1 + exam_2 + ... + exam_count
310
Figure 7.1: while-loop to compute average score
import javax.swing.*;
/** computeAverage computes the average of test scores submitted by a user
* @param how many - the quantity of test scores to read; must be nonnegative
* @return the average of the test scores */
public double computeAverage(int how many)
{ double total points = 0.0; // the total of all test scores
int count = 0; // the quantity of tests read so far
while ( count != how many )
// at each iteration: total points == exam 1 + exam 2 + ... + exam count
{ // ask the user for the next exam score:
String input = JOptionPane.showInputDialog("Type next exam score:");
int score = new Integer(input).intValue();
// add it to the total:
total points = total points + score;
count = count + 1;
// print a summary of the progress:
System.out.println("count = " + count + "; total = " + total points);
}
// at conclusion: total points == exam 1 + exam 2 + ... + exam how many
return (total points / how many);
}
This crucial fact is called the loop’s invariant property or invariant, for short. The invariant explains what the loop has accomplished with its iterations so far. Because of
their value in helping us understand a loop’s secrets, we will state invariant properties
for the loops we study.
Because of the loop’s invariant property, we know that when the loop stops with
count equal to how many, then it must be the case that total points holds all the
total points for all exams. From here, it is a small step to compute the average.
In the example, variables count and total points play crucial roles in remembering the loop’s progress—the former acts as the loop’s counter, and the latter
remembers a running total.
It is absolutely crucial to understand the details of loop execution, so we examine
part of the execution trace of the example. Say that the method has been invoked as
computeAverage(4); once variables count and total points are initialized, we have
311
7.3. DEFINITE ITERATION
the following configuration:
int how many == 4
double total points == 0.0
int count ==
0
>while ( count != how many )
{ int score = ...;
count = count + 1;
total points = total points + score;
}
The loop’s test evaluates to true, so execution moves into the body:
int how many == 4
double total points == 0.0
int count == 0
while ( true )
{ >int score = ...;
count = count + 1;
total points = total points + score;
}
The user types 8 as the first exam score; the loop’s body revises the variables’ values:
int how many == 4
double total points == 8.0
int count == 1
while ( true )
{ ...
>}
At this point, the while-loop “restarts”—the control marker, >, returns to the
beginning of the loop, and the process repeats. Of course, the variables retain their
updated values:
int how many == 4
double total points == 8.0
int count ==
1
>while ( count != how many )
{ int score = ...;
count = count + 1;
total points = total points + score;
}
Again, the test is evaluated and the result is true, so the body of the loop is entered
for yet another repetition, and the second score, 5, is read:
int how many == 4
while ( true )
{ ...
>}
double total points == 13.0 int count == 2
312
The loop repeats twice more, reading the last two scores, and we reach the configuration where count’s cell holds 4:
int how many == 4
double total points == 26.0
int count ==
4
double total points ==26.0 int count ==
4
>while ( count != how many )
{ int score = ...;
count = count + 1;
total points = total points + score;
}
The test evaluates to false, and the loop is abandoned:
int how many == 4
while ( false )
{ ... }
¿
This loop is an example of definite iteration, because once the loop is started, the
number of iterations is completely decided; here, it is how many iterations.
Definite iteration loops almost always use
• a loop counter that remembers how many iterations are completed,
• a test expression that examines the loop counter to see if all the iterations are
completed,
• a statement that increments the loop counter to record the iteration.
For a loop counter, count, the pattern of definite iteration often looks like this:
int count = INITIAL VALUE;
while ( TEST ON count )
{ EXECUTE LOOP BODY;
INCREMENT count;
}
We have seen this pattern used twice already.
Occasionally, count is incremented at the beginning of the loop body:
int count = INITIAL VALUE;
while ( TEST ON count )
{ INCREMENT count;
EXECUTE LOOP BODY;
}
7.3. DEFINITE ITERATION
313
Exercises
1. Implement an application that computes upon exam averages:
(a) First, place the computeAverage method in a new class you write, called
class ExamStatistics. Next, write a main method whose algorithm goes
like this:
i. construct a new ExamStatistics object;
ii. construct a dialog that asks the user to enter a positive number for
the number of exams
iii. invoke the computeAverage method in the ExamStatistics object
iv. construct a dialog that shows the result returned by computeAverage.
(b) Modify computeAverage so that if its argument is a nonpositive integer,
the method shows a dialog that announces an error and returns 0.0 as its
answer.
(c) Next, write a method, computeMaxScore:
/** computeMaxScore reads a sequence test scores submitted by a user and
* returns the highest score read
* @param how_many - the quantity of test scores to read; must be nonnegative
* @return the maximum test score */
public double computeMaxScore(int how_many)
Add this method to class ExamStatistics and modify the main method
you just wrote to invoke computeMaxScore instead.
(d) Write this method and include it within class ExamStatistics:
/** computeBetterAverage computes the average of test scores submitted
* by a user _but discards_ the lowest score in the computation.
* @param how_many - the quantity of test scores to read; must be > 1
* @return the average of the best (how_many - 1) test scores */
2. Write an execution trace for this example:
int t = 4;
int count = 2;
while ( count <= 4 )
{ t = t * 2;
count = count + 1;
}
3. Use the pattern for definite iteration to write a loop that displays this output,
all on one line: 88 77 66 55 44 33 22 11
314
4. Write a main method that does the following:
(a) uses a loop to ask the user to type four words, one word at a time, e.g.,
I
like
my
dog
(b) shows a dialog that displays the words listed in reverse order on one line,
e.g.,
dog my like I
5. Many standard mathematical definitions are computed with definite-iteration
loops. For each of the definitions that follow, write a method that computes
the definition:
(a) The summation of a nonnegative integer, i, is defined as follows:
summation(i) = 0 + 1 + 2 + ... + i
For example, summation(4) is 0 + 1 + 2 + 3 + 4 = 10. So, write a method
that computes summation whose header line reads like this:
public int summation(int i)
(b) The iterated product of two nonnegative integers, a and b, goes
product(a, b) =
a * (a+1) * (a+2) * ... * b
(Note: if b > a holds true, then define product(a, b) = 1.) For example,
product(3, 6) is 3 * 4 * 5 * 6 = 360.
(c) A famous variation on iterated product is factorial; for a nonnegative
integer, m, its factorial, m!, is defined as follows:
0! = 1
n! = 1 * 2 * ... * n,
for positive
n
For example, 5! is 1 * 1 * 2 * 3 * 4 * 5 = 120. (Important: Because
the values of m! grow large quickly as m grows, use the Java data type
long (“long integer”) instead of int for the argument and answer of this
function.)
(d) If you enjoy using sines and cosines, then use the factorial method in the
previous exercise to implement these classic methods:
7.3. DEFINITE ITERATION
315
i. /** sine calculates the sine value of its argument, using the formula
*
sin(x) = x - (x^3/3!) + (x^5/5!) - (x^7/7!) + ... - (x^n/19!)
* @param x - the value, in radians, whose sine is desired
*
(i.e., sine(0)=0, sine(pi/2)=1, sine(pi)=0, sine(3pi/2)=-1, etc.)
* @return the sine as calculated by the formula */
public double sine(double x)
(Note: use Math.pow(a,b) to compute ab .) Compare the answers your
method produces to the ones produced by the method, Math.sin(...).
ii. /** cosine calculates the cosine value of its parameter, using the formula
* cosin(x) = 1 - (x^2/2!) + (x^4/4!) - (x^6/6!) + ... - (x^20/20!)
* @param x - the value, in radians, whose cosine is desired
* @return the cosine as calculated by the formula */
public double cosine(double x)
7.3.1
Definite-Iteration Example: Painting a Bulls-Eye
The definite-iteration pattern for loops can help us to draw interesting graphical
patterns. As an example, perhaps we want to paint a “bulls-eye” pattern with n
alternating red and white rings, where n’s value is given by the program’s user. When
n is 7, we see:
Assuming that the size of the bulls-eye is also set by the user, we can use the definite
iteration pattern to paint each stripe, one at a time, from the outermost to the
innermost. Three variables will be needed to control the painting:
• count, which remembers how many circles have been drawn;
• color, which remembers the color of the next circle to paint;
• diameter, which remembers the diameter of the next circle to paint.
316
Why do we require these three variables? Obviously, to paint one ring, we must know
the ring’s diameter and color. If we wish to paint multiple rings, we must remember
how many rings we have painted already, so that we know when it is time to stop.
The three variables are used in the painting algorithm:
1. Set count equal to 0, set color equal to red, and set diameter equal to the
bulls-eye’s size.
2. While count not yet equals rings (the total number of rings desired), do the
following:
• In the center of the graphics window, paint a filled circle of the appropriate
diameter with the current color.
• Revise all variables: increment count by one, make the diameter smaller
(so that the next ring is painted smaller than this one), and reset the color
(if it’s red, make it white; if it’s white, make it red).
These are the basic steps, although the details for calculating the rings’ exact positions
are omitted for the moment.
Perhaps we write the algorithm as a method, so that the method can be used at
will to paint bulls-eyes at whatever position, number of rings, and diameter that we
desire: Figure 2 displays the Java method that is parameterized on these arguments.
The method in Figure 2 is used as a helper method in the class that displays the
graphics window—see Figure 3. To use class BullsEyeWriter, we would construct a
new object such as
new BullsEyeWriter(7, 140)
which draws a 7-ring bulls-eye of diameter 140 pixels.
Exercises
1. Construct this object:
new BullsEyeWriter(21, 200)
Explain why it is important that the bulls-eye’s circles are painted from the
largest to the smallest; explain why the innermost ring (a circle, actually) has
a width that is almost twice as large as all the other rings. (Hint: insert a
System.out.println(diameter) statement into the paintBullsEye method to
monitor the drawing of the rings.)
2. Write this Java method:
7.3. DEFINITE ITERATION
Figure 7.2: method that paints a bulls-eye
/** paintBullsEye paints a bulls-eye
* @param x position - of the upper left corner of the bulls-eye
* @param y position - of the upper left corner of the bulls-eye
* @param rings - the number of rings in the bulls-eye
* @size - the bulls-eye’s diameter
* @param g - the graphics pen */
public void paintBullsEye(int x position, int y position,
int rings, int size, Graphics g)
{ int count = 0;
// no rings painted just yet
int diameter = size;
// diameter of next ring to paint
int ring width = size / rings; // set width for each ring
Color color = Color.red;
while ( count != rings )
// invariant: have painted count rings so far
{ // calculate upper left corner of ring to paint, centering the
// the ring within the bulls-eye by dividing by 2:
int new x position = x position + ((ring width * count)/2);
int new y position = y position + ((ring width * count)/2);
// paint the ring:
g.setColor(color);
g.fillOval(new x position, new y position, diameter, diameter);
// increment variables:
count = count + 1;
diameter = diameter - ring width;
if ( color == Color.red )
{ color = Color.white; }
else { color = Color.red; }
}
}
317
318
Figure 7.3: a panel that displays a bulls-eye
import javax.swing.*;
import java.awt.*;
/** BullsEyeWriter paints a bulls-eye on a panel */
public class BullsEyeWriter extends JPanel
{ private int rings; // how many rings appear in the bulls-eye
private int size;
// the size of the completed bulls-eye
private int panel width; // width of the graphics panel
private int offset = 20; // where to start painting the bulls-eye
/** Constructor BullsEyeWriter constructs the panel and frames it.
* @param number of rings - how many rings in the bulls-eye
* @param total size - the diameter of the bulls-eye */
public BullsEyeWriter(int number of rings, int total size)
{ rings = number of rings;
size = total size;
panel width = size + (2 * offset);
// construct frame for this panel:
JFrame my frame = new JFrame();
my frame.getContentPane().add(this);
my frame.setTitle("Bulls-Eye");
my frame.setSize(panel width, panel width);
my frame.setVisible(true);
}
/** paintComponent draws the bulls-eye
* @param g - the graphics pen that does the drawing */
public void paintComponent(Graphics g)
{ g.setColor(Color.yellow); // paint background yellow:
g.fillRect(0, 0, panel width, panel width);
paintBullsEye(offset, offset, rings, size, g);
}
... paintBullsEye appears here ...
}
7.4. NONTERMINATION
319
/** paintSquares paints n squares across the left-to-right diagonal
* of a graphics window
* @param x_position - of the upper left corner of the first square
* @param y_position - of the upper left corner of the first square
* @param n - the number of squares to paint
* @size - the width of each square
* @param g - the graphics pen */
private void paintsquares(int x_position, int y_position,
int n, int size, Graphics g)
3. Figure 9 of Chapter 5 contains a helper method, paintAnEgg, that paints an egg
on a graphics window. Use this method to write a method, paintBullsEyeOfEggs,
that paints a “bulls-eye” of n centered eggs in a graphics window.
7.4
Nontermination
Recall the computeAverage method in Figure 1, which read a sequence of exam scores,
summed them, and computed their average. What happens when we invoke the
method with a negative integer, e.g., computeAverage(-1)? Indeed, the invocation
requests exam scores forever, summing the scores without ceasing, and we will see an
indefinite produced,
count
count
count
count
count
...
=
=
=
=
=
1;
2;
3;
4;
5;
total
total
total
total
total
=
=
=
=
=
8
13
19
26
30
assuming that the user is patient enough to submit a never-ending sequence of scores!
The loop inside computeAverage iterates indefinitely because its test expression is
always true. Such behavior is called nontermination, or more crudely, infinite looping
or just “looping.” A nonterminating loop prevents the remaining statements in the
program from executing, and in this case, the method from computing the average
and returning an answer.
Although the method’s header comment tells us to supply only nonnegative parameters to computeAverage, the method might defend itself with a conditional statement that checks the parameter’s value:
/** computeAverage computes the average of test scores submitted by a user
* @param how_many - the quantity of test scores to read; must be nonnegative
* @return the average of the test scores
* @throw RuntimeException, if how_many is negative */
public double computeAverage(int how_many)
320
{ if ( how_many <= 0 )
{ throw new RuntimeException("computeAverage error: negative quantity"); }
double total_points = 0.0; // the total of all test scores
int count = 0; // the quantity of tests read so far
while ( count != how_many )
{ ... see Figure 1 for the loop’s body ... }
return (total_points / how_many);
}
The initial conditional statement throws an exception to prevent looping.
Often, nontermination is an unwanted behavior, and unfortunately there is no
mechanical technique that can verify that a given loop must terminate, but the section
titled “Loop Termination,” included as an optional section at the end of the Chapter,
presents a technique that helps one prove loop termination in many cases.
If one of your Java applications appears to have infinite looping, you can terminate
it: When using an IDE, select the Stop or Stop Program button; when using the JDK,
press the control and c keys simultaneously.
Finally, we should note that a while-loop’s test can sometimes be written cleverly
so that looping becomes impossible. Consider again the loop in Figure 1, and say
that we rewrite the loop’s test so that the Figure reads as follows:
public double modifiedComputeAverage(int how_many)
{ double total_points = 0.0;
int count = 0;
while ( count < how_many )
{ String input = JOptionPane.showInputDialog("Type next exam score:");
int score = new Integer(input).intValue();
total_points = total_points + score;
count = count + 1;
System.out.println("count = " + count + "; total = " + total_points);
}
return (total_points / how_many);
}
Now, if how many receives a nonpositive value, then the loop’s test fails immediately,
and the loop executes zero iterations.
But the simple alteration is imperfect, because it allows the method to continue
its execution with an improper value for how many and compute an erroneous result:
If how many holds a negative number, then the method computes that the exam average is 0.0, which is nonsensical, and if how many holds zero, the result is even more
surprising—try modifiedComputeAverage(0) and see.
Perhaps looping is unwanted, but under no conditions do we want a method that
returns a wrong answer, either. For this reason, you should take care when altering a
while-loop’s test to “ensure” termination; the alteration might cause a wrong answer
to be computed, travel to another part of the program, and do serious damage.
7.5. INDEFINITE ITERATION: INPUT PROCESSING
321
Exercises
1. Write this method, which is is designed to execute forever:
/** printReciprocals displays the decimal values of the fractions,
* 1/2, 1/3, 1/4, ..., one at a time: When invoked,
* it shows a message dialog with the information, "1/2 = 0.5",
*
and when the user presses OK, it shows another message dialog
*
that says, "1/3 = 0.3333333333", and when the user presses OK,
*
it shows another message dialog, "1/4 = 0.25", and so on.... */
public void printReciprocals()
Now, write an application that invokes it. Test the application for as long as
you have the patience to do so.
2. Reconsider the paintBullsEye method in Figure 2. What happens if the method
is asked to paint zero rings? A negative number of rings? Revise the method
so that it will always terminate.
Study the invariant property for the loop in the computeAverage method in
Figure 1. When the loop in that method concludes, we know that total points
== exam 1 + exam 2 + ... + exam how many holds true.
Now, review modifiedComputeAverage. Is the loop invariant the same as that
in Figure 1? Can we conclude the same result when the loop terminates as we
did in Figure 1? What can we conclude about the value of total points when
the loop in modifiedComputeAverage terminates?
7.5
Indefinite Iteration: Input Processing
Many programs are designed to interact with a user indefinitely; for example, the
bank-account manager application in Chapter 6 processed as many account transactions as its user chose to enter. The appplication did this by sending a message to
restart itself after processing each input request, but we see in this Section that a
while-loop is a simpler technique for repetitive input processing.
We can build on the exam-average method in Figure 1 to illustrate the technique.
Say that we modify the method so that it does not know how many exam scores it
must read. Instead, the user enters exam scores, one by one, into input dialogs until
the user terminates the process by pressing the Cancel button. The method handles
this behavior with a loop that terminates when Cancel is pressed.
A first attempt at the revised method’s algorithm might go,
1. while ( the user has not yet pressed Cancel ), do the following:
• Generate a dialog to read the next exam score;
322
• If the user pressed Cancel, then note this and do no further computation
within the loop;
• else the user entered an exam score, so add it to the total and add one to
the count of exams read.
2. Compute the average of the exams.
To write the loop’s test expression, we will use a boolean variable, processing, to
remember whether the user has pressed the Cancel button, signalling the desire to
quit. This extra variable gives us a clever way of writing the loop:
boolean processing = true;
while ( processing )
{ generate a dialog to read the next exam score;
if ( the user pressed Cancel )
{ processing = false; } // time to terminate the loop!
else { add the score to the total and increment the count; }
}
Figure 4 displays the modified method from Figure 1.
There is a standard way to process a user’s input with a while-loop: A sequence
of input transactions are submitted, one at a time, and each input transaction is
processed by one iteration of the loop. The loop terminates when there are no more
input transactions. Here is the pattern:
boolean processing = true;
while ( processing )
{ READ AN INPUT TRANSACTION;
if ( THE TRANSACTION INDICATES THAT THE LOOP SHOULD STOP )
{ processing = false; }
else { PROCESS THE TRANSACTION; }
}
This pattern was used in Figure 4 and can be profitably used for most input-processing
applications. The loop is an example of indefinite iteration, because when the loop
starts, it is not decided how many iterations will occur until termination. Note that
indefinite iteration is different from nontermination—assuming that there are finitely
many input transactions, the loop will terminate!
Exercises
1. Explain what this method does:
public static void main(String[] args)
{ boolean processing = true;
7.5. INDEFINITE ITERATION: INPUT PROCESSING
323
Figure 7.4: processing input transactions
import javax.swing.*;
/** computeAverage computes the average of test scores submitted by a user.
* The user terminates the submissions by pressing Cancel.
* @return the average of the test scores
* @throw RuntimeException if the user presses Cancel immediately */
public double computeAverage()
{ double total points = 0.0; // the total of all test scores
int count = 0; // the quantity of tests read so far
boolean processing = true;
while ( processing )
// at each iteration: total points == exam 1 + exam 2 + ... + exam count
{ String input = JOptionPane.showInputDialog
("Type next exam score (or press Cancel to quit):");
if ( input == null ) // was Cancel pressed?
{ processing = false; } // time to terminate the loop
else { int score = new Integer(input).intValue();
total points = total points + score;
count = count + 1;
}
}
if ( count == 0 ) // did the user Cancel immediately?
{ throw new RuntimeException("computeAverage error: no input supplied"); }
return (total points / count);
}
int total = 0;
while ( processing )
{ String s = JOptionPane.showInputDialog("Type an int:");
int i = new Integer(s);
if ( i < 0 )
{ processing = false; }
else { total = total + i; }
}
JOptionPane.showMessageDialog(null, total);
}
2. Write an application that invokes the method in Figure 4 to compute an average
of some exam scores. The application displays the average in a message dialog.
3. Write an application that reads as many lines of text as a user chooses to type.
324
The user types the lines, one at a time, into input dialogs. When the user types
presses Cancel or when the user presses just the Enter key by itself (with no
text typed), the program prints in the command window all the lines the user
has typed and halts. (Hint: You can save complete lines of text in a string
variable as follows:
String s = "";
...
s = s + a_line_of_text + "\n";
Recall that the \n is the newline character.)
4. Revise the bank-account manager of Chapter 6 so that it uses a while-loop to
process its input transactions. (Hint: Revise processTransactions in class
AccountController in Figure 16, Chapter 6.)
7.5.1
Indefinite Iteration: Searching
In the real world, searching for a missing object is an indefinite activity, because we
do not know when we will find the object. Programs can also go “searching”—for
example, a program might search a computerized telephone directory for a person’s
telephone number. A small but good example of computerized searching is finding
a specific character in a string. Searches use an important pattern of loop, so we
develop the character-search example in detail.
Recall these two useful operations on strings from Table 5, Chapter 3:
• The length operation returns the length of a string; for example,
String s = "abcde";
int i = s.length();
assigns 5 to i. The length of an empty string, "", is 0.
• We use the charAt operation to extract a character from a string:
String s = "abcde";
char c = s.charAt(3);
assigns ’d’ to c. (Recall that a string’s characters are indexed by 0, 1, 2, and
so on.)
Now we consider how to search a string, s, for a character, c: We examine the
string’s characters, one by one from left to right, until we find an occurrence of c or
we reach the end of s (meaning c is not found). We use an integer variable, index,
to remember which character of s we should examine next:
325
7.5. INDEFINITE ITERATION: INPUT PROCESSING
1. Set index to 0.
2. While ( c is not yet found, and there are still unexamined characters within s
), do the following:
(a) If s.charAt(index) is c, then we have found the character at position index
and can terminate the search.
(b) Otherwise, increment index.
The while-loop suggested by the algorithm looks like this:
int index = 0;
boolean found = false; // remembers whether
while ( !found && index < s.length() )
{ if ( s.charAt(index) == c )
{ found = true; }
else { index = index + 1; }
}
c
has been found in
s
The loop’s test consists of two boolean expressions, combined by conjunction (&&),
and the loop terminates when either of the conjuncts becomes false.
This algorithm is realized in Figure 5.
The loop’s invariant property tells us how the loop operates: As long as found is
false, c is not found in the searched prefix of s; when found becomes true, the search
has succeeded.
The loop might terminate one of two ways:
• !found becomes false, that is, variable found has value true. By Clause (1) of
the invariant, we know that c is found at position index in s.
• !found stays true, but index < s.length() becomes false. By Clause (2) of
the invariant, we know that c is not in the entire length of string s.
These facts are crucial to returning the correct answer at the end of the function;
study the above until you understand it thoroughly.
A string is a kind of “collection” or “set” of characters. In the general case, one
searches a set of items with the following pattern of while-loop:
boolean item_found = false;
DETERMINE THE FIRST ‘‘ITEM’’ TO EXAMINE FROM THE ‘‘SET’’;
while ( !item_found && ITEMS REMAIN IN THE SET TO SEARCH )
{ EXAMINE AN ITEM;
if ( THE ITEM IS THE DESIRED ONE )
{ item_found = true; }
else { DETERMINE THE NEXT ITEM TO EXAMINE FROM THE SET; }
}
326
Figure 7.5: searching for a character in a string
/** findChar locates the leftmost occurrence of a character in a string.
* @param c - the character to be found
* @param s - the string to be searched
* @return the index of the leftmost occurrence of c in s;
* return -1 if c does not occur in s */
public int findChar(char c, String s)
{ boolean found = false; // remembers if c has been found in s
int index = 0;
// where to look within s for c
while ( !found && index < s.length() )
// invariant:
// (1) found == false means c is not any of chars 0..(index-1) in
// (2) found == true means c is s.charAt(index)
{ if ( s.charAt(index) == c )
{ found = true; }
else { index = index + 1; }
}
if ( !found ) // did the loop fail to find c in all of s?
{ index = -1; }
return index;
}
s
The loop can terminate in two possible ways:
• !item found evaluates to false. This means the search succeeded, and the
desired item is the last ITEM examined.
• ITEMS REMAIN evaluates to false. This means the search examined the entire
set and failed to locate the desired item.
This pattern was used to write the method in Figure 5.
Here is another use of the searching pattern—determining whether an integer is
a prime number. Recall that an integer 2 or larger is a prime if it is divisible (with
no remainder) by only itself and 1. For example, 3, 13, and 31 are primes, but 4 and
21 are not.
To determine whether an integer n is prime, we try dividing n by each of the
numbers in the set, {2, 3, 4, ..., n/2 }, to try to find a number that divides into n
with remainder zero. (There is no need to try integers larger than n/2, of course.) If
our search for a divisor fails, then n is a prime.
The method’s algorithm is based on the searching pattern:
boolean item_found = false;
current = n / 2;
// start searching here for possible integer divisors
7.5. INDEFINITE ITERATION: INPUT PROCESSING
327
Figure 7.6: method for prime detection
/** isPrime examines an integer > 1 to see if it is a prime.
* @param n - the integer > 1
* @return 1, if the integer is a prime;
* return the largest divisor of the integer, if it is not a prime;
* @throw RuntimeException, if the argument is invalid, that is, < 2. */
public int isPrime(int n)
{ int answer;
if ( n < 2 )
{ throw new RuntimeException("isPrime error: invalid argument " + n ); }
else { boolean item found = false;
int current = n / 2; // start search for possible integer divisor
while ( !item found && current > 1 )
// invariant:
// (1) item found == false means n is not divisible
//
by any of n/2, (n/2)-1, ...down to... current+1
// (2) item found == true means current divides n
{ if ( n % current == 0 )
{ item found = true; }
// found a divisor
else { current = current - 1; } // try again
}
if ( item found )
{ answer = current; } // current is the largest divisor of n
else { answer = 1; }
// n is a prime
}
return answer;
}
// and count downwards towards 2
while ( !item_found && current > 1 )
{ if ( current divides into n with remainder 0 )
{ item_found = true; }
// current is a divisor of n
else { current = current - 1; } // select another possible divisor
}
The search through the set, {n/2, (n/2) - 1, ..., 3, 2}, terminates if we find a divisor
that produces a remainder of 0 or if we search the entire set and fail. Figure 6 shows
the completed method.
Exercises
1. Modify method findChar in Figure 3 so that it locates the rightmost occurrence
of character c in string s. Remember to revise the loop’s invariant.
328
2. Use the searching pattern to write the following method:
/** powerOfTwo determines whether its argument is a power of 2.
* @param n - the argument, a nonnegative int
* @return m, such that n == 2^m, if n is a power of 2
* return -1, if n is not a power of 2 */
public int powerOfTwo(int n)
What is the “collection” that you are “searching” in this problem?
7.6
For-Statements
Definite-iteration loops pervade computer programming. When we use this pattern
of definite iteration,
{ int i = INITIAL VALUE;
while ( TEST ON i )
{ EXECUTE LOOP BODY;
INCREMENT i;
}
}
there is a special loop statement in Java, called a for-statement, that we can use to
tersely code the above pattern; it looks like this:
for ( int i = INITIAL VALUE; TEST ON i; INCREMENT i; )
{ EXECUTE LOOP BODY; }
The semantics of the for-statement is exactly the semantics of the definite iteration
pattern, but there is a small technical point: The scope of the declaration of i extends
only as far as the for-statement’s body—the statements following the for-statement
cannot examine i’s value. If it is important that i’s value be available after the loop
terminates, then an extra declaration must be prefixed to the loop:
int i;
for ( i = INITIAL VALUE; TEST ON i; INCREMENT i; )
{ EXECUTE LOOP BODY; }
// because i is declared before the loop starts, i’s value can be read here
Here is the for-loop that corresponds to the while-loop we saw at the beginning
of this Chapter, which prints the decimal values of the reciprocals, 1/2 to 1/20:
for ( int denominator = 2; denominator <= 20; denominator = denominator+1 )
// invariant: 1/2, ...up to..., 1/(denominator-1) printed so far
{ System.out.println("1/" + denominator + " = " + (1.0 / denominator)); }
7.7. NESTED LOOPS
329
Compare this to the original while-loop:
int denominator = 2;
while ( denominator <= 20 )
{ System.out.println("1/" + denominator + " = " + (1.0 / denominator));
denominator = denominator + 1;
}
There are two advantages in using the for-statement for definite iteration:
• The for-statement is more concise than the while-statement, because it displays
the initialization, test, and increment of the loop counter all on one line.
• The for-statement suggests to the reader that the loop is a definite iteration.
Begin Footnote: Java’s for-statement can be used to compute an indefinite iteration (by omitting the INCREMENT i part), but we do not do this in this text. End
Footnote
The for-statement is particularly useful for systematically computing upon all the
items in a set; here is a loop that easily prints all the characters in a string s, one per
line, in reverse order:
for ( int i = s.length() - 1; i >= 0; i = i - 1 )
// invariant: printed characters at s.length()-1 ...downto... (i+1)
{ System.out.println(s.charAt(i)); }
Exercises
1. Rewrite the computeAverage method in Figure 1 so that it uses a for-statement.
2. Use a for-statement to write a function, reverse, that receives a string parameter, s, and returns as its result the string that looks like s reversed, e.g.,
reverse("abcd") returns "dcba".
3. Rewrite the method in Figure 2 to use a for-statement.
4. Rewrite into for-loops the while-loops you wrote in the answers to the Exercises
for the “Definite Iteration” Section
7.7
Nested Loops
A loop may be placed within the body of another loop; a nested loop is the result.
Nested loops are the natural solution to problems that require a “table” of answers,
so we begin with two examples that build tables.
330
Computing a Multiplication Table
We might wish to generate a 4-by-5 table of all the mutiplications of 0..3 by 0..4. The
output might appear like this:
To do this, we must generate all possible combinations of i * j, when i varies in the
range 0..3 and i varies in the range 0..4. The algorithm for printing the multiplications
might go:
1. for i varying from 0 to 3, do the following: print i * 0, i * 1, ..., i * 4.
The “for” keyword in the algorithm suggests that a for-statement is the best way to
write the required loop. Similarly, printing the multiplications, i * 0, i * 1, ... i *
4, can be done by varying the second operand over the range, 0..4:
1. for i varying from 0 to 3, do the following:
• for j varying from 0 to 4, do the following: print i * j
The completed algorithm uses its outer loop to count and print the rows of multiplications and uses its inner loop to print the individual multiplications on each
row:
for ( int i = 0; i <= 3; i = i +
{ // invariant: printed 0*x
for ( int j = 0; j <= 4; j
// invariant: printed
{ System.out.print(i +
System.out.println();
}
1 )
up to (i-1)*x, for all values x
= j + 1 )
i*0 up to i*(j-1)
"*" + j + "=" + (i * j) + " "); }
Note the uses of print and println to format the rows of the table.
in 0..4
331
7.7. NESTED LOOPS
Drawing a Chessboard
How might we paint an n-by-n chessboard in a graphics window?
This task is also a table-printing problem, where the “values” in the table are red
and white squares. To understand which squares should be red and which should be
white, consider this numbering scheme for the squares, which was suggested by the
multiplication table seen earlier:
0, 0
1, 0
...
n-1, 0
0, 1
1, 1
0, 2
1, 2
...
...
0, n-1
1, n-1
n-1, 1
n-1, 2
...
n-1, n-1
Assuming that the board’s upper-left corner (the one numbered by 0,0) is a red square,
then it is easy to calculate the color of every square: If the square’s number is i,j,
then the square is red when i + j is even-valued; otherwise, it is white (when i + j
is odd-valued).
We write the algorithm for painting the board by imitating the algorithm for
printing the multiplication table:
1. for i varying from 0 to n-1, do the following:
• for j varying from 0 to n-1, do the following: paint the square numbered
i, j: If i + j is even-valued, paint a red square; otherwise, paint a white
square.
332
Figure 7.7: method to paint a chessboard
/** paintBoard paints an n-by-n chessboard of red and white squares
* @param start x - position of the upper left corner of the board
* @param start y - position of the upper left corner of the board
* @param total rows - the number of rows of the board
* @param square size - the width of each square, in pixels
* @param g - the graphics pen */
private void paintBoard(int start x, int start y,
int total rows, int square size, Graphics g)
{ for ( int x = 0; x < total rows; x = x + 1 )
// invariant: have painted x rows so far
{ // calculate position of row x:
int x position = start x + (x * square size);
for ( int y = 0; y < total rows; y = y + 1 )
// invariant: have painted y squares of row x
{ // calculate position of the y-th square:
int y position = start y + (y * square size);
if ( ((x + y) % 2) == 0 ) // is square x,y a red one?
{ g.setColor(Color.red); }
else { g.setColor(Color.white); }
g.fillRect(x position, y position, square size, square size);
}
}
}
We write the method so that a chessboard can be painted at whatever position,
number of rows, and size a user desires. Figure 7 shows the method, which can be
invoked by a panel’s paintComponent method, as we saw in the bulls-eye-painting
example earlier in this chapter in Figures 2 and 3.
Alphabetizing a String
When a set of items must be arranged or reordered, nested loops often give a solution. Given a string, s, say that we must construct a new string that has all of s’s
characters but rearranged in alphabetical order. For example, if s is butterfly, then
its alphabetized form is beflrttuy.
The algorithm for alphabetization will copy the characters, one by one, from string
s into a new, alphabetized string, which we call answer. Here is an initial algorithm:
1. Set answer = ""
2. Working from left to right, for each character in s, copy the character into its
proper position in answer.
7.7. NESTED LOOPS
333
The use of “for” in Step 2 suggests that a for-statement might be used to examine
and extract the characters in s, say, as s.charAt(0), s.charAt(1), and so on:
answer = "";
for ( int i = 0; i != s.length(); i = i + 1 )
// invariant: characters at indices 0..i-1 have been inserted into
{ insertAlphabetically(s.charAt(i), answer); }
answer
The body of the loop contains a helper method, insertAlphabetically, which will
place its first argument, the character, into the correct position within its second
argument, answer, so that answer remains alphabetized.
What is the algorithm for insertAlphabetically? Let’s consider the general
probem of inserting a character, c, into its proper position in an alphabetized string,
alpha. This is in fact a searching problem, where we search alpha from left to right
until we find a character that appears later in the alphabet than does c. We adapt
the searching pattern for loops seen earlier in this Chapter:
1. Set index = 0 (this variable is used to look at individual characters within
alpha), and set searching for c position = true.
2. While ( searching for c position and there are still characters in alpha to
examine ), do the following
• If c is less-or-equals to alpha.charAt(index), then we have found the correct position for inserting c, so set searching for c position = false;
• Else, increment index (and keep searching).
3. Insert c into alpha in front of the character at position index within alpha.
Figure 8 shows the final versions of the two methods developed for generating the
complete, alphabetized string. The public method, alphabetize, uses a for-statement
to systematically copy each character in the orginal string into the alphabetized string.
Helper method insertAlphabetically uses a searching loop to find the correct
position to insert each character into the alphabetized string. When the searching
loop finishes, local variable index marks the position where the character should be
inserted. The insertion is done by the last statement within insertAlphabetically,
which makes clever use of the substring method:
return
alpha.substring(0, index) + c + alpha.substring(index, alpha.length());
That is, alpha.substring(0, index) is the front half of string alpha, from character
0 up to character index, and alpha.substring(index, alpha.length()) is the back
half of alpha, from character index to the end.
Note also within insertAlphabetically that the <= operation is used to compare
two characters—for example, ’a’ <= ’b’ computes to true but ’b’ <= ’a’ is false.
334
Figure 7.8: methods for alphabetizing a string
/** alphabetize computes a string that has the same characters as
* its argument but arranged in alphabetical order
* @param s - the input string
* @return the alphabetized string */
public String alphabetize(String s)
{ String answer = "";
for ( int i = 0; i != s.length(); i = i + 1 )
// update the answer by inserting s.charAt(i) into it:
{ answer = insertAlphabetically(s.charAt(i), answer); }
return answer;
}
/** insertAlphabetically inserts c into alpha, preserving
* alphabetical ordering. */
private String insertAlphabetically(char c, String alpha)
{ int index = 0; // the position where we will possibly insert c
boolean searching for c position = true;
while ( searching for c position && index < alpha.length() )
{ if ( c <= alpha.charAt(index) ) // should c be placed at
{ searching for c position = false; }
else { index = index + 1; }
}
// ‘‘break’’ alpha into two pieces and place c in the middle:
return alpha.substring(0, index)
+ c + alpha.substring(index, alpha.length());
}
index?
Alphabetization is a special case of sorting, where a collection of items are arranged
ordered according to some standardized ordering. (For example, a dictionary is a
collection of words, sorted alphabetically, and a library is a collection of books, sorted
by the catalog numbers stamped on the books’ spines.)
Exercises
1. Write nested loops that generate the addition table for 0+0 up to 5+5.
2. Write this method:
/** removeDuplicateLetters constructs a string that contains the
* same letters as its argument except that all duplicate letters
* are removed, e.g., for argument, "butterflies", the result is
* "buterflis"
* @param s - the argument string
335
7.8. WRITING AND TESTING LOOPS
* @return a string that looks like s but with no duplicates
public String removeDuplicateLetters(String s)
*/
3. Write nested loops that print this pattern:
0
1
2
3
0
0
0
0
1 1
2 1
3 1
2 2
3 2
3 3
4. Write nested loops that print this pattern:
0
1
2
3
7.8
3
3
3
3
0 2
1 2
2 2
0 1
0 1
0 0
Writing and Testing Loops
Loops are easily miswritten. For example, one must not inadvertently type an extra
semicolon—this example,
int i = 2;
while ( i != 0 );
{ i = i - 1; }
fails to terminate because the semicolon after the test causes the Java compiler to read
the code as while ( i != 0 ) {}; { i = i - 1; }—the loop has an empty body.
Problems also arise when a loop’s test is carelessly formulated. For example, this
attempt to compute the sum, 1 + 2 + ... + n, fails,
int total = 0;
int i = 0;
while ( i <= n )
{ i = i + 1;
total = total + i;
}
because the loop iterates one time too many. This form of error, where a loop iterates
one time too many or one time too few, is commonly made, so be alert for it when
testing the loops you write.
A related problem is an improper starting value for the loop counter, e.g.,
336
int total = 0;
int i = 1;
while ( i <= n )
{ i = i + 1;
total = total + i;
}
This loop again attempts to compute the summation, 1 + 2 + ... + n, but it
forgets to add the starting value of i into its total.
Examples like these show that it is not always obvious when a loop iterates exactly
the correct number of times. When you test a loop with example data, be certain to
know the correct answer so that you can compare it to the loop’s output.
Here is another technical problem with loop tests: Never code a loop’s test expression as an equality of two doubles. For example, this loop
double d = 0.0;
while ( d != 1.0 )
{ d = d + (1.0 / 13);
System.out.println(d);
}
should terminate in 13 iterations but in reality does not due to computer arithmetic,
where fractional numbers like 1/13 are imperfectly represented as fractions in decimal
format.
Formulating test cases for loops is more difficult than formulating test cases for
conditionals, because it is not enough to merely ensure that each statement in the
loop body is executed at least once by some test case. A loop encodes a potentially
infinite number of distinct execution paths, implying that an infinite number of test
cases might be needed. Obviously, no testing strategy can be this exhaustive.
In practice, one narrows loop testing to these test cases:
• Which test cases should cause the loop to terminate with zero iterations?
• Which test cases should cause the loop to terminate with exactly one iteration?
• Which “typical” test cases should cause the loop to iterate multiple times and
terminate?
• Which test cases might cause the loop to iterate forever or produce undesirable
behavior?
Test cases of all four forms are applied to the loop code.
One more time, this attempt to compute the summation 1 + 2 + ... + n,
7.8. WRITING AND TESTING LOOPS
337
int n = ... ; // the input value
int total = 0;
int i = 0;
while ( i != n )
{ total = total + i;
i = i + 1;
}
might be tested with n set to 0 (which we believe should cause immediate termination),
set to 1 (should cause termination in one iteration), set to, say, 4 (a typical case that
requires multiple iterations), and set to a negative number, say, -1, (which might cause
unwanted behavior). These test cases quickly expose that the test expression forces
termination one iteration too soon. Subtle errors arise when a loop’s test expression
stops the loop one iteration too soon or one iteration too late; testing should try to
expose these “boundary cases.”
A more powerful version of loop testing is invariant monitoring: If you wrote an
invariant for the loop, you can monitor whether the invariant is holding true while the
loop iterates. To do this, insert inside the loop one or more println statements that
display the values of the variables used by the loop. (Or, use an IDE to halt the loop
at each iteration and display the values of its variables.) Use the variables’s values to
validate that the loop’s invariant is holding true. If the invariant is remaining true,
this gives you great confidence that the loop is making proper progress towards the
correct answer.
For example, consider Figure 1, which displays a loop that sums a sequence of
exam scores. The loop’s invariant,
// at each iteration: total_points == exam_1 + exam_2 + ... + exam_count
tells us what behavior to observe at the start (and the end) of each loop iteration.
The println statement placed at the end of the loop in Figure 1 lets us verify that
the invariant property that we believed to be true is in fact holding true during the
execution.
If we monitor a loop’s invariant, like the one in Figure 1, and we find that the
invariant goes false at some interation, this is an indication that either the loop’s code
or the invariant is incorrectly written. In the former case, repair is clearly needed; in
the latter case, we do not understand what our loop should do and we must study
further.
Although loop testing can never be exhaustive, please remember that any testing
is preferable to none—the confidence that one has in one’s program increases by the
number of test cases that the program has successfully processed.
338
7.9
Case Study: Bouncing Ball Animation
Because a loop lets us repeat an action over and over, we can write a loop that paints
a graphical image over and over, changing the image slightly with each iteration—
This gives the illusion of movement. An application that displays moving objects in
a graphics window is an animation.
A simple but classic animation is a ball bouncing within a box:
(Although the printed page cannot show it, the red ball is travelling from side to side
within the frame.)
A program written in the Model-View-Controller architectural style does best:
The animation is modelled, in this case, by objects that represent the ball and box. A
view paints the model’s current state on the display. Finally a controller monitors the
passage of time and tells the model when to update the ball’s position and repaint it
on the display.
Figure 9 shows the architecture of the animation program. The controller contains
a loop that executes an iteration for each unit of time that passes. At each unit of
time, the loop’s body tells the model to update its state, and it tells the view to
repaint the model; the repeated paintings look like movement. The model and view
have methods that respond to the controller’s requests. Also, the view must ask the
model for its current state each time the model must be painted.
Recall once more the steps we take to design and solve a programming problem:
1. State the program’s behavior, from the perspective of the program’s user.
2. Select a software architecture that can implement the behavior.
3. For each of the architecture’s components, specify classes with appropriate attributes and methods.
7.9. CASE STUDY: BOUNCING BALL ANIMATION
339
Figure 7.9: architecture of a simple animation
Controller
run()
{ while (true)
{ wait one time unit;
model.updateState();
view.paintModel();
}
}
View
paintModel()
{ model.getState();
repaint();
}
Model
getState()
updateState()
4. Write and test the individual classes.
5. Integrate the classes into the architecture and test the system.
Let’s move through these steps:
Behaviors and Architecture
Steps (1) and (2) are not a challenge for building the moving-ball animation: The
behavior of the animation has already been indicated—there is nothing for the user
to do but watch—and the architecture in Figure 9 is a good start towards implementing the desired behavior. The architecture shows that the application has distinct
model, view, and controller subassemblies, so we develop each subassembly separately,
beginning with the model.
Specifying the Model Subassembly
We begin with the model subassembly of the animation—What are the model’s components? They are of course the ball and the box in which the ball moves. Our first
attempt at specifying the model might place ball and box together in one class, like
this:
340
class BallAndBox
maintains a ball that moves within a box
Attributes
the box’s size, the ball’s size,
the ball’s position, the ball’s velocity
Methods
moveTheBall(int time units)
getTheState()
Updates the model’s state by moving the ball a
distance based on its current position, its velocity, and the amount of time, time units, that
have passed since the last time the state has
been updated.
Returns the current position of the ball, its size,
and the dimensions of the box.
This initial specification is a bit problematic: The attributes of the ball and the
box are mixed together, and this will cause trouble if we later modify the animation,
say by using two balls or using a different form of container. The specification of the
getTheState also has some confusion about what exactly is the state of the model.
These concerns motivate us to design the model as two classes—one class for the
ball and one for the box.
Let’s consider the ball’s class first—what are its attributes and what are its methods? Of course, a ball has a physical size and location (x- and y-coordinates). Because
it is moving in two-dimensional space, it has a velocity in both x- and y-coordinates.
The primary method the ball requires is the ability to move on request. This produces the specification of class MovingBall that appears in Table 10. In addition
to its mutator method, move, the final version of class MovingBall will also possess
accessor methods that return the ball’s radius, position, and so on.
The description of the move method in Figure 10 makes clear that the moving ball
must send messages to the box in which it is contained, to learn if it has hit a well and
must change its direction. This motivates us to design class Box. A box’s attributes
are just the positions of its walls, and assuming that the box is shaped as a square, it
suffices to remember just the box’s width. A box does not actively participate in the
moving-ball animation, but it must have methods that reply when asked if a given
position is in contact with a wall.
Table 11 shows one way to specify class Box.
At this point, the design of the model is mostly complete. Refinements might be
made as the programming problem is better understood, but the specifications are a
good starting point for developing the remainder of the program.
Implementing and Testing the Model
Now, we can write the classes. As usual, class Ball has accessor methods that yield
the values of its attributes. The most interesting method is move, which updates the
7.9. CASE STUDY: BOUNCING BALL ANIMATION
341
Figure 7.10: specification of moving ball
models a ball that moves in two-dimensional space
class MovingBall
Attributes
int x pos, int y pos
radius
int x velocity, int
y velocity
Method
move(int time units)
the center coordinates of the ball’s location
the ball’s radius (size)
the ball’s horizontal and vertical velocities
moves to its new position based on its velocity the
the elapsed amount of time units, reversing direction if it comes into contact with a wall of the container (the Box) that holds it.
Figure 7.11: specification of box
class Box
Attribute
int BOX SIZE
Methods
inHorizontalContact(int
x position): boolean
inVerticalContact(int
y position): boolean
models a square box
the width of the square box
responds whether x position (a horizontal coordinate) is in contact with one of the Box’s (leftmost
or rightmost) walls
responds whether y position (a vertical coordinate) is in contact with one of the Box’s (upper
or lower) walls
ball’s state; its algorithm goes
1. Move the ball to its new position.
2. Ask the box that contains the ball whether the ball has come into contact
with one of the box’s horizontal or vertical walls; if it has, then change the
ball’s direction. (For example, if the ball makes contact with a horizontal wall,
then its horizontal direction must reverse; this is done by negating the ball’s
horizontal velocity.)
Figure 12 shows the coding of class Ball’s move method.
The coding of class Box is simple, because the box’s state is fixed when the box
is constructed, so the box’s methods merely return properties related to the positions
of the box’s walls. See Figure 13.
342
Figure 7.12: model of moving ball
/** MovingBall models a moving ball */
public class MovingBall
{ private int x pos; // ball’s center x-position
private int y pos; // ball’s center y-position
private int radius; // ball’s radius
private int x velocity = +5; // horizonal speed; positive is to the right
private int y velocity = +2; // vertical speed; positive is downwards
private Box container;
// the container in which the ball travels
/** Constructor MovingBall constructs the ball.
* @param x initial - the center of the ball’s starting horizontal position
* @param y initial - the center of the ball’s starting vertical position
* @param r - the ball’s radius
* @param box - the container in which the ball travels */
public MovingBall(int x initial, int y initial, int r, Box box)
{ x pos = x initial;
y pos = y initial;
radius = r;
container = box;
}
/** xPosition returns the ball’s current horizontal position */
public int xPosition()
{ return x pos; }
/** yPosition returns the ball’s current vertical position */
public int yPosition()
{ return y pos; }
/** radiusOf returns the ball’s radius */
public int radiusOf()
{ return radius; }
/** move moves the ball
* @param time units - the amount of time since the ball last moved */
public void move(int time units)
{ x pos = x pos + (x velocity * time units);
if ( container.inHorizontalContact(x pos) )
{ x velocity = -x velocity; }
// reverse horizontal direction
y pos = y pos + (y velocity * time units);
if ( container.inVerticalContact(y pos) )
{ y velocity = -y velocity; }
// reverse vertical direction
}
}
7.9. CASE STUDY: BOUNCING BALL ANIMATION
343
Figure 7.13: model of a box
/** Box models a box in which moving objects live */
public class Box
{ private int box size; // the size of the box
/** Constructor Box builds the box
* @param size - the size of the box */
public Box(int size)
{ box size = size; }
/** inHorizontalContact replies whether a position has contacted a
* horizontal wall.
* @param x position - the position examined
* @return true, if x position equals or exceeds the positions of the
*
horizontal walls; return false, otherwise */
public boolean inHorizontalContact(int x position)
{ return (x position <= 0 ) || (x position >= box size); }
/** inVerticalContact replies whether a position has contacted a
* vertical wall.
* @param y position - the position examined
* @return true, if y position equals or exceeds the positions of the
*
vertical walls; return false, otherwise */
public boolean inVerticalContact(int y position)
{ return (y position <= 0 ) || (y position >= box size); }
/** sizeOf returns the size of the box */
public int sizeOf()
{ return box size; }
}
Because class MovingBall depends on class Box, we test the classes together,
say, by writing a testing program that constructs a box and places the ball in the
middle of the box. Then, we tell the ball to move and we print its new position. Of
course, we should move the ball until it comes into contact with a wall so that we
can see how the ball’s direction changes as a result. Here is some testing code:
Box box = new Box(50);
// 50 pixels by 50 pixels in size
MovingBall ball = new MovingBall(25, 25, 10, box); // radius is 10 pixels
while ( true )
{ ball.move(1);
// 1 unit of elapsed time
System.out.println("x = " + ball.xPosition()
+ "; y = " + ball.yPosition());
}
344
Specifying and Implementing the View Subassembly
The ball and box become more interesting once we write a view to paint them. Since
the model is designed in two components, we propose a view class that paints the box
and a view class that paints the ball. This will make it easier to extend the animation
later to contain multiple moving balls, and it suggests the pleasant idea that each
model component should have its own view component. Here is a diagram of how the
view classes might be specified:
AnimationWriter
MovingBall
(see Table 10)
BallWriter
paint(Graphics g)
Box
(see Table 11)
BoxWriter
paint(Graphics g)
paintComponent(Graphics g)
{ box.paint(g);
ball.paint(g);
}
When the animation must be painted, an AnimationWriter is asked to paint the entire
picture on a panel. This class asks the BoxWriter to paint the box and it asks the
BallWriter to paint a ball. The latter two classes query the accessor methods of their
respective model components for the state of the box and ball.
There is little more to specify about the view classes, so we study their codings.
Figure 14 presents class AnimationWriter; notice how this class gives its graphics
pen to class BoxWriter and then to class BallWriter, presented in Figure 15, so
that the box and ball are painted on the window with which the graphics pen is
associated.
We can test the view classes with the model we have already built and tested.
Implementing the Controller
Finally, the program’s controller uses a loop to control the progress of the simulation;
its algorithm is
1. loop forever, doing the following steps:
• Tell the ball to move one time unit.
• Tell the writer to repaint the state of the animation on the display
Figure 16 gives the coding of this loop and also the coding of the start-up class that
contructs all the animation’s objects.
The while-loop in the run method of class BounceController is intended not to
terminate, hence its test is merely true. To present an illusion of movement, the same
7.9. CASE STUDY: BOUNCING BALL ANIMATION
Figure 7.14: view class for moving-ball simulation
import java.awt.*;
import javax.swing.*;
/** AnimationWriter displays a box with a ball in it. */
public class AnimationWriter extends JPanel
// the output-view of the box
{ private BoxWriter box writer;
private BallWriter ball writer; // the output-view of the ball in the box
/** Constructor AnimationWriter constructs the view of box and ball
* @param b - the box’s writer
* @param l - the ball’s writer
* @param size - the frame’s size */
public AnimationWriter(BoxWriter b, BallWriter l, int size)
{ box writer = b;
ball writer = l;
JFrame my frame = new JFrame();
my frame.getContentPane().add(this);
my frame.setTitle("Bounce");
my frame.setSize(size, size);
my frame.setVisible(true);
}
/** paintComponent paints the box and ball
* @param g - the graphics pen */
public void paintComponent(Graphics g)
{ box writer.paint(g);
ball writer.paint(g);
}
}
345
346
Figure 7.15: view classes for box and ball
import java.awt.*;
/** BoxWriter displays a box */
public class BoxWriter
{ private Box box;
// the (address of the) box object that is displayed
/** Constructor BoxWriter displays the box
* @param b - the box that is displayed */
public BoxWriter(Box b)
{ box = b; }
/** paint paints the box
* @param g - the graphics pen used to paint the box */
public void paint(Graphics g)
{ int size = box.sizeOf();
g.setColor(Color.white);
g.fillRect(0, 0, size, size);
g.setColor(Color.black);
g.drawRect(0, 0, size, size);
}
}
import java.awt.*;
/** BallWriter displays a moving ball */
public class BallWriter
{ private MovingBall ball;
// the (address of the) ball object displayed
private Color balls color; // the ball’s color
/** Constructor BallWriter
* @param x - the ball to be displayed
* @param c - its color */
public BallWriter(MovingBall x, Color c)
{ ball = x;
balls color = c;
}
/** paint paints the ball on the view
* @param g - the graphics pen used to paint the ball */
public void paint(Graphics g)
{ g.setColor(balls color);
int radius = ball.radiusOf();
g.fillOval(ball.xPosition() - radius,
ball.yPosition() - radius, radius * 2, radius * 2);
}
}
7.10. RECURSION
347
technique from motion pictures is used: Method delay pauses the program slightly
(here, 20 milliseconds, but this should be adjusted to look realistic for the processor’s
speed) before advancing the ball one more step. The method uses a built-in method,
Thread.sleep, which must be enclosed by an exception handler. These details are
unimportant, and the delay method may be copied and used whereever needed.
The moving-ball animation is a good start towards building more complex animations, such as a billiard table or a pinball machine. Indeed, here is a practical tip: If
you are building a complex animation, it helps to build a simplistic first version, like
the one here, that implements only some of the program’s basic behaviors. Once the
first version performs correctly, then add the remaining features one by one.
For example, if our goal is indeed to build an animated pinball machine, we should
design the complete pinball machine but then design and code an initial version that
is nothing more than an empty machine (a box) with a moving pinball inside it. Once
the prototype operates correctly, then add to the machine one or more of its internal
“bumpers” (obstacles that the ball hits to score points). Once the pinball correctly
hits and deflects from the bumpers, then add the scoring mechanism and other parts.
Each feature you add to the animation causes you to implement more and more of
the attributes and methods of the components. By adding features one by one, you
will not be overwhelmed by the overall complexity of the animation. The Exercises
that follow give a small example of this approach.
Exercises
1. Execute the moving ball animation. Occasionally, you will observe that the
ball appears to bounce off a wall “too late,” that is, the ball changes direction
after it intersects the wall. Find the source of this problem and suggest ways to
improve the animation.
2. Revise the moving-ball animation so that there are two balls in the box. (Do
not worry about collisions of the two balls.)
3. If you completed the previous exercise, rebuild the animation so that a collision of the two balls causes both balls to reverse their horizontal and vertical
directions.
4. Place a small barrier in the center of the box so that the ball(s) must bounce
off the barrier.
7.10
Recursion
No doubt, you have seen a “recursive picture,” that is, a picture that contains a
smaller copy of itself that contains a smaller copy of itself that contains.... (You can
348
Figure 7.16: controller and start-up class for moving-ball animation
/** BounceController controls a moving ball within a box.
public class BounceController
{ private MovingBall ball; // model object
private AnimationWriter writer; // output-view object
*/
/** Constructor BounceController initializes the controller
* @param b - the model object
* @param w - the output-view object */
public BounceController(MovingBall b, AnimationWriter w)
{ ball = b;
writer = w;
}
/** runAnimation runs the animation by means of an internal clock */
public void runAnimation()
// time unit for each step of the animation
{ int time unit = 1;
int painting delay = 20; // how long to delay between repaintings
while ( true )
{ delay(painting delay);
ball.move(time unit);
System.out.println(ball.xPosition() + ", " + ball.yPosition());
writer.repaint(); // redisplay box and ball
}
}
/** delay pauses execution for how long
private void delay(int how long)
{ try { Thread.sleep(how long); }
catch (InterruptedException e) { }
}
}
milliseconds */
7.10. RECURSION
349
Figure 7.16: start-up class for moving-ball animation (concl.)
import java.awt.*;
/** BounceTheBall constructs and starts the objects in the animation. */
public class BounceTheBall
{ public static void main(String[] args)
{ // construct the model objects:
int box size = 200;
int balls radius = 6;
Box box = new Box(box size);
// place the ball not quite in the box’s center; about 3/5 position:
MovingBall ball = new MovingBall((int)(box size / 3.0),
(int)(box size / 5.0),
balls radius, box);
BallWriter ball writer = new BallWriter(ball, Color.red);
BoxWriter box writer = new BoxWriter(box);
AnimationWriter writer
= new AnimationWriter(box writer, ball writer, box size);
// construct the controller and start it:
new BounceController(ball, writer).runAnimation();
}
}
simulate this by hanging two large mirrors on opposite walls, standing in the middle,
and looking at one of the mirrors.) It is self-reference that characterizes recursion,
and when a method contains a statement that invokes (restarts) itself, we say that
the method is recursively defined.
In the bank-accounting example in Chapter 6. we used recursion to restart a
method, but now we study a more sophisticated use of recursion, where a complex
problem is solved by solving simpler instances of the same problem.
Here is an informal example of recursive problem solving: How do you make a
three-layer cake? A curt answer is, “make a two-layer cake and top it with one more
layer!” In one sense, this answer begs the question, because it requires that we know
already how to make a cake in order to make a cake, but in another sense, the answer
says we should learn how to make first a simpler version of the cake and use the result
to solve the more complex problem.
If we take seriously the latter philosophy, we might write this recipe for making a
cake with some nonzero number of layers, say, N+1:
To make an (N+1)-layer cake, make an N-layer cake and top it with one more layer.
The reciple simplifies (N+1)-layer cake making into the problem of N-layer cake making.
But something is missing—where do we begin cake making? The answer is startlingly
simple:
350
To make a zero-layer cake, place an empty cake pan on the table.
The solution to the original problem becomes clearer—to make a three-layer cake,
we must first make a two layer cake (and then top it with one more layer); but to
make a two-layer cake, we must first make a one-layer cake (and then top it with one
more layer); but to make a one-layer cake, we must make a zero-layer cake (and then
top it with one more layer); and we make a “zero-layer cake” from just an empty
pan. Perhaps this explanation is a bit wordy, but all the steps are listed. A diagram
displays the steps more pleasantly:
make a 3-layer cake
1
8
make a 2-layer cake
and top it with one more layer
2
7
make a 1-layer cake
and top it with one more layer
3
6
make a 0-layer cake
and top it with one more layer
4
5
place an empty cake pan on the table
By following the arrows, we understand how the problem of making a multi-layer case
is decomposed into simpler problems (see the downwards arrows) and the result is
assembled from from the solutions—the pan and layers (see the upwards arrows).
The strategy of solving a task by first solving a simpler version of the task and
building on the simpler solution is called problem solving by recursion or just recursion,
for short. When we solve a problem by recursion, we can approach it as a task of
writing simultaneous algebraic equations, like these, for cake making:
bakeACake(0) = ...
bakeACake(N + 1) = ... bakeACake(N) ...,
where N is nonnegative
The first equation states how a 0-layer cake is made, and the second explains how an
N+1-layer cake is made in terms of making an N-layer one. Based on the narrative for
cake making, we would finish the equations like this:
bakeACake(0) = place an empty pan on the table
bakeACake(N + 1) = bakeACake(N) and top it with one more layer
The algorithm can be used to generate the steps for a 3-layer cake:
bakeACake(3)
=> bakeACake(2)
and top it with one more layer
=> (bakeACake(1)
and top it with one more layer)
351
7.10. RECURSION
Figure 7.17: class Cake
/** class Cake is a simple model of a multi-layer cake
public class Cake
{ int how many layers;
*/
/** Constructor Cake constructs a 0-layer cake---a pan */
public Cake()
{ how many layers = 0; }
/** addALayer makes the Cake one layer taller */
public void addALayer()
{ how many layers = how many layers + 1; }
/** displayTheCake prints a representation of the cake */
public void displayTheCake()
{ ... left as a project for you to do ... }
}
and top it with one more layer
=> ((bakeACake(0)
and top it with one more layer))
and top it with one more layer)
and top it with one more layer
=> ((place an empty pan on the table
and top it with one more layer))
and top it with one more layer)
and top it with one more layer
A recursive algorithm can be reformatted into a Java method that uses recursion.
Perhaps there is a Java data type, named Cake, such that new Cake() constructs a
0-layer cake. Perhaps Cake objects have a method, named addALayer, which places
one more layer on a cake. For fun, a version of class Cake is displayed in Figure 17.
Now, we can use class Cake to reformat the two algebraic equations for cake
making into the Java method displayed in Figure 18.
Now, the assignment,
Cake c = bakeACake(0);
constructs a 0-layer cake. More interesting is
Cake c = bakeACake(3);
because this triggers bakeACake(2), which invokes bakeACake(1), which starts bakeACake(0).
The last invocation constructs a new cake object, which is then topped by three layers
352
Figure 7.18: a recursive method for cake making
/** bakeACake makes a multi-layer cake
* @param layers - the number of the cake’s layers
* @return the cake */
public Cake bakeACake(int layers)
{ Cake result;
if ( layers == 0 )
// then the first equation applies:
// bakeACake(0) = place an empty pan on the table
{ result = new Cake(); }
else // the second equation applies:
// bakeACake(N + 1) = bakeACake(N) and top it with one more layer
{ result = bakeACake(layers - 1);
result.addALayer();
}
return result;
}
and eventually assigned to variable c. To understand the process, we should study
an execution trace.
7.10.1
An Execution Trace of Recursion
We now study how the method in Figure 12 contructs its answer by recursion in an
example invocation of bakeACake(3).
The invocation causes 3 to bind to the the formal parameter, layers; it and the
local variable, result, are created:
bakeACake
{ int layers == 3
}
Cake result == ?
> if ( layers == 0 )
{ result = new Cake(); }
else { result = bakeACake(layers - 1);
result.addALayer();
}
return result;
The test marked by >1??? computes to false, meaning that the next step is a
353
7.10. RECURSION
recursive invocation:
bakeACake
Cake result == ?
{ int layers == 3
. . .
else { > result = bakeACake(layers - 1);
result.addALayer();
}
return result;
}
The invocation causes a fresh, distinct activation record of bakeACake to be executed:
bakeACake
Cake result == ?
{ int layers == 3
. . .
else { > result = AWAIT RESULT
result.addALayer();
}
return
bakeACake
}
Cake result == ?
{ int layers == 2
}
> if ( layers == 0 )
{ result = new Cake(); }
else { result = bakeACake(layers - 1);
result.addALayer();
}
return result;
This shows that a recursive method invocation works like any other invocation: actual
parameters are bound to formal parameters, and a fresh copy of the method’s body
executes.
Further execution causes two more copies of bakeACake to be invoked, producing
354
this configuration:
bakeACake
{ int layers ==
Cake result == ?
3
bakeACake
{ int layers ==
2
Cake result == ?
bakeACake
{ int layers ==
1
Cake result == ?
bakeACake
{ int layers == 0
}
Cake result == ?
> if ( layers == 0 )
{ result = new Cake(); }
else { result = bakeACake(layers - 1);
result.addALayer();
}
return result;
Because the conditional’s test evaluates to true, the conditional’s then-arm constructs
a new Cake object and places its address, a1, in the local variable, result.
a1 : Cake
how many layers == 0
...
bakeACake
{ int layers ==
3
Cake result == ?
bakeACake
{ int layers ==
2
Cake result == ?
bakeACake
{ int layers ==
1
Cake result == ?
bakeACake
{ int layers == 0
. . .
> return result;
}
Cake result == a1
355
7.10. RECURSION
The 0-layer cake is returned as the result, and the activation record for the completed
invocation disappears.
a1 : Cake
how many layers == 0
...
bakeACake
{ int layers ==
Cake result == ?
3
bakeACake
{ int layers ==
Cake result == ?
2
bakeACake
Cake result == a1
{ int layers == 1
. . .
> result.addALayer();
}
return result;
}
Next, result.addALayer() adds a layer to the cake, and the one-layer cake is returned
as the result:
a1 : Cake
how many layers == 1
...
bakeACake
{ int layers ==
3
Cake result == ?
bakeACake
Cake result == a1
{ int layers == 2
. . .
> result.addALayer();
}
return result;
}
This process repeats until the result variable at the initial invocation receives the
cake object at address a1 and places the third layer on it.
356
The example shows how each recursive invocation decreases the actual parameter
by 1, halting when the parameter has 0. When an invoked method returns its result,
the result is used by the caller to build its result. This matches our intuition that
recursion is best used to solve a problem in terms of solving a simpler or smaller one.
Admittedly, the cake-making example is contrived, and perhaps you have already
noticed that, given class Cake, we can “make” an N-layer cake with this for-loop:
Cake c = new Cake();
for ( int i = 1; i != N;
{ c.addALayer(); }
i = i + 1 )
But the style of thinking used when writing the for-loop is different than the style
used when writing the recursively defined method.
The loop in the previous paragraph should remind you that an incorrectly written
repetitive computation might not terminate—this holds true for loops and it holds
true for recursively defined methods as well. It is not an accident that the cake-making
example used recursive invocations with an argument that counted downward from
an nonnegative integer, N, to 0. As a rule:
A recursively defined method should use a parameter whose value decreases at each
recursive invocation until the parameter obtains a value that stops the recursive
invocations.
The “counting-down” pattern suggested above will protect us from making the foolish
mistake of writing a recursively defined method that restarts itself forever.
7.11
Counting with Recursion
Many computing problems that require counting can be solved by recursion. One
classic example is permutation counting: Say that we want to calculate the total
number of permutations (orderings) of n distinct items. For example, the permutations
of the three letters a, b, and c are abc, bac, bca, acb, cab, and cba—there. are six
permutations.
Permutation generation has tremendous importance to planning and scheduling
problems, for example:
• You must visit three cities, a, b, and c, next week. What are the possible orders
in which you can visit the cities, and which of these orders produces the shortest
or least-expensive trip?
• You must complete three courses to finish your college degree; what are the
possible orders in which the courses might be taken, and which ordering allows
you to apply material learned in an earlier course to the courses that follow?
7.11. COUNTING WITH RECURSION
357
• Three courses that are taught at the same hour must be scheduled in three
classrooms. What are the possible assignments of courses to classrooms, and
which assignments will accommodate all students who wish to sit the courses?
• A printer must print three files; what are the orders in which the files can be
printed, and which orderings ensure that the shorter files print before the longer
files?
It is valuable for us to know how to generate and count permutations. To study
this problem, say that we have n distinct letters, and we want to count all the permutations of the letters. The following observation holds the secret to the solution:
By magic, say that we already know the quantity of permutations of n-1 distinct
letters—say there are m of them, that is, there are distinct words, word 1, word
2, ..., word m, where each word is n-1 letters in length.
Next, given the very last, nth letter, we ask, “how many permutations can we
make from the m words using one more letter?” The answer is, we can insert the
new letter it into all possible positions in each of the m words: We take word
1, which has length n-1, and we insert the new letter in all n possible positions
in word 1. This generates n distinct permutations of length n. We do the same
for word 2, giving us n more permutations. If we do this for all m words, we
generate m sets of n permutations each, that is, a total of n * m permutations
of length n. This is the answer.
Finally, we note that when we start with an alphabet of one single letter, there is
exactly one permutation.
The above insights tell us how to count the quantity of permutations:
For an alphabet of just one letter, there is just one permutation:
number_of_permutations_of(1) = 1
If the alphabet has n+1 letters, we count the permutations made from n letters, and
we use the technique described above to count the permutations generated by the
addition of one more letter:
number_of_permutations_of(n+1) = (n + 1) * number_of_permutations_of(n)
These two equations define an algorithm for computing the quantity of permutations. We can use this algorithm to quickly count the permutations of 4 distinct
letters:
number_of_permutations_of(4)
= 4 * number_of_permutations_of(3)
= 4 * ( 3 * number_of_permutations_of(2) )
= 4 * ( 3 * ( 2 * number_of_permutations_of(1) ))
= 4 * 3 * 2 * 1 = 24
358
Calculations with this algorithm quickly convince us that even small alphabets generate huge quantities of permutations.
Not surprisingly, permutation counting occurs often in probability and statistics
theory, where it is known as the factorial function. It is traditional to write the
factorial of a nonnegative integer, n, as n!, and calculate it with these equations:
0!
= 1
(n+1)!
= (n+1) * n!, when n is nonnegative
(Notice that the starting number is lowered to zero, and that 1! equals 1, just like
we determined earlier.)
It is easy to translate these two equations into a recursively defined method that
computes factorials of integers:
public int fac(int n)
{ int answer;
if ( n == 0 )
{ answer = 1; }
else { answer = n * fac(n - 1); }
return answer;
}
// 0! = 1
// (n+1)! = (n+1) * n!
If you test the function, fac, on the computer, you will see that, when the argument
to fac is greater than 13, the answer is so huge that it overflows the internal computer
representation of an integer!
BeginFootnote: Unfortunately, a Java int can hold a value only between the range
of about negative 2 billion and positive 2 billion. EndFootnote.
For this reason, we revise method fac so that it uses Java’s long version of integers. We also make check that the argument to the function is not so large that
the computed answer will overflow the internal representation of a long integer. See
Figure 19 for the implementation of the factorial function.
When you test the factorial function, say by
System.out.println("4! = " + factorial(4));
you will find that the invocation of factorial(4) generates the invocation to factorial(3),
and so on, down to factorial(0). Then, once the invoked functions starting returning their answers, a series of four multiplications are performed, which compute the
result, 24. This pattern of invocations and multiplications are displayed when we
compute with factorial’s algorithm:
4! =>
=>
=>
=>
=>
=>
4
4
4
4
4
4
*
*
*
*
*
*
3!
(3
(3
(3
(3
(3
*
*
*
*
*
2!)
(2 *
(2 *
(2 *
(2 *
1!))
(1 * 0!)))
(1 * 1)))
1)) => 4 * (3 * 2)
=>
4 * 6
=>
24
7.11. COUNTING WITH RECURSION
359
Figure 7.19: recursively defined method that computes factorial
/** factorial computes n! for n in the range 0..20.
* (note: answers for n > 20 are too large for Java to compute,
* and answers for n < 0
make no sense.)
* @param n - the input; should be in the range 0..20
* @return n!, if n in 0..20
* @throw RuntimeException, if n < 0 or n > 20 */
public long factorial(int n)
{ long answer;
if ( n < 0 || n > 20 )
{ throw new RuntimeException("factorial error: illegal input"); }
else { if ( n == 0 )
{ answer = 1; }
// 0! = 1
else { answer = n * factorial(n - 1); } // (n+1)! = (n+1) * n!
}
return answer;
}
7.11.1
Loops and Recursions
Now that we understand the reason why the factorial function gives the answers it
does, we can study its Java coding and ask if there is an alternative way to program
the function. Indeed, because the coding of factorial invokes itself once, we can
rearrange its statements into a for-loop. Here is the loop version of the function:
public long factorial(int n)
{ long answer;
if ( n < 0 || n > 20 )
{ throw new RuntimeException("factorial error: illegal input"); }
else { answer = 1;
int count = 0;
while ( count != n )
// invariant: answer == count!
{ count = count + 1;
answer = count * answer;
}
}
return answer;
}
The loop mimicks the sequence of multiplications that compute the answer. Since
the statements in the body of the loop bear no resemblance to the original recursive
algorithm, we rely on the loop’s invariant to understand why the loop computes the
correct result.
360
In practice, many counting problems are best solved with recursive techniques.
If the recursive solution has a simple form, like those seen in the cake-making and
factorial examples, then it may be possible to rewrite the recursive implementation
into a loop. But the next section shows counting problems where solutions with
multiple recursions are needed; such solutions do not always convert to loop code.
7.11.2
Counting with Multiple Recursions
Some problems simply cannot be understood and resolved without recursive techniques, and here is a counting problem that makes this point.
Perhaps we are obsessed by insects and want to know how reproductive house flies
are. Say that, in its lifetime, one house fly lays exactly two eggs. Each egg produces
a fly that itself lays exactly two eggs. If this process occurs for n generations of
egg-laying, how many flies result? (Assume that the flies never die.)
Because each fly gives birth to two more flies, which themselves give birth to even
more, the pattern of counting cannot be done by a simple iteration—we must solve
the problem by thinking about it as a counting problem that “decomposes” into two
more counting problems.
An answer to the problem is that the total number of flies produced by the original
fly is the sum of the flies produced by the first egg, plus the flies produced by the
second egg, plus the original fly. If we say that the original fly is n-generations old,
then its child flies are one generation younger—each are n-1 generations old. We have
this recursive equation:
flies_at_generation(n) = flies_at_generation(n-1) + flies_at_generation(n-1) + 1
A newly hatched fly is 0-generations old:
flies_at_generation(0) = 1
that is, the number of flies produced by a newly hatched fly is just the fly itself.
These two equations generate a Java method that contains two recursions:
public int fliesAtGeneration(int n)
{ int answer;
if ( n < 0 )
{ throw new RuntimeException("error: negative argument"); }
else { if ( n == 0 ) // is it a newly hatched fly?
{ answer = 1; }
else { int first_egg_produces = fliesAtGeneration(n - 1);
int second_egg_produces = fliesAtGeneration(n - 1);
answer = first_egg_produces + second_egg_produces + 1;
}
return answer;
}
7.11. COUNTING WITH RECURSION
361
When executing the innermost else-clause, the computer computes the first recursion
completely to its integer result before it starts the second recursive invocation. Because each recursive invocation uses an argument that is smaller than the one given
to the caller method, all the recursions will terminate.
Now that we have used recursive problem solving, we can readily see that the two
recursions in the method’s innermost else-clause can be replaced by one:
int one_egg_produces = fliesAtGeneration(n - 1);
answer = (2 * one_egg_produces) + 1;
This simplifies the method and even allows us to rewrite the method’s body into a
loop, if we so desire.
Although the fly-counting example is a bit contrived, there is a related counting
problem that is not: the Fibonacci function. The Fibonacci number of a nonnegative
integer is defined in the following way:
Fib(0) = 1
Fib(1) = 1
Fib(n) = Fib(n-1) + Fib(n-2), when n >= 2
This recursively defined algorithm was proposed in the 13th century for counting
the number of pairs of rabbits produced by one initial pair, assuming that a pair of
rabbits takes one month to mature and from then on the pair produces one more pair
of rabbits every month thereafter! Since that time, Fibonacci numbers have arisen in
surprising places in problems in biology, botany, and mathematics.
The algorithm for the Fibonacci function has an easy coding as a method that
uses two recursions in its body. It is a fascinating project to rewrite the method so
that it uses only one recursion.
Exercises
1. Write an application that prints the values of 3!, 6!, 9!, ..., 18!.
2. Remove from the factorial method the first conditional, the one that tests n <
0 || n > 20. Then, try to compute factorial(20), factorial(21), factorial(99),
and factorial(-1).
3. With a bit of thought, you can use a while-loop to program the recursive equations for factorial. Do this.
4. Implement the Fibonacci function with a method that contains two recursions,
and try your function with the actual parameters 20; 30; 35; 40. Why does the
computation go so slowly for the test cases?
362
(Incidentally, the Fibonacci function was proposed in the 13th century for counting the number of pairs of rabbits produced by one initial pair, assuming that a
pair of rabbits takes one month to mature and from then on the pair produces
one more pair of rabbits every month thereafter!)
5. Here is a recursive algorithm for computing the product of a sequence of nonnegative integers:
product(a, b) = 1, when b < a
product(a, b) = product(a, b-1) * b, when b >= a
Write a recursively defined method that computes product.
6. Even an activity as simple as the addition of two nonnegative integers can be
solved recursively in terms of just “-1” and “+1”:
add(0, b) = b
add(a, b) = add(a-1, b) + 1, when a > 0
(a) Write a recursively defined method that computes this definition.
(b) In a similar style, define multiplication, mult, of two nonnegative integers
in terms of “-1” and add.
(c) Define exponentiation, exp, of two nonnegative integers in terms of “-1”
and mult.
7. Ackermann’s function is yet another famous recursive definition:
A(0, n) = n + 1
A(m, 0) = A(m-1, 1), when m > 0
A(m, n) = A(m-1, A(m, n-1)), when m > 0 and n > 0
Write a recursively defined method based on this definition. The function is
famous because its recursions cause the values of the the second parameter
to zoom upwards while the first parameter slowly decreases, yet the function
always terminates.
8. Say that a word is sorted if all its letters are distinct and are listed in alphabetical
order, e.g., "bdez" is sorted but "ba" and "bbd" are not. Write a recursive
algorithm that calculates the number of sorted words of length n or less that
one can create from n distinct letters.
7.12. DRAWING RECURSIVE PICTURES
363
9. Solve this modified version of the fly-generation problem: Say that an “ordinary” fly lays exactly two eggs. One of the eggs produces another “ordinary”
fly, but the other egg produces a “queen” fly, which can lay, for every generation
thereafter, two eggs that hatch into ordinary flies.
Starting from one ordinary fly, how many flies are produced from it in n generations? Starting from one queen fly, how many flies are produced from it in n
generations?
(Hint: You will need one recursive algorithm that counts the flies produced
from one ordinary fly and one recursive algorithm that counts the number of
children flies produced from one queen fly.)
7.12
Drawing Recursive Pictures
Recursively defined methods are ideal for drawing beautiful recursive pictures.
Perhaps we wish to paint a picture of an egg resting in front of a picture of
another egg resting in front of a picture of another egg, ..., until the eggs recede into
nothingness:
You might give a painter a bucket of paint, a brush, and these recursive instructions:
Paint a slightly smaller egg-picture in the background and next paint a big egg in the
front!
More precisely stated, the algorithm goes as follows:
1. First, paint a completed an egg-picture, sized slightly smaller than the desired
size of the final picture. (If this makes the background eggs have zero size, then
don’t bother with it.)
364
2. Next, paint the largest egg, of the desired size, in the foreground.
3. Finally, draw a border around the largest egg and its background.
Of course, Step 1 is a recursive reference, and an argument, size, will be used to
determine how large to paint the picture. A recursive invocation will decrease size to
draw the background picture, and the drawings of the background pictures terminate
when size reaches 0.
Figure 20 shows the method, named paintPicture, that uses this algorithm.
When paintPicture draws a picture, it first sends a message to itself to paint
a background picture at 0.8 size. The regress of drawing successively smaller background pictures halts when paintPicture notices that a background picture would
have a 0-sized egg. In this way, the recursions “count down” to 0, and the quantity of
recursive invocations count how many background pictures must be drawn to fill the
entire frame. When the background pictures are drawn, from smallest to largest, the
eggs are properly positioned, smallest to largest.
Figure 21 shows an ever more clever example, where a “field of eggs” is drawn by
a method that invokes itself two times:
The work is done by method paintEggField, which paints two triangular fields of
eggs, one to the left rear and one to the right rear, of a lead egg. The recursively
defined method, paintEggField, uses parameter layer to count downwards by one
each time the rear layers of eggs are painted. Because the left rear egg field is painted
after the right rear field, the left field’s eggs rest on the top of the right field’s.
Exercises
1. Revise class RecursivePictureWriter by removing the border around each picture; next, move the eggs so that they rest in the left corner of the picture rather
than the right corner.
7.12. DRAWING RECURSIVE PICTURES
Figure 7.20: class that paints a recursively defined picture
import java.awt.*; import javax.swing.*;
/** RecursivePictureWriter displays a recursively defined picture of an
* egg in front of a picture (of eggs). */
public class RecursivePictureWriter extends JPanel
{ private double scale = 0.8; // the size of the background picture relative
//
to the foreground
private int width = 400;
// frame width and depth
private int depth = 200;
private int first egg = 150; // size of the egg in the foreground
/** Constructor RecursivePictureWriter creates the window */
public RecursivePictureWriter()
{ JFrame my frame = new JFrame();
my frame.getContentPane().add(this);
my frame.setTitle("RecursivePictureWriter");
my frame.setSize(width, depth);
my frame.setBackground(Color.white);
my frame.setVisible(true);
}
/** paintComponent paints the recursively defined picture
* @param g - the ‘‘graphics pen’’ */
public void paintComponent(Graphics g)
{ g.setColor(Color.white);
g.fillRect(0, 0, width, depth); // paint the background
paintPicture(width, depth, first egg, g);
}
/** paintPicture paints a picture of an egg in front of a picture of eggs.
* @param right border - the right border of the picture painted
* @param bottom - the bottom border of the picture painted
* @param size - the size of the egg to be painted in the foreground
* @param g - the graphics pen */
private void paintPicture(int right border, int bottom,
int size, Graphics g)
{ // paint the background picture first:
int background size = (int)(size * scale);
if ( background size > 0 ) // is the background worth painting?
{ paintPicture((int)(right border * scale), (int)(bottom * scale),
background size, g);
}
// paint an egg in the foreground:
paintAnEgg(right border - size, bottom, size, g);
g.setColor(Color.black);
g.drawRect(0, 0, right border, bottom); // draw the border
}
...
365
366
Figure 7.20: class that paints a recursively defined picture (concl.)
/** paintAnEgg paints an egg in 2-by-3 proportion
* @param left edge - the position of the egg’s left edge
* @param bottom - the position of the egg’s bottom
* @param width - the egg’s width
* @param g - the graphics pen */
private void paintAnEgg(int left edge, int bottom, int width, Graphics g)
{ int height = (2 * width) / 3;
int top = bottom - height;
g.setColor(Color.pink);
g.fillOval(left edge, top, width, height);
g.setColor(Color.black);
g.drawOval(left edge, top, width, height);
}
public static void main(String[] args)
{ new RecursivePictureWriter(); }
}
2. Write a class that generates a circle of diameter 200 pixels, in which there is
another circle of 0.8 size of the first, in which there is another circle of 0.8 size
of the second, ..., until the circles shrink to size 0.
3. To better understand class EggFieldWriter, do the following. First, replace
method paintAnEgg with this version, which draws a transparent egg:
private void paintAnEgg(int left_edge, int bottom, int width, Graphics g)
{ int height = (2 * width) / 3;
int top = bottom - height;
g.setColor(Color.black);
g.drawOval(left_edge, top, width, height);
}
Retry EggFieldWriter.
4. Write a recursive definition, like that for the factorial and Fibonacci functions,
that defines precisely how many eggs are drawn in an egg field of n layers. How
many eggs are drawn by the output view in Figure 6?
7.13
Summary
This chapter presented concepts about repetition:
367
7.13. SUMMARY
Figure 7.21: a field of eggs
import java.awt.*; import javax.swing.*;
/** EggFieldWriter displays a binary tree depicted as a ‘‘field of eggs’’ */
public class EggFieldWriter extends JPanel
{ private int size = 20; // size for eggs
private int total layers of eggs = 7; // how many layers to paint
private int frame width = 600;
private int frame height = 200;
/** Constructor EggFieldWriter creates the window and makes it visible */
public EggFieldWriter()
{ JFrame my frame = new JFrame();
my frame.getContentPane().add(this);
my frame.setTitle("EggFieldWriter");
my frame.setSize(frame width, frame height);
my frame.setBackground(Color.white);
my frame.setVisible(true);
}
/** paintComponent fills the window with the field of eggs
* @param g - the ‘‘graphics pen’’ */
public void paintComponent(Graphics g)
{ paintEggField(220, 200, total layers of eggs, g); }
/** paintEggField paints two fields of eggs behind a lead egg
* @param left border - the left edge of the lead egg in the field
* @param baseline - the bottom of the lead egg
* @param layer - how many layers of eggs to draw
* @param g - the graphics pen */
private void paintEggField(int left border, int baseline, int layer,
Graphics g)
{ if ( layer > 0 )
{ int egg size = size * layer; // invent a size for the lead egg
// paint right sub-egg field:
paintEggField(left border + ((2 * egg size) / 3),
baseline - (egg size / 3), layer - 1, g);
// paint left sub-egg field:
paintEggField(left border - (egg size / 2),
baseline - (egg size / 3), layer - 1, g);
// paint leading egg:
paintAnEgg(left border, baseline, egg size, g);
}
}
// see Figure 20 for method paintAnEgg
}
368
New Constructions
• while-statement (from Figure 1):
int total = 0;
int count = 0;
while ( count != n )
{ System.out.println("count = " + count + "; total = " + total);
count = count + 1;
total = total + count;
}
• for-statement:
int total = 0;
for ( int count = 1; count <= n; count = count + 1 )
{ total = total + count;
System.out.println("count = " + count + "; total = " + total);
}
New Terminology
• loop: a statement (or sequence of statements) that repeats computation, such
as a while-statement
• iteration: repetitive computation performed by a loop; also refers to one execution of a loop’s body
• definite iteration: iteration where the number of the loop’s iterations is known
the moment the loop is started
• indefinite iteration: iteration that is not definite
• nontermination: iteration that never terminates; also called “infinite looping”
• invariant property: a logical (true-false) fact that holds true at the start and at
the end of each iteration of a loop; used to explain the workings of a loop and
argue the loop’s correctness
• loop counter: a variable whose value remembers the number of times a loop has
iterated; typically used in the loop’s test to terminate iteration. Also called a
loop control variable.
• recursive definition: a method that invokes itself, directly or indirectly; used to
solve a problem by recursive invocations to first solve simpler versions of the
problem and next combine the results to compute overall result.
7.13. SUMMARY
369
Points to Remember
• While-loops are used in two forms:
– definite iteration, where the number of times the loop repeats is known
when the loop is started. A standard pattern for definite iteration reads,
int count = INITIAL VALUE;
while ( TEST ON count )
{ EXECUTE LOOP BODY;
INCREMENT count;
}
where count is the loop control variable. The for-statement is used to
tersely code the above pattern:
for ( int count = INITIAL VALUE; TEST ON count; INCREMENT count; )
{ EXECUTE LOOP BODY (DO NOT ALTER count); }
– indefinite iteration, where the number of repetitions is not known. The
pattern for indefinite iteration of input processing reads
boolean processing = true; // this variable announces when it’s time to stop
while ( processing )
{ READ AN INPUT TRANSACTION;
if ( THE INPUT INDICATES THAT THE LOOP SHOULD STOP )
{ processing = false; } // reset processing to stop loop
else { PROCESS THE TRANSACTION; }
}
and the pattern for indefinite iteration for searching a “set” of items reads
boolean item_found = false;
DETERMINE THE FIRST ‘‘ITEM’’ TO EXAMINE FROM THE SET;
while ( !item_found && ITEMS REMAIN IN THE SET TO SEARCH )
{ EXAMINE ITEM FROM THE SET;
if ( THE ITEM IS THE DESIRED ONE )
{ item_found = true; }
else { DETERMINE THE NEXT ITEM TO EXAMINE FROM THE SET; }
}
• Inside the design of every loop is an invariant property, which states what the
loop is accomplishing as it progresses towards completion. When you design a
loop, you should also write what you believe is the loop’s invariant property;
this clarifies your thinking and provides excellent documentation of the loop.
• Use a recursively defined method when a problem is solved by using the solution
to a slightly simpler/smaller-valued version of the same problem. Write the
370
recursively defined method so that it uses a parameter whose value becomes
simpler/smaller each time the method invokes itself.
7.14
Programming Projects
1. Write these methods for comparing strings and insert them into an application
that lets a user ask questions about strings.
(a) /** palindrome decides whether a string is a palindrome, that is,
* the same sequence of letters spelled forwards or backwards.
* (e.g., "abcba" is a palindrome, but "abccda" is not.)
* @param s - the string to be analyzed
* @return whether s is a palindrome */
private static boolean palindrome(String s)
(b) /** permutation decides whether one string is a permutation of another,
* that is, the two strings contain exactly the same letters in the
* same quantities, but in possibly different orders.
* (e.g., "abcd" and "abdc" are permutations, but "abcd" and "aabdc" are not)
* @param s1 - the first string
* @param s2 - the second string
* @return whether s1 and s2 are permutations of each other */
private static boolean permutation(String s1, String s2)
(Note: your efforts will be eased if you consult the Java API for java.lang.String
and learn about the indexOf method for strings.)
2. Write an application that reads a series of nonnegative integers and prints the
list of all the nonzero divisors of the input integer. (Recall that b is a divisor of
a if a divided by b yields a remainder of zero.) The program terminates when
a negative integer is typed; it should behave like this:
Type a positive integer: 18
The divisors are: 2 3 6 9
Type a positive integer: 23
The divisors are: none---it’s a prime
Type a positive integer: -1
Bye!
3. Write an application that calculates the odds of a person winning the lottery
by guessing correctly all m numbers drawn from a range of 1..n. The formula
that calculates the probability is
probability = m!
/ product((n-m)+1, n)
371
7.14. PROGRAMMING PROJECTS
where m! is the factorial of m as seen earlier, and product(a,b) is the iterated
product from a up to b, which is defined as follows:
product(a, b) = a * (a+1) * (a+2) * ...
* b
(Footnote: Here is the rationale for the probability formula. Consider the task
of matching 3 chosen numbers in the range 1..10; the chances of matching on the
first number drawn at the lottery is of course 3 in 10, or 3/10, because we have
3 picks and there are 10 possible values for the first number. Assuming success
with the first draw, then our chances for matching the second number drawn are
2/9, because we have 2 numbers left and the second number is drawn from the
9 remaining. The chance of matching the third number drawn is 1/8. In total,
our chances of matching all three numbers are (3/10) * (2/9) * (1/8), which
we reformat as (1*2*3) / (8*9*10). A pattern appears: The odds of picking m
numbers out of the range 1..n are (1*2*...*m ) / ((n-m)+1 * (n-m)+2 *...*n)
which is succinctly coded as the formula presented above. End Footnote.)
4. Write an application that can translate between Arabic numbers (ordinary nonnegative integers) and Roman numerals. (Recall that Roman numeral I denotes
1, V denotes 5, X denotes 10, L denotes 50, and C denotes 100. The symbols of
a numeral should be in nonincreasing order, e.g. LXXVIII is 78, but an out-oforder symbol can be used to subtract from the symbol that follows it, e.g., XCIV
is 94.)
The application must be able to translate any Arabic numeral in the range 1
to 399 into a Roman numeral and vice versa. (Recall that no letter in a roman
number can appear more than three times consecutively, e.g., you must write
IX rather than VIIII for 9.)
5. Write an application that implements pattern matching on strings: The program’s inputs are s, a string to be searched, and p, a pattern. The program
searches for the leftmost position in s where p appears as a substring. If such
a position is found, the program displays the index in s where p begins; if p
cannot be found in s, the answer displayed is -1.
Here are some hints: If you did pattern matching with pencil and paper, you
would align the pattern underneath the string and check for a match of the
alignment. For example, for string "abcdefg" and pattern "cde", you begin:
s == a b c d e f g
p == c d e
current position == 0
The “current position” remembers the index where the pattern is aligned. Since
the match fails, the search moves one character:
372
s == a b c d e f g
p ==
c d e
current position == 1
The process proceeds until a match succeeds or the pattern cannot be shifted
further.
6. Write a method that computes the value of PI by using this equation:
PI = 4 * ( 1 - 1/3 + 1/5 - 1/7 + ... )
The parameter to the method is the maximum value of the denominator, n,
used in the fractions, 1/n, in the series. Insert the method in a test program
and try it on various parameters and compare your results with the value of the
Java constant Math.PI.
7. (a) Design a calendar program, which takes two inputs: a month (an integer
in range 1..12) and a year (for simplicity, we require an integer of 1900
or larger) and presents as its output the calendar for the month and year
specified. We might use the program to see the calendar for December,
2010:
Su Mo Tu We
1
5 6 7 8
12 13 14 15
19 20 21 22
26 27 28 29
Th
2
9
16
23
30
Fr
3
10
17
24
31
Sa
4
11
18
25
The application’s model will need methods to calculate how many days are
in a given month (within a given year), on what day of the week a given
month, year begins, whether a year is a leap year, etc.
(b) Next, revise the application so that, on demand, the calendar prints a
proper French calendar, that is, the days are presented Monday-Tuesday...-Sunday (lundi-mardi-mercredi-jeudi-vendredi-dimanche) with the labels,
lu ma me je ve sa di
8. Write a program that implements the word game, “hangman.” Two people
play: The first inserts a “secret word” into the game, and the second tries to
guess the secret word, one letter at a time, before six wrong guesses are made.
After the first player has typed the secret word (e.g., apple), the second sees
the following on the console:
Pick a letter:
7.14. PROGRAMMING PROJECTS
373
The second player guesses a letter, e.g., e. The program replies whether or not
the guess was correct:
Pick a letter:e
Correct!
__
| |
|
|
|
_ _ _ _ e
The program displays the partially guessed word and a “hangman’s pole” upon
which the wrong guesses, drawn as a stick figure, are compiled. The game
continues with the second player making guesses, one by one, until either the
word is guessed correctly, or the six-piece stick figure is displayed:
Pick a letter:x
__
| |
| 0
| /|\
| /\
_ p p _ e
You lose--the word was "apple"!
(Note: this program can be written with judicious use of the charAt and
substring methods for strings; see Table 9, Chapter 3, for details about these
methods.)
9. Euclid’s algorithm for calculating the greatest common divisor of two nonnegative integers is based on these laws:
• GCD(x,y) = x, if x equals y
• GCD(x,y) = GCD(x-y,y), if x > y
• GCD(x,y) = GCD(x,y-x), if y > x
Implement these equations with a method that accepts x and y as parameters
and returns as its result the greatest common divisor. Write the method with
a while-loop first. Next, write the method with recursive message sending.
10. Newton’s approximation method for square roots goes as follows: The square
root of a double, n, can be approximated as the limit of the sequence of values,
ni , where
374
• n0 = n
• ni+1 = ((n/ni ) + ni )/2
Write a method that takes as its parameters two doubles: the number n and
a precision, epsilon. The method prints the values of ni until the condition
|nk+1 − nk | < epsilon holds for some k > 0. The result of the method is nk+1 .
(Hint: the Java operation Math.abs(E) computes |E|, the absolute value of E.)
Insert this method into a test program and try it with various values of epsilon.
11. Recall yet again this formula for the monthly payment on a loan of principal,
p (double), at annual interest rate, i (double), for a loan of duration of y (int)
years:
(1 + i)y ∗ p ∗ i
annual payment =
(1 + i)y − 1
monthly payment = annual payment/12.0
The monthly payment history on a loan of principal, p, at annual interest rate,
i, is computed as follows:
• p0 = p
• pj+1 = ((1 + (i/12))pj ) − monthly payment
for j >= 0.
Use these formulas to write an application that that calculates the monthly
payment for a loan and prints the corresponding payment history. Test your
application on various inputs, and explain why the principal is completely repaid
a bit sooner than exactly y full years.
12. Write an output-view class that contains these two methods for formatting
numbers. The first is
/** format formats an integer for display.
* @param formatcode - the format in which the integer should print.
*
The variants are: "n" - format the integer to have length n characters
*
"n.m" - format the integer with length n characters
*
followed by a decimal point, followed by m zeroes.
* (Pad with leading blanks if the integer needs less than n characters.)
* Note: if the integer is too large to fit within the formatcode, the
* formatcode is ignored and the entire integer is used.
* @param i - the integer to be formatted
* @return a string containing i, formatted according to the formatcode */
public String format(String formatcode, int i)
The second method is also called format and uses the following interface:
7.14. PROGRAMMING PROJECTS
375
/** format formats a double for display.
* @param formatcode - the format in which the double should print.
*
The format must have form "n1.n2" -- format the double using n1
*
characters for the whole number part, followed by a decimal point,
*
followed by n2 characters for the fraction part.
* (Pad with leading blanks if the whole part of the double needs less than
* n1 characters.) Note: if the whole number part of the double is too large
* to fit within formatcode, the entire whole number part is included.
* @param d - the double to be formatted
* @return a string containing d, formatted according to the formatcode */
public void printf(String formatcode, double d)
Insert the two methods in a class called Formatter and use the class to print
nicely formatted output, e.g.,
Formatter f = new Formatter();
int i = ... read an input integer ...;
System.out.println("For input, " + f.format("3", i));
System.out.println("The square root is " + f.format("2.3", Math.sqrt(i)));
Don’t forget to print a leading negative sign for negative numbers.
13. Write a program that lets two players play a game of tic-tac-toe (noughts and
crosses). (Hint: the model simulates the the game board, whose internal state
can be represented by three strings, representing the three rows of the board.
Write methods that can insert a move into the board and check for three-ina-row. (Note: use the charAt and substring methods for strings; see Table 9,
Chapter 3 for details about substring.)
It is simplest to build the game for only one player at first; then add a second
player. Next, revise the game so that the computer can compete against a
human player.
14. Program an animation of a falling egg. The animation will show, at correct
376
velocity, an egg falling from a great height and hitting the ground.
The egg’s fall is calculated by this famous formula from physics:
height = I0 − (0.5 ∗ g ∗ t2 )
where I0 is the egg’s initial height (in meters), t is the time (in seconds) that
the egg has been falling, and g is the acceleration due to gravity, which is
9.81meters/second2 .
7.14. PROGRAMMING PROJECTS
377
The animation should let its user specify the initial height from which the egg
is dropped. When the egg reaches height 0, it must “smash” into the ground.
15. Program an animation of a cannon ball shot into the air: The position, x,y,
of the cannon ball in the air after t seconds have elapsed is defined by these
formulas:
x = initial velocity ∗ cosine(radians angle) ∗ t
y = (initial velocity ∗ sine(radians angle) ∗ t) − ((gravity ∗ t2 )/2)
where initial velocity is the velocity of the ball when it first leaves the cannon,
gravity is the pull of gravity, and radians angle is computed as radians angle =
(degrees ∗ P I)/180 degrees is the angle that the cannon was pointed when it
shot the cannon ball.
Your application should let the user experiment with different initial velocities,
degrees angles, and gravitational constants.
16. Make the cannon-ball animation into a game by drawing a bucket (pail) at a
random spot near the end of the graphics window and letting the user adjust
the velocity and angle of the cannon to try to make her cannon ball fall into
the bucket.
17. Program an animation of a clock—every second, the clock redisplays the time.
(To start, program a digital clock. Next, program a clock with a face and three
moving hands; see Chapter 4 for advice at drawing clock hands.)
18. Program a simulation of traffic flow at a traffic intersection:
|
| v |
|
|
-----+
+---->
-----+
+---|
|
|
|
For simplicity, assume that traffic on one street travels only from north to south
and traffic on the other street travels only from west to east. The simulation
maintains at most one car at a time travelling from north to south and at most
one car at a time travelling from east to west. When one car disappears from
view, another car, travelling in the same direction, may appear at the other
end of the street. All cars travel at the same velocity of 10 meters (pixels) per
second.
378
Assume that both roadway are 90 meters in length and that the intersection
has size 10-by-10 meters.
Build the simulation in stages:
(a) First, generate north-south cars only: A new north-south car appears
within a random period of 0-2 seconds after the existing north-south car
disappears from view. Next, add a counter object that counts how many
cars complete their journey across the intersection in one minute.
(b) Next, generate east-west cars: A new east-west car appears within a random period of 0-5 seconds after the existing one disappears. Count these
cars as well. (Do not worry about collisions at the intersection.)
(c) Now, program the cars so that a car must stop 20 meters (pixels) before
the intersection if a car travelling in another direction is 20 meters or
closer to entering the intersection. (If both cars are equi-distant from the
intersection, the east-west car—the car on the “right”—has right-of-way.)
Count the total flow of cars in one minute.
(d) Experiment with stop signs at the intersection: A stop sign causes a car to
stop for 1 second, and if another car is within 20 meters of the intersection
at the end of the 1 second, the stopped car must wait until the moving car
crosses the intersection. Place a stop sign on just the east-west street and
count the total traffic flow for one minute. Do the same for a stop sign
just on the north-south street.
(e) Finish the simulation with two stop signs; count cars.
Which arrangement of stop signs, if any, caused optimal traffic flow?
7.15
Beyond the Basics
7.15.1 Loop termination with break
7.15.2 The do-while Loop
7.15.3 Loop Invariants
7.15.4 Loop Termination
7.15.5 More Applets
These optional sections expand upon the materials in the chapter. In particular,
correctness properties of loops and recursively defined methods are studied in depth.
379
7.15. BEYOND THE BASICS
7.15.1
Loop Termination with
break
A loop that does indefinite iteration must terminate at some point, and often the
decision to terminate is made in the middle of the loop’s body. Java provides a
statement named break that makes a loop quit at the point where the termination
decision is made.
For example, Figure 5 showed how the findChar method used indefinite iteration
to search for the leftmost occurrence of a character in a string. Once the character
is located, a boolean variable, found, is set to true, causing the loop to quit at the
beginning of the next iteration. We can terminate the loop immediately by using a
break statement instead of the assignment, found = true:
public int findChar(char c, String s)
{ int index = 0; // where to look within s for c
while ( index < s.length() )
// invariant: at this program point,
//
c is not any of chars 0..(index-1) in s
{ if ( s.charAt(index) == c )
{ break; } // exit loop immediately
else { index = index + 1; }
}
// If the break executed, this means index < s.length() is still true
// at this point and that s.charAt(index) == c.
if ( !(index < s.length()) ) // did the loop terminate normally?
{ index = -1; }
// then c is not found in s
return index;
}
The break statement causes the flow of control to move immediately from the middle
of the loop to the first statement that follows the loop. For this reason, the found
variable no longer controls loop termination. This simplifies the loop’s test but forces
us to write a more complex conditional that follows the loop.
When a break statement is inserted into a loop, it means that the loop has more
than one way of exiting—it can exit by failure of its test and by execution of the
break. For this reason, a break statement should be used sparingly and with caution:
insert a break only in a position where the reason for loop exit is perfectly clear
In the above example, the break occurs exactly when s.charAt(index) == c, which
is a crucial property for describing what the loop does. This property is crucial to
the the conditional statement that follows the loop. If you are not completely certain
about inserting a break statement into a loop, then don’t do it.
Finally, note that a break exits only one loop’s body. For example, the following
nested loop appears to make the values of variables i and j vary from 0 to 3. But
the break statement in the body of the inner loop causes that loop to terminate
prematurely:
380
int i = 0;
while ( i != 4 )
{ int j = 0;
while ( j != 4 )
{ if ( i + j == 3 )
{ break; }
j = j + 1;
}
System.out.println("i = " + i + ", j = " + j);
i = i + 1;
}
The loops print
i
i
i
i
=
=
=
=
0,
1,
2,
3,
j
j
j
j
=
=
=
=
3
2
1
0
The above example should make clear that a break statement can make a program
more difficult to understand.
7.15.2
The
do-while
Loop
The Java language provides a variant of while-loop that performs its test at the end
of the loop’s body, rather than the beginning; the new loop is a do-while-statement:
do { S } while ( E );
and its semantics states
1. The body, S, executes.
2. The test, E, computes to a boolean answer. If false, the loop is finished; if
true, Steps 1 and 2 repeat.
The do-while-statement can be convenient for programming repetitive tasks where
there is at least one repetition. Input-transition processing is one such example, and
its pattern can be written with a do-while-statement:
boolean processing = true;
do { READ AN INPUT TRANSACTION;
if ( THE INPUT INDICATES THAT THE LOOP SHOULD STOP )
{ processing = false; }
else { PROCESS THE TRANSACTION; }
}
while ( processing );
7.15. BEYOND THE BASICS
7.15.3
381
Loop Invariants
The two crucial questions of loop design are:
1. What is the purpose of the loop’s body?
2. How does the loop’s test ensure termination?
We study the first question in this section and the second in the next.
A loop must achieve its goal in multiple steps, and surprisingly, one understands
a loop better by asking about what the loop accomplishes in its steps towards its
goal, rather than asking about the ultimate goal itself. Precisely, one must answer
the following question, ideally in one sentence:
Say that the loop has been iterating for some time; what has it accomplished so far?
The answer to this question is called a loop’s invariant.
The term “invariant” is used to describe the answer, because the answer must
be a property that holds true whether the loop has executed for 2 repetitions, or 20
repetitions, or 200 repetitions, or even 0 repetitions—the answer must be invariant
of the number of repetitions of the loop.
A loop invariant also reveals what the loop’s ultimate goal will be: Once the loop
terminates, it will be the case that (i) the invariant is still true, and (ii) the loop’s
test has evaluated to false. These two facts constitute the loop’s total achievement.
Let’s consider several examples. Here is a loop:
int n = ...; // read integer from user
int i = 0;
while ( i != (n + 1) )
{ System.out.println(i * i);
i = i + 1;
}
Answer the question: Say that the loop has been iterating for some time; what has it
accomplished so far? An informal but accurate answer is, “the loop has been printing
squares.” Indeed, this is an invariant, and it is useful to document the loop exactly
this way:
while ( i != (n + 1) )
// the loop has been printing squares
{ System.out.println(i * i);
i = i + 1;
}
Someone who is more mathematically inclined might formulate a more precise answer:
The loop has been printing the squares 0*0, 1*1, 2*2, etc. Indeed, the value of its
counter variable, i, states precisely how many squares have been printed:
382
while ( i != (n + 1) )
// the loop has printed the squares
{ System.out.println(i * i);
i = i + 1;
}
0*0, 1*1, ...up to... (i-1)*(i-1)
This is also an invariant, because whether the loop has iterated 2 times, 20 times,
etc., the invariant states exactly what has happened. For example, after 20 iterations,
i has value 21, and indeed the loop has printed from 0*0 up to 20*20. (If the loop
has iterated zero times, i has value 0 and the invariant says the loop has printed from
0*0 up to -1*-1, that is, it has printed nothing so far.)
When the loop terminates, its test, i != (n + 1), evaluates to false. Hence, i has
the value, n+1. But we know that the invariant is still true, and by substituting n+1
for i, we discover that the loop has printed exactly the squares from 0*0 up to n*n.
This exposes the ultimate goal of the loop.
Next, reconsider the summation example and its proposed invariant:
int n = ... ; // read input value
int total = 0; int i = 0;
while ( i != n )
// proposed invariant: total == 0 + 1 + ...up to... + i
{ i = i + 1;
total = total + i;
}
How do we verify that the invariant is stated correctly? To prove an invariant, we
have two tasks:
1. basis step: we show the invariant is true the very first time the loop is encountered.
2. inductive step: We assume the invariant is already holding true at the start of an
arbitrary iteration of the loop, and we show that when the iteration completes
the invariant is still true.
Such a proof style is known as proof by induction on the number of iterations of the
loop.
The proof of the invariant for the summation loop is an induction:
1. When the loop is first encountered, both total and i have value 0, therefore it
is true that total == 0 + ...up to... + 0.
2. Next, we assume the invariant is true at the start of an arbitary iteration:
total_start == 0 + 1 + ...up to... + i_start
7.15. BEYOND THE BASICS
383
and we show that the statements in the loop’s body update the invariant so
that it holds true again at the end of the iteration:
total_next == 0 + 1 + ...up to... + i_next
We use V start to represent the value of variable V at the start of the iteration
and V next to represent the new value V receives during the iteration.
Here is the proof:
• Because of i = i + 1, we know that i next == i start + 1, and because
of total = total + i, we know that total next = total start + i next
• Use algebra to add i next to both sides of the starting invariant: total start
+ i next == (0+1+...up to...+ i start) + i next.
• Using the definitions of total next and i next, we substitute and conclude
that total next == 0+1+...up to...+ i next
We conclude, no matter how long the loop iterates, the invariant remains true.
The previous examples were of definite iteration loops, where the pattern of invariant takes the form
int i = INITIAL VALUE;
while ( TEST ON i )
// all elements in the range INITIAL VALUE ...up to... (i-1)
// have been combined into a partial answer
{ EXECUTE LOOP BODY;
i = i + 1;
}
In the case of input processing, the natural invariant for the transaction-processing
loop is simply,
boolean processing = true;
while ( processing )
// every transaction read so far has been processed correctly
{ READ AN INPUT TRANSACTION;
if ( THE INPUT INDICATES THAT THE LOOP SHOULD STOP )
{ processing = false; }
else { PROCESS THE TRANSACTION; }
}
Searching loops often have invariants in two parts, because the loops can exit in
two different ways; the general format is
384
boolean item_found = false;
DETERMINE THE FIRST ‘‘ITEM’’ TO EXAMINE FROM THE COLLECTION;
while ( !item_found && ITEMS REMAIN IN THE COLLECTION TO SEARCH )
// Clause 1: item_found == false implies the DESIRED ITEM is not found
// in that part of the COLLECTION examined so far
// Clause 2: item_found == true implies the DESIRED ITEM is ITEM
{ EXAMINE ITEM FROM THE COLLECTION;
if ( ITEM IS THE DESIRED ONE )
{ item_found = true; }
else { DETERMINE THE NEXT ITEM TO EXAMINE FROM THE COLLECTION; }
}
Clauses 1 and 2 are necessary because the value of item found can possibly change
from false to true within an iteration.
If you write a loop and you “know” what the loop does, but you find it difficult
to state its invariant, then you should generate execution traces that exhibit values
of the loop’s variables and examine the relationships between the variable’s values at
the various iterations. These relationships often suggest an invariant. For example,
say that we believe this loop computes multiplication of two nonnegative integers a
and b via repeated additions:
int i = 0;
int answer = 0;
while ( i != a )
{ answer = answer + b;
i = i + 1;
}
The question is: Say that the loop has been iterating for some time; what has it
accomplished so far? We can use the debugging tool of an IDE to print an execution
trace, or we might make the loop generate its own trace with a println statement:
int i = 0;
int answer = 0;
while ( i != a )
{ System.out.println("i=" + i + " a=" + a + " b=" + b
+ " c=" + c + " answer=" + answer);
answer = answer + b;
i = i + 1;
}
When a is initialized to 3 and b is initialized to 2, we get this trace:
i=0
i=1
i=2
i=3
a=3
a=3
a=3
a=3
b=2
b=2
b=2
b=2
answer=0
answer=2
answer=4
answer=6
7.15. BEYOND THE BASICS
385
We see that i * b = answer holds at the start of each iteration. This is indeed the
crucial property of the loop, and when the loop terminates, we conclude that i = a
and therefore a * b = answer.
These examples hint that one can use loop invariants and elementary symbolic
logic to construct formal proofs of correctness of computer programs. This is indeed
the case and unfortunately this topic goes well beyond the scope of this text, but a
practical consequence of invariants does not: A programmer who truly understands
her program will be capable of stating invariants of the loops it contains. As a matter of
policy, the loops displayed in this text will be accompanied by invariants. Sometimes
the invariant will be stated in mathematical symbols, and often it will be an English
description, but in either case it will state the property that remains true as the
iterations of the loop work towards the loop’s goal.
Exercises
State invariants for these loops and argue as best you can that the invariants remain
true each time the loop does an additional iteration:
1. Multiplication:
int a = ...; int b = ...;
int i = b; int answer = 0;
while ( !(i == 0 ) )
{ answer = answer + a;
i = i - 1;
}
System.out.println(answer);
2. Division:
int n = ...;
if ( n >= 0
{ int q =
int rem
while (
{
int d = ...;
&& d > 0 )
0;
= n;
rem >= d )
q = q + 1;
rem = rem - d;
}
}
3. Exponentiation:
386
int n = ...; int p = ...;
if ( p >= 0 )
{ int i = 0;
int total = 1;
while ( i != p )
{ total = total * n;
i = i+1;
}
}
else { ... };
4. Search for character, ’A’:
String s = ... ;
boolean item_found = false;
int index = 0;
while ( !item_found && (index < s.length()) )
{ if ( s.charAt(index) == ’A’ )
{ item_found = true; }
else { index = index + 1; }
}
5. String reverse:
String s = ...;
String t = "";
int i = 0;
while ( i != s.length() )
{ t = s.charAt(i) + t;
i = i + 1;
}
7.15.4
Loop Termination
How do we write the test of a loop so that it ensures that the loop must terminate?
Imagine that every loop comes with an “alarm clock” that ticks downwards towards
0—when the clock reaches 0, the loop must terminate. (The loop might terminate
before the alarm reaches 0, but it cannot execute further once the alarm reaches 0.)
Our job is to show that a loop indeed has such an “alarm clock.”
Usually, the alarm clock is stated as an arithmetic expression, called a termination expression. Each time the loop executes one more iteration, the value of the
termination expression must decrease, meaning that eventually it must reach zero.
Consider the loop that multiplies nonnegative integers a and b:
7.15. BEYOND THE BASICS
387
int i = 0; int answer = 0;
while ( i != a )
{ answer = answer + b;
i = i + 1;
}
The termination expression is a - i, because each time the loop completes an iteration, the value of a - i decreases. Since a is a nonnegative number, at some point
i’s value will equal a’s, and the termination expression’s value reaches 0, and at this
very moment, the loop does indeed terminate. (Again, we require that a and b are
nonnegative integers.)
Definite iteration loops use termination expressions that compare the value of the
loop counter to a stopping value, because the increment of the loop counter within
the loop’s body makes the counter move closer to the stopping value.
Searching loops ensure termination by limiting their search to a finite collection.
In the case of the example in Figure 4, where the loop searches for divisors, the termination expression is current - 1, because variable current is searching for possible
divisors by counting from n/2 down to 1. When the termination expression reaches
0, it forces the loop’s test to go false.
Unfortunately, not all loops have such termination expressions; a loop that implements a divergent infinite series clearly lacks such an “alarm clock.” An even simpler
example is a loop that reads a sequence of transactions submitted by a user at the
keyboard—there is no termination expression because there is no guarantee that the
user will tire and terminate the sequence of inputs.
Exercises
For each of the loops in the previous exercise set, state the conditions under which
each is guaranteed to terminate and explain why.
7.15.5
More Applets
Part of the fun in writing animations and drawing recursive pictures is displaying
them. As noted at the end of Chapter 4, we can use an applet to display an outputview object within a web page.
Because they are designed for use only with graphical-user-interface (GUI) controllers,
Begin Footnote: We study these in Chapter 10. End Footnote
Java applets do not execute the controller objects we have written so far. So, if we
wish to display this Chapter’s moving-ball animation as an applet, we must rewrite
the controller in Figure 16.
First, we review the key modifications to make an output-view class into a Java
applet:
388
Figure 7.22: output-view class for animation applet
import java.awt.*;
import javax.swing.*;
/** AnimationApplet contains the start-up class, controller,
and output-view of the moving ball animation. */
public class AnimationApplet extends JApplet
// the output-view of the box
{ private BoxWriter box writer;
private BallWriter ball writer; // the output-view of the ball in the box
private MovingBall ball;
// the (address of the) moving ball object
// this was formerly the application’s main method:
/** init initializes (constructs) the applet */
public void init()
{ // construct the model objects:
int box size = 200;
int balls radius = 6;
Box box = new Box(box size);
ball = new MovingBall((int)(box size / 3.0), (int)(box size / 5.0),
balls radius, box);
// construct the output-view objects:
ball writer = new BallWriter(ball, Color.red);
box writer = new BoxWriter(box);
// constructor method for class AnimationWriter is unneeded:
// AnimationWriter writer
//
= new AnimationWriter(box writer, ball writer, box size);
// the controller’s runAnimation method is renamed paint, so
// following statement is unneeded:
// new BounceController(ball, writer).runAnimation();
}
/** paint paints the applet---this is the runAnimation method.
* @param g - the graphics pen */
public void paint(Graphics g)
{ while ( true )
{ delay(20);
ball.move();
// System.out.println(ball.xPosition() + ", " + ball.yPosition());
// the following statement replaces writer.repaint():
paintAnimation(g);
}
}
...
389
7.15. BEYOND THE BASICS
Figure 7.22: output-view class for animation applet (concl.)
/** delay pauses execution for how long
private void delay(int how long)
{ try { Thread.sleep(how long); }
catch (InterruptedException e) { }
}
milliseconds */
/** paintAnimation is formerly AnimationWriter’s
public void paintAnimation(Graphics g)
{ box writer.paint(g);
ball writer.paint(g);
}
paintComponent
method */
}
• Change the class from extends JPanel to extends JApplet.
• Remove the header line from the constructor method and replace it by public
void init().
• Remove the enclosing frame and all invocations of setSize, setTitle, and
setVisible.
If the constructor method uses parameters, then you are out of luck. (But
Chapter 10 presents an awkward way of binding strings to variables within
init.)
• Rename paintComponent to paint.
For example, the class in Figure 20 is revised this way:
import java.awt.*;
import javax.swing.*;
/** RecursivePictureApplet displays a recursively defined picture of an
* egg in front of a picture (of eggs). */
public class RecursivePictureApplet extends JApplet
{ private double scale = 0.8; // the size of the background picture relative
//
to the foreground
private int width = 400;
// frame width and depth
private int depth = 200;
private int first_egg = 150; // size of the egg in the foreground
/** the constructor method is renamed
public void init()
{ setBackground(Color.white); }
init:
*/
390
/** paintComponent is renamed paint: */
public void paint(Graphics g)
{ ... }
... the other methods stay the same ...
}
As noted in Chapter 4, the applet is executed by the applet command in an
HTML-coded web page. Here is a sample web-page:
<body>
Here is my applet:
<p>
<applet code = "RecursivePictureApplet.class" width=400 height=200>
Comments about the applet go here.<applet>
</body>
We must work harder to make the animation example into an applet—the start-up
and controller classes must be squeezed into the output-view class, AnimationWriter.
The unfortunate result appears in Figure 22. The controller must be moved into the
applet’s paint method, because a newly created applet first executes its init method,
followed by paint. The “real” painting method, that of the output view, is renamed
paintAnimation and is invoked from within paint.
In Chapter 10, we learn how to avoid such surgeries.
Chapter 8
Data Structure: Arrays
8.1 Why We Need Arrays
8.2 Collecting Input Data in Arrays
8.3 Translation Tables
8.4 Internal Structure of One-Dimensional Arrays
8.5 Arrays of Objects
8.6 Case Study: Databases
8.6.1 Behaviors
8.6.2 Architecture
8.6.3 Specifications
8.6.4 Implementation
8.6.5 Forms of Records and Keys
8.7 Case Study: Playing Pieces for Card Games
8.8 Two-Dimensional Arrays
8.9 Internal Structure of Two-Dimensional Arrays
8.10 Case Study: Slide-Puzzle Game
8.11 Testing Programs with Arrays
8.12 Summary
8.13 Programming Projects
8.14 Beyond the Basics
Computer programs often manage many objects of the same type, e.g., a bank’s
accounting program must manage hundreds of customer accounts. It is inconvenient
and usually impossible to declare distinctly named variables for each of the customer
accounts; instead, one constructs a new form of object—a data structure—to collectively hold and name the customer accounts.
The most popular form of data structure is the array, and this chapter introduces
standard uses of arrays. After studying this chapter, the reader should be able to
392
• use arrays to model real-life collections like a library’s catalog, a company’s
database of customer records, or the playing pieces of a game.
• understand when to use one-dimensional arrays to model sequences and when to
use two-dimensional arrays to model grids.
8.1
Why We Need Arrays
When a program manipulates many variables that contain “similar” forms of data,
organizational problems quickly arise. Here is an example: In an ice-skaking competition, each skater’s performance is judged by six judges, who assign fractional scores.
The six scores must be collected and manipulated in various ways, e.g., printed from
highest to lowest, averaged, multiplied by weighting factors, and so on.
Say that the six scores are saved in these variables,
double score0;
double score3;
double score1;
double score4;
double score2;
double score5;
and say that you must write a method that locates and prints the highest score of
the six. How will you do this? Alas, the method you write almost certainly will use
a sequence of conditional statements, like this:
double high_score = score0;
if ( score1 > high_score ) { high_score
if ( score2 > high_score ) { high_score
if ( score3 > high_score ) { high_score
if ( score4 > high_score ) { high_score
if ( score5 > high_score ) { high_score
System.out.println(high_score);
=
=
=
=
=
score1;
score2;
score3;
score4;
score5;
}
}
}
}
}
This unpleasant approach becomes even more unpleasant if there are more even scores
to compare or if a more difficult task, such as ordering the scores from highest to
lowest, must be performed. Some other approach is required.
Can we employ a loop to examine each of score0 through score6? But the six
variables have distinct names, and a loop has no way of changing from name to name.
Ideally, we wish to use “subscripts” or “indexes,” so that we may refer to score0 as
score0 , score1 as score1 , and so on. The notation would be exploited by this for-loop,
double high score = score0 ;
for ( int i = 1; i <= 5; i = i + 1 )
{ if ( scorei > high score )
{ high score = scorei ; }
}
System.out.println(high score);
393
8.1. WHY WE NEED ARRAYS
which would examine all six variables.
Java uses array variables, for indexing like this. An array variable names a collection of individual variables, each of which possesses the same data type. For example,
we can declare and initialize an array variable that holds six doubles by stating the
following:
double[] score = new double[6];
The name of this variable is score, and its declared data type is double[] (read this
as “double array”). The data type indicates that score is the name of a collection
of doubles, and the doubles named by the array variable are score[0], score[1], ...,
score[5].
The initialization statement’s right-hand side, new double[6], constructs a new
form of object that holds six elements, each of which is a double variable.
It is traditional to draw an array like this:
score
0
1
2
3
4
5
0.0
0.0
0.0
0.0
0.0
0.0
The diagram shows that a newly constructed array that holds numbers starts with
zeros in all the elements.
When we wish to refer to one of the elements in the array named score, we use
an index (also known as subscript) to identify the element. The elements are indexed
as score[0], score[1], and so on, up to score[5]. For example, we can print the
number held in element 3 of score by writing,
System.out.println(score[3]);
Java requires that an array’s indexes must be integers starting with zero.
It is helpful to think of variable score as the name of a “hotel” that has six
“rooms,” where the rooms are labelled 0 through 5. By stating score[3], we specify
the precise address of one of the hotel’s rooms, where we locate the room’s “occupant.”
Of course, each of the elements of an array is itself a variable that can be assigned.
For example, say that the leading judge gives the score, 5.9, to a skater. We insert
the number into the array with this assignment:
score[0] = 5.9;
If the skater’s next score is 4.4, then we might write,
score[1] = 4.4;
394
Here is a picture that shows what we have accomplished:
score
0
1
2
3
4
5
5.9
4.4
0.0
0.0
0.0
0.0
We can insert values into all the array’s elements in this fashion.
But most importantly, the index contained within the square brackets may be a
variable or even an integer-valued arithmetic expression. For example,
int i = 3;
System.out.println(score[i]);
System.out.println(score[i + 1]);
locates and prints the doubles held in elements 3 and 4 of score. By using variables
and expressions as indexes, we can write intelligent loops, such the one that locates
and prints a skater’s highest score:
double high_score = score[0];
for ( int i = 1; i <= 5; i = i + 1 )
// invariant: high_score holds the highest score in the range,
//
score[0] ..upto.. score[i-1]
{ if ( score[i] > high_score )
{ high_score = score[i]; }
}
System.out.println(high_score);
By changing the value of i at each iteration, the loop’s body examines all the array’s
elements, solving the problem we faced at the beginning of this section.
As noted above, we can use integer-valued arithmetic expressions as indexes. For
example, perhaps variable i remembers an array index; if we wish to exchange the
number in score[i] with that of its predecessor element, we can write
int temp = score[i];
score[i - 1] = score[i];
score[i] = temp;
The phrase, score[i - 1], refers to the element that immediately precedes element
score[i]. For example, for the array pictured earlier and when i holds 2, the result
of executing the above assignments produces this array:
score
0
1
2
3
4
5
5.9
0.0
4.4
0.0
0.0
0.0
If variable i held 0 (or a negative number), then score[i - 1] would be a nonsensical reference, and the execution would halt with a run-time exception, called an
ArrayIndexOutOfBoundsException.
8.1. WHY WE NEED ARRAYS
395
The above example showed how an array might hold a set of numbers. But arrays
can hold characters, booleans, strings, and indeed, any form of object whatsoever. For
example, the words of a sentence might be stored into an array like this:
String[] word = new String[3];
word[0] = "Hello";
word[1] = "to";
word{2] = "you";
Array word is declared so that it keeps strings in its elements.
Second, if we have written a class, say, class BankAccount, then we can declare
an array to hold objects constructed from the class:
BankAccount[] r = new BankAccount[10];
r[3] = new BankAccount();
BankAccount x = new BankAccount();
r[0] = x;
The previous sequence of statements constructs two BankAccount objects and assigns
them to array r.
Because they can hold numbers, booleans, characters, and objects, arrays are
heavily used in computer programming to model sets or collections. The collection of
skating scores seen at the beginning of this section is one simple example, but there
are many others:
• a bank’s customer accounts
• a library’s books
• playing pieces or players for an interactive game
• a table of logarithms or solutions to an algebraic equation
• indeed, any “data bank,” where multiple objects are held for reference
The sections that follow show how to use arrays to model these and other examples.
Exercises
1. Say that we declare this array:
int r = new int[4];
What do each of these loops print? (Hint: It will be helpful to draw a picture
of array r, like the ones seen in this section, and update the picture while you
trace the execution of each loop.)
396
(a) for ( int i = 0; i < 4; i = i + 1 )
{ System.out.println(r[i]); }
(b) int i = 1;
r[i] = 10;
r[i + 2] = r[i] + 2;
for ( int i = 0; i < 4; i = i + 1 )
{ System.out.println(r[i]); }
(c) for ( int i = 3; i >= 0; i = i - 1 )
{ r[i] = i * 2; }
for ( int i = 0; i < 4; i = i + 1 )
{ System.out.println(r[i]); }
(d) r[0] = 10;
for (
{
for (
{
int i = 1; i != 4; i = i + 1 )
r[i] = r[i - 1] * 2; }
int i = 0; i < 4; i = i + 1 )
System.out.println(r[i]); }
2. Declare an array, powers of two, that holds 10 integers; write a for-loop that
places into powers of two[i] the value of 2i , for all values of i in the range, 0
to 9.
3. Declare an array, letter, that holds 26 characters. Write a for-loop that initializes the array with the characters, ’a’ through ’z’. Next, write a loop that
reads the contents of letter and prints it, that is, the letters in the alphabet,
all on one line, in reverse order.
4. Declare an array, reciprocals, that holds 10 doubles; write a for-loop that
places into reciprocals[i] the value of 1.0 / i, for all values of i in the range,
1 to 9. (What value remains in reciprocals[0]?)
8.2
Collecting Input Data in Arrays
Arrays are particularly useful for collecting input data that arrive in random order.
A good example is vote counting: Perhaps you must write a program that tallies the
votes of a four-candidate election. (For simplicity, we will say that the candidates’
“names” are Candidate 0, Candidate 1, Candidate 2, and Candidate 3. ) Votes arrive
one at a time, where a vote for Candidate i is denoted by the number, i. For example,
two votes for Candidate 3 followed by one vote for Candidate 0 would appear:
3
3
0
397
8.2. COLLECTING INPUT DATA IN ARRAYS
and so on.
Vote counting will go smoothly with an array that holds the tallies for the four
candidates: We construct an array whose elements are integers,
int[] votes = new int[4];
where votes[0] holds Candidate 0’s votes, and so on. When a vote arrives, it must
be added to the appropriate element:
int v = ...read the next vote from the input...
votes[v] = votes[v] + 1;
The algorithm for vote counting follows the “input processing pattern” from Chapter
7:
boolean processing = true;
while ( processing )
{ int v = ...read the next vote from the input...
if ( v is a legal vote, that is, in the range, 0..3 )
{ votes[v] = votes[v] + 1; }
else { processing = false; }
}
Once all the votes are tallied, they must be printed. There is a standard way of
printing the contents of an array like votes:
for ( int i = 0; i != votes.length; i = i + 1 )
// invariant: values of votes[0]..votes[i-1]
{ System.out.println( ... votes[i] ... ); }
have been printed
A for-loop works with a loop-counter variable, i, to print all the array’s elements.
Notice the phrase, votes.length, which is new and appears in the loop’s termination
test: The phrase is a built-in Java convenience that denotes the length of array votes
(here, 4). It is always better to use votes.length, rather than 4, in the coding, because
the former need not be changed if array votes is changed in a later version of the
program.
Figure 1 presents the resulting vote counting program. For example, if the election’s votes arrived in the order, 3, 3, 0, 2, 3, followed by a terminating number,
e.g., -1, the program prints
398
Figure 8.1: vote counting
import javax.swing.*;
/** VoteCount tallies the votes for election candidates.
* input: a sequence of votes, terminated by a -1
* output: the listing of the candidates and their tallied votes */
public class VoteCount
{ public static void main(String[] args)
// how many candidates
{ int num candidates = 4;
int[] votes = new int[num candidates];
// holds the votes;
// recall that each element is initialized to 0
// collect the votes:
boolean processing = true;
while ( processing )
// invariant: all votes read have been tallied in array votes
{ int v = new Integer(JOptionPane.showInputDialog
("Vote for (0,1,2,3):")).intValue();
if ( v >= 0 && v < votes.length ) // is it a legal vote?
{ votes[v] = votes[v] + 1; }
else { processing = false; } // quit if there is an illegal vote
}
// print the totals:
for ( int i = 0; i != votes.length; i = i + 1 )
// totals for votes[0]..votes[i-1] have been printed
{ System.out.println("Candidate" + i + " has " + votes[i] + " votes"); }
}
}
Perhaps the key statement in the program is the conditional statement,
if ( v >= 0 && v < votes.length )
{ votes[v] = votes[v] + 1; }
else { ... }
The conditional adds the vote to the array only if the vote falls within the range
0..3. If an improperly valued v, say, 7, was used with votes[v] = votes[v] + 1, then
execution would halt with this exception,
java.lang.ArrayIndexOutOfBoundsException: 7
at Test.main(VoteCount.java:...)
because there is no element, votes[7]. It is always best to include conditional statements that help a program defend itself against possible invalid array indexes.
8.3. TRANSLATION TABLES
399
Exercises
1. Modify class VoteCount in Figure 1 so that it prints the total number of votes
cast and the winning candidate’s “name.”
2. Modify class VoteCount so that the application first asks for the number of
candidates in the election. After the number is typed, then votes are cast as
usual and the results are printed.
3. Modify class VoteCount so that the application first requests the names of the
candidates. After the names are typed, the votes are cast as as usual and the
results are printed with each candidate’s name and votes. (Hint: Use an array
of type String[] to hold the names.)
4. Write an application that reads a series of integers in the range 1 to 20. The
input is terminated by an integer that does not fall in this range. For its output,
the application prints the integer(s) that appeared most often in the input, the
integer(s) that appeared least often, and the average of all the inputs.
8.3
Translation Tables
Arrays are useful for representing tables of information that must be frequently consulted. Here is an example: A simple form of coding is a substitution code, which
systematically substitutes individual letters by integer codes. For example, if we replace every blank space by 0, every ’a’ by 1, every ’b’ by 2, and so on, we are using
a substitution code. A sentence like,
a bed is read
is encoded into this sequence of integers:
1 0 2 5 4 0 9 19 0 18 5 1 4
This simple substitution code is all too easy for outsiders to decipher.
Here is a slightly more challenging substitution code: Starting with a “seed”
integer, k, we encode a blank space by k. Call this value code(’ ’). Next, we encode
the alphabet in this pattern, where each character’s code is the twice as large as its
precedessor’s, plus one:
code(’ ’)
code(’a’)
code(’b’)
...
code(’z’)
= k;
= (code(’ ’) * 2) + 1
= (code(’a’) * 2) + 1
= (code(’y’) * 2) + 1
For example, with a starting seed of 7, a bed is read encodes to
400
15 7 31 255 127 7 4095 4194303 7 2097151 255 15 127
The encoding program will work most efficiently if it first calculates the integer
codes for all the letters and saves them in a translation table—an array. Then, the
letters in the input words are quickly encoded by consulting the table for the codes.
We build the table as follows. First we declare the array:
int[] code = new int[27];
//
//
//
//
this is
code[0]
code[1]
code[2]
the translation table:
holds the code for ’ ’,
holds the code for ’a’,
holds the code for ’b’, and so on
Next, we systematically compute the codes to store in array code: the value of code[i]
is defined in terms of its predecessor, code[i - 1], when i is positive:
code[i] = (code[i - 1] * 2) + 1;
The arithmetic expression, i - 1, can be used, because Java allows integer-valued
expressions as indexes.
We now write this loop to compute the codes:
int seed = ... ;
code[0] = seed;
for ( int i = 1; i != code.length; i = i + 1 )
{ code[i] = (code[i - 1] * 2) + 1; }
We are now ready to read a string and translate its characters one by one into
integer codes: Java treats characters like they are integers, which makes it easy to
check if a character, c, is a lower-case letter (c >= ’a’ && c <= ’z’ does this) and to
convert the character into the correct index for array code ((c - ’a’) + 1 does this):
String input_line = JOptionPane.showInputDialog("type sentence to encode: ");
for ( int j = 0; j != input_line.length(); j = j + 1 )
{ char c = input_line.charAt(j);
if ( c == ’ ’ )
{ System.out.println(code[0]); }
else if ( c >= ’a’ && c <= ’z’ )
{ int index = (c - ’a’) + 1;
System.out.println(code[index]);
}
else { System.out.println("error: bad input character"); }
}
(Recall that S.length() returns the length of string S and that S.charAt(j) extracts
the jth character from S.)
When we build a translation table, we compute each possible translation exactly
once, and we save and reuse the answers when we read the inputs. For this reason,
translation tables are most useful when it is expensive to compute translations and
there are many inputs to translate.
401
8.3. TRANSLATION TABLES
Exercises
1. Write the complete application which takes a seed integer and a line of words
as input and produces a series of integer codes as output.
2. Write the corresponding decoder program, which takes the seed integer as its
first input and then reads a sequence of integers, which are decoded into characters and printed.
3. Write statements that construct a translation table, powers of two, and assign
to the table the powers of two, namely,
powers_of_two[i] = 2^i
for all values of i in the range, 0 to 9.
4. Translation tables have a special connection to recursively defined equations,
like the ones we saw in Chapter 7. For example, this recursive definition of
summation:
summation(0) = 0
summation(n) = n + summation(n-1),
if
n > 0
“defines” the following translation table:
int[] summation = new int[...];
summation[0] = 0;
for ( int n = 1; n != summation.length; n = n + 1 )
{ summation[n] = n + summation[n-1]; }
Write applications that build tables (arrays of 20 elements) for the following
recursive definitions; make the applications print the contents of each table, in
reverse order.
(a) The factorial function:
0!
n!
= 1
= n * (n-1)!, when n is positive
(b) The Fibonacci function:
Fib(0) = 1
Fib(1) = 1
Fib(n) = Fib(n-1) + Fib(n-2), when n >= 2
This example is especially interesting, because it is far more efficient to
compute the entire translation table for a range of Fibonnaci values and
consult the table just once, than it is to compute a single Fibonnaci value
with a recursively defined method. Why is this so?
402
8.4
Internal Structure of One-Dimensional Arrays
Now that we have some experience with arrays, we should learn about their internal
structure. The story starts innocently enough: A Java array variable is declared like
any Java variable, e.g.,
int[] r;
This declares variable r with data type int[] (“int array”). But variable r is meant
to hold the address of an array object; it is not itself the array. We use assignment to
place an address in r’s cell:
r = new int[6];
As noted earlier, the phrase, new int[6], constructs an array object of six integer
elements. We can do the two previous two steps in a single initialization statement:
int[] r = new int[6];
As a result of the initialization to r, computer storage looks like this:
a1 : int[6]
int[ ] r == a1
0
1
2
3
4
5
0
0
0
0
0
0
The address, a1, of the array object, new int[6], is saved in variable r. Notice that
the run-time data type of the object at address a1 is int[6]—the data type includes
the arrays’s length and its elements’ data type.
Many programmers fail to understand the difference between an array variable
and an array object and try to duplicate an array like this:
int[] s = r;
This statement duplicates the address of the array object and not the object itself!
When we examine computer storage, we see that variable s holds the same address
held by r—the two variables share the same array object:
a1 : int[6]
int[ ] r == a1
int[ ] s == a1
0
1
2
3
4
5
0
0
0
0
0
0
This means assignments to s’s elements alter r’s elements as well: For example, s[0]
= 3 will make r[0] == 3 as well.
8.4. INTERNAL STRUCTURE OF ONE-DIMENSIONAL ARRAYS
403
Once constructed, an array object’s length cannot change. The length of an
array, like r, can be obtained by the phrase, r.length. Note that r.length lacks a
parentheses set, (). For whatever reason, the Java designers made length a “public
field” of an array object, rather than a “public method.”
The array named r is called one dimensional because one index (subscript) is
required to identify a specific element in the array. Some programming languages
permit construction of a two-dimensional array (also known as a matrix or grid),
where two indexes are required to identify an element. Two dimensional arrays are
studied later in this Chapter.
We can construct arrays of integers, doubles, booleans, strings, and indeed, of any
legal data type. Here are some examples:
• double[] score = new double[6] constructs an array of six elements, each of
which holds doubles. Each element is initialized to 0.0.
• boolean[] safety check = new boolean[i + 2] constructs an array whose length
is the value of integer variable i plus 2. The example shows that an integervalued arithmetic expression states the length of the array object. (The value
must be nonnegative.) Each element of a boolean array is initialized to false
• BankAccount[] account = BankAccount[100] constructs an array that can hold
100 distinct BankAccount objects. (See Figure 11, Chapter 6, for class BankAccount.)
Each element is initialized to null, which means, “no value.”
The last statement of the last example is crucial to understanding Java arrays:
When you construct an array whose elements hold objects, the array’s elements are
initialized to null values—there is only a “container” but no objects in it. Therefore,
you must explicitly construct new objects and assign them to the elements. For the
last example, we might write a loop that constructs and inserts objects into array
account:
BankAccont[] account = new BankAccount[100];
for ( int i = 0; i != account.length; i = i + 1 )
{ account[i] = new BankAccount( ... ); } // see Figure 11, Chapter 6
An array can be partially filled with objects, just like an hotel can have some occupied rooms and some vacancies. We can test if an element is occupied by comparing
the element’s value to null. For example, here is a loop that prints the balances of
all accounts in array account, skipping over those elements that are empty:
for ( int i = 0; i != account.length; i = i + 1 )
{ if ( account[i] != null )
{ System.out.println( "Balance of account " + i
+ " is " + account[i].balanceOf() );
}
}
404
As noted in the previous section, integer-valued arithmetic expressions are used to
index an array’s elements. For example, these statements make short work of building
a lookup table of powers of two:
int[] r = new int[6];
r[0] = 1;
for ( int i = 1; i < r.length;
{ r[i] = r[i - 1] * 2; }
i = i + 1 )
Now, r[j] holds the value of 2j :
0
1
2
3
4
5
1
2
4
8
16
32
A numeric or boolean array can be constructed and initialized with a set-like
notation, which looks like this:
int[] r = {1, 2, 4, 8, 16, 32};
Because they are objects, array objects can be parameters to methods and can be
results from methods. Here is an example: Method reverse accepts (the address of)
an array object as its argument and returns as its result a newly constructed array
object whose elements are arranged in reverse order of those in its argument:
public double[] reverse(double[] r)
{ double[] answer = new double[r.length];
for ( int i = 0; i != r.length; i = i+1 )
{ answer[(r.length - 1) - i] = r[i]; }
return answer;
}
When this method is invoked, for example,
double[] numbers d = {2.3, -4.6, 8, 3.14};
double[] e = reverse(d);
it is the address of the four-element array named by d that is bound to the formal
parameter, r. Inside the method, a second array is constructed, and the numbers held
in the array named d are copied into the new array object. When the method finishes,
it returns the address of the second array—variables d and e hold the addresses of
distinct array objects.
The main method that comes with every Java application uses an array parameter,
args:
public static void main(String[] args)
{ ... args[0] ... args[1] ... }
8.4. INTERNAL STRUCTURE OF ONE-DIMENSIONAL ARRAYS
405
When an application is started, whatever program arguments supplied in the application’s start-up command are packaged into an array object, and the address of the
array is bound to args. This explains why the leading program argument is referred
to as args[0], the next argument is args[1], and so on. The number of program
arguments supplied is of course args.length.
Exercises
Create the following arrays and assign to their elements as directed.
1. An array, r, of 15 integers, such that r[0] = 0, and the value of all the other
r[i]s, i in 1..14, is the summation of i. (Hint: Use the algorithm underlying
Figure 1, Chapter 7, to calculate summations.)
2. An array, d, of 30 doubles, such that value of each d[i] is the square root of i.
(Hint: Use Math.sqrt.) For i ranging from 0 to 29, print i and its square root.
3. An array, b, of 4 booleans, such that the value of b[0] is true, the value of b[1]
is the negation of b[0], and the value of b[2] is the conjunction of the previous
two elements. (The value of b[3] does not matter.) Print the values of b[3]
through b[1].
4. Write this method:
/** maxElement returns the largest integer in its array parameter.
* @param r - an array of 1 or more integers
* @return the largest integer in the array */
public int maxElement(int[] r)
5. Write this method:
/** add adds the elements of two arrays, element-wise
* @param r1 - an array
* @param r2 - an array. Note: the two arrays’ lengths must be the same
* @return a newly constructed array, s, such that s[i] = r1[i] + r2[i],
*
for all i; return null, if r1 and r2 have different lengths. */
public double[] add (double[] r1, double[] r2)
6. Given this declaration,
BankAccount[] bank = new BankAccount[100];
406
(a) Write a for-loop that creates one hundred distinct bank accounts, each
with starting balance of 0, and assigns the accounts to the elements of
bank.
(b) Write a statement that adds 50 to the account at element 12; write statements that transfer all the money in the account at element 12 to the
account at element 45.
(c) Write a for-loop that prints the index numbers and balances for all accounts
that have nonzero balances.
(d) Write an assignment statement that makes the account at element 12 vanish.
(e) Explain what happens when this assignment is executed: bank[15] = bank[10];
8.5
Arrays of Objects
The previous section pointed out that an array can hold objects. Arrays of objects
can collect together bank accounts or library books or tax records into a single “data
base.” Here is a small example.
Recall once more, class BankAccount, from Figure 11, Chapter 6. When we
construct a new BankAccount(N), we construct an object with an initial balance of N
that can receive deposits and withdrawals. For example,
BankAccount x = new BankAccount(70);
... x.withdraw(50) ...
constructs an account named x with an initial balance of 70 and performs a withdrawal
of 50 upon it.
A bank has hundreds, if not thousands, of customers, so a program that maintains
the bank’s accounts must construct many BankAccount objects. An array is a good
structure for saving the objects. Here is one simple modelling: Say that a bank is able
to maintain at most 100 accounts. Each account is given an identification number
in the range of 0 to 99. The program that does accounting for the bank will use an
array like this:
BankAccount[] bank = new BankAccount[100];
This constructs an array that has 100 elements, such that each element holds the
initial value, null. (There are no accounts yet, just a structure to hold the accounts.)
Now, say that a customer opens an account at the bank with an initial desposit of
200, and the customer wants her account to have the identification number, 75. The
programming statements that enact this request might read,
BankAccount new_account = new BankAccount(200);
bank[75] = new_account;
407
8.5. ARRAYS OF OBJECTS
Or, more directly stated,
bank[75] = new BankAccount(200);
Here is a diagram of the array and the newly constructed object whose address is
saved within the array:
a1 : BankAccount[100]
BankAccount[] bank == a1
0
null
1
. . .
75
null
. . . a2
. . .
99
. . .
null
a2 : BankAccount
int balance ==
.
.
20
.
If the customer wishes to withdraw 60 from the account, this invocation,
bank[75].withdraw(60)
will preform the action. Note that bank[75] names the bank account object whose
withdraw method is invoked. This example emphasizes that the array, bank, holds
objects that are indexed by integers.
Next, say that the customer closes her account, so that it is no longer needed by
the bank. The bank “erases” the account by stating,
bank[75] = null;
Since null means “no value,” this means another customer might open an account
and choose 75 for its identification number.
As just noted, an array can be partially filled with objects; we can test if an array
element is occupied by comparing the element’s value to null. For example, here
is a loop that prints the balances of all accounts in array bank, skipping over those
elements that are empty:
for ( int i = 0; i != bank.length; i = i + 1 )
{ if ( bank[i] != null )
{ System.out.println( "Balance of account " + i
+ " is " + bank[i].getBalance() );
}
}
With these basic ideas in mind, we can write a simple bank-accounting application
that lets customers open bank accounts, make deposits and withdrawals, check the
balances, and close the accounts when they are no longer needed—we use an array to
hold the accounts, and we use the array’s indexes as the identification numbers for
the accounts.
408
Of course, it is overly simplistic to use simple integers as identification numbers
for bank accounts—we should use a more general notion of a key to identify an
account. The key might be a word, or a sequence of numerals and letters, or even an
object itself. The case study in the next section develops a more realistic approach
to maintaining databases of objects where objects are identified by keys.
Exercises
1. Here is a class, Counter, that can remember a count:
public class Counter
{ private int c;
public Counter(int v) { c = v; }
public void increment() { c = c + 1; }
public int getCount() { return c; }
}
Say that we change the declaration of votes in the vote-counting application in
Figure 1 to be
Counter[] votes = new Counter[num_candidates];
Revise the vote-counting application to operate with this array.
2. Let’s write a class that “models” the bank described in this section:
/** Bank models a collection of bank accounts */
public class Bank
{ BankAccount[] bank; // the array that holds the account
int max_account; // the maximum account number
/** Constructor Bank initialize the bank
* @param how_many - the maximum number of bank accounts */
public Bank(int how_many)
{ max_account = how_many;
bank = new BankAccount[how_many];
}
/**
*
*
*
*
addNewAccount adds a new account to the bank
@param id_number - the account’s identification number; must be in
the range, 0..maximum_account_number - 1
@param account - the new bank account object
@return true, if the account is succesfully added;
8.6. CASE STUDY: DATABASES
409
* return false, if the id_number is illegal */
public boolean addNewAccount(int id_number, BankAccount account)
{ boolean result = false;
if ( id_number >= 0 && id_number < max_account
&& bank[id_number] == null ) // is id_number legal?
{ bank[id_number] = account;
result = true;
}
return result;
}
/** getAccount finds a bank account
* @param id_number - the identification number of the desired account
* @return the bank account whose identification is id_number;
* if there is no account with the id_number, return null */
public BankAccount getAccount(int id_number)
{ ... }
/** deleteAccount removes a bank account
* @param id_number - the identification number of the account to be removed
* @return true, if the deletion was successful;
* return false, if no account has the id_number */
public boolean deleteAccount(int id_number)
{ ... }
}
We might use the class as follows:
Bank b = new Bank(500);
b.addNewAccount(155, new BankAccount(100));
...
BankAccount a = b.getAccount(155);
... a.deposit(200) ...
Write the two missing methods for class Bank.
3. Use class Bank in the previous exercise to write an application that lets a user
construct new bank accounts, do deposits and withdrawals, and print balances.
8.6
Case Study: Databases
A large collection of information, such as a company’s sales records or its customer
accounts or its payroll information, is called a database. An important programming
challenge is determining the proper structure for a database.
410
In simplest terms, a database is a “container” into which objects are inserted,
located, and removed; the objects that are stored in a database are called records.
An important feature about a record is that it is uniquely identified by its key, which
is held within the record itself. Here are some examples of records:
• A bank’s database holds records of accounts. Each account record is uniquely
identified by a multi-letter-and-digit account number. A typical record would
contain information such as
1. its key, the multi-digit integer, which identifies the account
2. the name (or some other identification) of the account’s owner
3. the amount of money held in the account
• A library’s database holds records of books. Each record has for its key the
book’s catalog number. For example, the U.S. Library of Congress catalog
number is a pair: an alphabetic string and a fractional number, such as QA
76.8. The records held in a library’s database would have these attributes:
1. the key, which is the book’s catalog number
2. the book’s title, author, publisher, and publication date
3. whether or not the book is borrowed, and if so, by which patron
• The U.S. Internal Revenue Service database hold records of taxpayers. Each
record is identified by a nine-digit social-security number. The record holds the
number as its key and also holds the taxpayer’s name, address, and copies of
the person’s tax reports for the past five years.
Although the example records just listed differ markedly in their contents, they share
the common feature of possessing a key. This crucial feature helps us understand the
function of a database:
A database is a container that locates records by using the records’ keys as indices.
Compare this concept to that of an array: An array is a container that locates
objects by using integer indices numbered 0, 1, 2, ..., and so on. A database is like a
“smart array” that uses a record’s key to save and locate the record.
How can we model and build a general-purpose database in Java? Here are some
crucial concepts:
1. keys are objects
2. records are objects, and a record holds as one of its attributes (the address of)
its key object
411
8.6. CASE STUDY: DATABASES
3. a database is a kind of “array” of record objects; it must have methods for
inserting a record, finding a record, and deleting a record
4. when the database’s user wishes to insert a record into the database, she calls
the databases’ insert method, supplying the record as the argument; when she
wishes to find a record, she calls the find method, supplying a key object as
an argument; when she wishes to delete a record, the calls the delete method,
supplying a key object as an argument
For example, if we build a database to hold library books, the key objects will be
Library of Congress catalog numbers, and each record object will hold (the address
of) a key object and information about a book. Such records are inserted, one by
one, into the database. When a user wishes to find a book in the database, she must
supply a key object to the database’s find method and she will receive in return (the
address of) the desired book object; an informal picture of this situation looks like
this:
a1 : Key
QA
76.9
a3 : Database
insert(a2)
. . .
a3
. . .
a2 : Record
a1
”Charles Dickens”
”A Tale of Two Cities”
borrowed
...
a4 : Key
find(a4)
QA
76.9
The picture suggests that the database will operate the same, regardless of whether
books, bank accounts, and so on, are saved. As long as the records—whatever they
are—hold keys, the database can do its insertions, lookups, and deletions, by manipulating the records’ keys and not the records themselves. This is strongly reminiscent
of arrays, which can hold a variety of objects without manipulating the objects themselves.
So, how does a database manipulate a key? Regardless of whether keys are numbers or strings or pairs of items, keys are manipulated by comparing them for equality.
Consider a lookup operation: The database receives a key object, and the database
searches its collection of records, asking each record to tell its key, so that each key
can be compared for equality to the desired key. When an equality is found true, the
corresponding record is returned. This algorithm operates the same whether integers,
strings, or whatever else is used for keys.
In summary,
1. The Database holds a collection of Record objects, where each Record holds a Key
412
object. The remaining structure of the Records is unimportant and unknown
to the database.
2. The Database will possess insert, find, and delete methods.
3. Records, regardless of their internal structure, will possess a getKey method that
returns the Record’s Key object when asked.
4. Key objects, regardless of their internal structure, will have an equals method
that compares two Keys for equality and returns true or false as the answer.
We are now ready to design and build a database subassembly in Java. We
will build a subassembly—not an entire program—such that the subassembly can be
inserted as the model into a complete application. We follow the usual stages for
design and construction:
1. State the subassembly’s desired behaviors.
2. Select an architecture for the subassembly.
3. For each of the architecture’s components, specify classes with appropriate attributes and methods.
4. Write and test the individual classes.
5. Integrate the classes into a complete subassembly.
8.6.1
Behaviors
Regardless of whether a database holds bank accounts, tax records, or payroll information, its behaviors are the same: a database must be able to insert, locate, and
delete records based on the records’ keys. We plan to write a class Database so that
an application can construct a database object by stating,
Database db = new Database(...);
Then, the application might insert a record—call it r0—into db with a method invocation like this:
db.insert(r0);
As stated earlier, each record possesses its own key. Say that record r0 holds object
k0 as its key. To retrieve record r0 from the database, we use a command like this:
Record r = db.find(k0);
This places the address of record r0 into variable r for later use. We can delete the
record from the database by stating:
413
8.6. CASE STUDY: DATABASES
Figure 8.2: architecture for a database
Database
insert
find
delete
1
*
Record
getKey(): Key
Key
equals(Key y): boolean
db.delete(k0);
Notice that variable r still holds the address of the record, but the record no longer
lives in the database.
The above behaviors imply nothing about the techniques that the database uses
to store and retrieve records; these activities are internal to class Database and are
best left unknown to the database’s users.
8.6.2
Architecture
The previous examples suggest there are at least three components to the database’s
design: the Database itself, the Records that are inserted into it, and the Keys that
are kept within records and are used to do insertions, lookups, and deletions. The
class diagram in Figure 2 lists these components and their dependencies. There
is a new notation in the Figure’s class diagram: The annotation, 1 --> *, on the
arrow emphasizes that one Database collaborates with (or collects) multiple Records,
suggesting that an array will be useful in the coding of class Database. As noted
earlier, whatever a Record or Key might be, the methods getKey and equals are
required. (The format of the equals method will be explained momentarily.)
8.6.3
Specifications
To keep its design as general as possible, we will not commit class Database to saving
any particular form of Record—the only requirement that a database will make of a
record is that a record can be asked for its key. Similarly, the only requirement a
database will make of a key is that the key can be compared to another key for an
equality check.
Since class Database must hold multiple records, its primary attribute will be
an array of records, and the database will have at least the three methods listed in
Figure 2.
414
Figure 8.3: specifications for database building
Database
Attribute
private Record[] base
Methods
insert(Record r): boolean
find(Key k):
delete(Key k):
Record
Methods
getKey():
Record
boolean
a container for data items, called Records
Holds the records inserted into the database.
Attempts to insert the record, r, into the database.
Returns true if the record is successfully added,
false otherwise.
Attempts to locate the record whose key has value
k. If successful, the address of the record is returned, otherwise, null is returned.
Deletes the record whose key has value k. If successful, true is returned; if no record has key k,
false is returned.
a data item that can be stored in a database
Returns the key that uniquely identifies the record.
Key
Key
Methods
equals(Key m):
an identification, or “key,” value
boolean
Compares itself to another key, m, for equality. If
this key and m are same key value, then true is
returned; if m is a different key value, then false is
returned.
The specification for Record is kept as minimal as possible: whatever a record
object might be, it has a function, getKey, that returns the key that uniquely identifies
the record. Similarly, it is unimportant whether a key is a number or a string or
whatever else; therefore, we require only that a key possesses a method, equals, that
checks the equality of itself to another key.
Table 3 presents the specifications that summarize our assumptions about databases,
records, and keys. Because we have provided partial (incomplete) specifications for
Record and Key, many different classes might implement the two specifications. For
example, we might write class Book to implement a Record so that we can build a
database of books, or we might write class BankAccount to implement a database of
bank accounts. Different classes of keys might also be written, if only because books
use different keys than do bank accounts.
Key’s specification deserves a close look: the specification is written as if keys are
objects (and not mere ints). For this reason, given two Key objects, K1 and K2, we
8.6. CASE STUDY: DATABASES
415
must write K1.equals(K2) to ask if the two keys have the same value. (This is similar
to writing S1.equals(s2) when comparing two strings, S1 and S2, for equality.) We
exploit this generality in the next section.
8.6.4
Implementation
The specifications for Record and Key make it possible to write a complete coding for
class Database without knowing any details about the codings for the records and
keys. Let’s consider the implementation of class Database.
The database’s primary attribute is an array that will hold the inserted records.
class Database must contain this field declaration:
private Record[] base;
The constructor method for the class will initialize the field to an array:
base = new Record[HOW_MANY_RECORDS];
where all the array’s elements have value null, because the array is empty. Records
will be inserted into the database one by one. To do an insert(Record r), follow this
algorithm:
1. Search array base to see if r is present. (More precisely, search base to see if a
record with the same key as r’s key is already present.)
2. If r is not in base, then search for the first element in base that is empty (that
is, holds value null).
3. Insert r into the empty element.
Each of the algorithm’s three steps requires more refinement: To fill in details in the
first step, say that we write a helper method, findLocation, which searches the array
for a record whose key equals k. The helper method might be specified like this:
/** findLocation is a helper method that searches base for a record
* whose key is k.
If found, the array index of the record within
* base is returned, else -1 is returned. */
private int findLocation(Key k)
Then, Step 1 of the algorithm is merely,
if ( findLocation(r.getKey()) == -1 )
because r.keyOf() extracts the key held within record r, and a result of -1 from
findLocation means that no record with the same key is already present.
Step 2 of the algorithm is clearly a searching loop, and we use the techniques from
Chapter 7 to write this loop, which searches for the first empty element in base where
a new record can be inserted:
416
boolean found_empty_place = false;
int i = 0;
while ( !found_empty_place && i != base.length )
// so far, all of base[0]..base[i-1] are occupied
{ if ( base[i] == null )
// is this element empty?
{ found_empty_place = true; }
else { i = i + 1; }
}
When this loop completes, i holds the index of the first empty element in base,
meaning that Step 3 is just base[i] = r, unless array base is completely filled with
records and there is no available space. What should we do in the latter situation?
Because Java arrays are objects, it is possible to construct a new array object that
is larger than the current array and copy all the elements from the current array to
the new array. Here is a standard technique for doing so:
// This constructs a new array twice as large as base:
Record[] temp = new Record[base.length * 2];
// Copy elements in array named by base
into temp:
for ( int j = 0; j != base.length; j = j + 1 )
{ temp[j] = base[j]; }
// Change base to hold address of temp:
base = temp;
The last assignment, base = temp, copies the address of the larger array into array
variable base, meaning that base once again holds the address of an array of records.
BeginFootnote: If you have studied the Java libraries, perhaps you discovered
class Vector, which behaves like an array but automatically expands to a greater
length when full. The technique that a Java Vector uses to expand is exactly the one
presented above. EndFootnote.
Figure 4 displays the completed version of insert.
Next, we consider how to delete an element from the database: The algorithm for
method, delete(Key k), would go,
1. Search array base to see if if a record with the key, k, is present.
2. If such a record is located, say, at element index, then delete it by assigning,
base[index] = null.
We use the helper method, findLocation, to code Step 1. We have this coding:
int index = findLocation(k);
if ( index != -1 )
{ base[index] = null; }
8.6. CASE STUDY: DATABASES
417
See Figure 4 for the completed method.
We can write the lookup method so that it merely asks findLocation to find the
desired record in the array. Again, see Figure 4.
To finish, we must write the findLocation method, which finds the record in
array base whose key is k. The algorithm is a standard searching loop, but there is a
small complication, because array base might have null values appearing in arbitrary
places, due to deletions of previously inserted records:
private int locationOf(Key k)
{ int result = -1; // recall that -1 means ‘‘not found’’
boolean found = false;
int i = 0;
while ( !found && i != base.length )
{ if ( base[i] != null
// is this element occupied?
&& base[i].keyOf().equals(k) ) // is it the desired record?
{ found = true;
result = i;
}
else { i = i + 1; }
}
return result; // return array index of the record found
}
Note the conditional statement in the loop’s body:
if ( base[i] != null
// is this array element occupied?
&& base[i].keyOf().equals(k) ) // is it the desired record?
{ ... } // we found the record at array element, i
else { i = i + 1; }
// the record is not yet found; try i + 1 next
The test expression first asks if there is a record stored in element, base[i], and if
the answer is true, then the element’s key (namely, base[i].keyOf()) is compared for
equality to the desired key, k.
The completed Database class appears in Figure 4. In addition to attribute base,
we define the variable, NOT FOUND, as a memorable name for the -1 answer used to
denote when a search for a record failed.
The coding presents several lessons:
• Although class Database appears to store records based on their keys, a more
primitive structure, an array, is used inside the class to hold the records. The
helper method, findLocation, does the hard work of using records’ keys as if
there were “indices.”
• Aside from the getKey and equals methods, nothing is known about the records
and keys saved in the database. This makes class Database usable in a variety
of applications, we see momentarily.
418
Figure 8.4: class Database
/** Database implements a database of records */
public class Database
{ private Record[] base;
// the collection of records
private int NOT FOUND = -1; // int used to denote when a record not found
/** Constructor Database initializes the database
* @param initial size - the size of the database */
public Database(int initial size)
{ if ( initial size > 0 )
{ base = new Record[initial size]; }
else { base = new Record[1]; }
}
/** findLocation is a helper method that searches base for a record
* whose key is k.
If found, the index of the record is returned,
* else NOT FOUND is returned. */
private int findLocation(Key k)
{ int result = NOT FOUND;
boolean found = false;
int i = 0;
while ( !found && i != base.length )
{ if ( base[i] != null && base[i].keyOf().equals(k) )
{ found = true;
result = i;
}
else { i = i + 1; }
}
return result;
}
/** find locates a record in the database based on a key
* @param key - the key of the desired record
* @return (the address of) the desired record;
* return null if record not found. */
public Record find(Key k)
{ Record answer = null;
int index = findLocation(k);
if ( index != NOT FOUND )
{ answer = base[index]; }
return answer;
}
...
419
8.6. CASE STUDY: DATABASES
Figure 8.4: class Database (concl.)
/** insert inserts a new record into the database.
* @param r - the record
* @return true, if record added; return false if record not added because
*
another record with the same key already exists in the database */
public boolean insert(Record r)
{ boolean success = false;
if ( findLocation(r.keyOf()) == NOT FOUND ) // r not already in base?
{ // find an empty element in base for insertion of r:
boolean found empty place = false;
int i = 0;
while ( !found empty place && i != base.length )
// so far, all of base[0]..base[i-1] are occupied
{ if ( base[i] == null )
// is this element empty?
{ found empty place = true; }
else { i = i + 1; }
}
if ( found empty place )
{ base[i] = r; }
else { // array is full! So, create a new one to hold more records:
Record[] temp = new Record[base.length * 2];
for ( int j = 0; j != base.length; j = j + 1 )
{ temp[j] = base[j]; } // copy base into temp
temp[base.length] = r;
// insert r in first free element
base = temp;
// change base to hold address of temp
}
success = true;
}
return success;
}
/** delete removes a record in the database based on a key
* @param key - the record’s key (identification)
* @return true, if record is found and deleted; return false otherwise
public boolean delete(Key k)
{ boolean result = false;
int index = findLocation(k);
if ( index != NOT FOUND )
{ base[index] = null;
result = true;
}
return result;
}
}
*/
420
• Because the array of records can be filled, we use a standard technique within
the insert method to build a new, larger array when needed.
8.6.5
Forms of Records and Keys
When we use class Database to hold records, we must write a class Record and a
class Key. The contents of these classes depends of course on the application that
requires the database, but we know from Table 3 that class Record must include a
getKey method and class Key must include an equals methods. Figure 5 shows one
such implementation: a record that models a simple bank account and a key that is
merely a single integer value.
The Record in Figure 5 has additional methods that let us do deposits and check
balances of a bank account, but the all-important getKey method is present, meaning
that the record can be used with class Database of Figure 4.
In order to conform to the requirements demanded by class Database, the integer
key must be embedded within a class Key. This means the integer is saved as a
private field within class Key and that the equals method must be written so that
it asks another key for its integer attribute, by means of an extra method, getInt.
Here is how we might use the classes in Figure 5 in combination with Figure 4.
Perhaps we are modelling a bank, and we require this database:
Database bank = new Database(1000);
When a customer opens a new account, we might ask the customer to select an integer
key for the account and make an initial deposit:
int i = ...some integer selected by the customer...;
int start_balance = ...some initial deposit by the customer...;
Key k1 = new Key(i);
boolean success = bank.insert( new Record(start_balance, k1) );
System.out.println("account inserted = " + success);
The fourth statement both constructs the new account and inserts it into the database.
Later, if the account must be fetched so that its balance can be checked, we can
find it and print its balance like this:
Record r = bank.find(k1); // recall that k1 is the account’s key
if ( r != null ) // did we successfully fetch the account?
{ System.out.println(r.getBalance()); }
To show that the database can be used in a completely different application, we
find in Figure 6 a new coding of record and key, this time for library books. Now,
class Record holds attributes for a book’s title, author, publication date, and catalog
number; the catalog number serves as the book’s key.
8.6. CASE STUDY: DATABASES
Figure 8.5: BankAccount Record and AccountKey
/** Record models a bank account with an identification key */
public class Record
{ private int balance; // the account’s balance
private Key id;
// the identification key
/** Constructor Record initializes the account
* @param initial amount - the starting account balance, a nonnegative.
* @param id - the account’s identification key */
public Record(int initial amount, Key id)
{ balance = initial amount;
key = id;
}
/** deposit adds money to the account.
* @param amount - the amount of money to be added, a nonnegative int */
public void deposit(int amount)
{ balance = balance + amount; }
/** getBalance reports the current account balance
* @return the balance */
public int getBalance() { return balance; }
/** getKey returns the account’s key
* @return the key */
public int getKey() { return key; }
}
/** Key models an integer key */
public class Key
{ private int k; // the integer key
/** Constructor Key constructs the Key
* @param i - the integer that uniquely defines the key */
public Key(int i) { k = i; }
/** equals compares this Key to another for equality
* @param c - the other key
* @return true, if this key equals k’s; return false, otherwise */
public boolean equals(Key c)
{ return ( k == c.getInt() ); }
/** getInt returns the integer value held within this key */
public int getInt() { return k; }
}
421
422
Figure 8.6: Book Record and CatalogNumber Key
/** Record models a Library Book */
public class Record
{ // the names of the fields describe their contents:
private Key catalog number;
private String title;
private String author;
private int publication date;
/** Constructor Record constructs the book.
* @param num - the book’s catalog number
* @param a - the book’s author
* @param t - the book’s title
*/
public Record(Key num, String a, String t, int date)
{ catalog number = num;
title = t;
author = a;
publication date = date;
is borrowed by someone = false;
}
/** getkey returns the key that identifies the record
* @return the key */
public Key getKey() { return catalog number; }
/** getTitle returns the book’s title
* @return the title */
public String getTitle() { return title; }
/** getAuthor returns the book’s author
* @return the author */
public String getAuthor() { return author; }
/** getDate returns the book’s publication date
* @return the date */
public int getDate() { return publication date; }
}
8.6. CASE STUDY: DATABASES
423
Figure 8.6: CatalogNumber Key (concl.)
/** Key models a Library-of-Congress-style id number,
* consisting of a letter code concatenated to a decimal number */
public class Key
{ private String letter code; // the letter code, e.g., "QA"
private double number code; // the number code, e.g., 76.884
/** Constructor Key constructs a catalog number
* @param letters - the letter code, e.g., "QA"
* @param num - the decimal number code, e.g., 76.884 */
public Key(String letters, double num)
{ letter code = letters;
number code = num;
}
/** equals returns whether the catalog number held within this object
* is identical to the catalog number held within c
* @param c - the other catalog number
* @return true, if this catalog number equals c; return false, otherwise */
public boolean equals(Key c)
{ String s = c.getLetterCode();
double d = c.getNumberCode();
return ( s.equals(letter code) && d == number code );
}
/** getLetterCode returns the letter code part of this catalog number
* @return the letter code, e.g., "QA" */
public String getLetterCode() { return letter code; }
/** getNumberCode returns the number code part of this catalog number
* @return the number code, e.g., "76.884" */
public double getNumberCode() { return number code; }
}
424
The structure of the catalog number is more complex: Its class Key holds a string
and a double, because we are using the U.S. Library of Congress coding for catalog
numbers, which requires a string and a fractional number. The class’s equals method
compares the strings and fractional numbers of two keys.
Here is a short code fragment that constructs a database for a library and inserts
a book into it:
Database library = new Database(50000);
Record book = new Book( new Key("QA", 76.8), "Charles Dickens",
"Great Expectations", 1860 );
library.insert(book);
// We might locate the book this way:
Key lookup_key = new Key("QA", 76.8);
book = library.find(lookup_key);
// We can delete the book, if necessary:
boolean deleted = library.delete(lookup_key);
As noted by the statement, Key lookup key = new Key("QA", 76.8), we can manufacture keys as needed to perform lookups and deletions.
It is a bit unfortunate that the bank account record in Figure 5 was named class
Record and that the book record in Figure 6 was also named class Record; more
descriptive names, like class BankAccount and class Book would be far more appropriate and would let us include both classes in the same application if necessary.
(Perhaps a database must store both bank accounts and books together, or perhaps
one single application must construct one database for books and another for bank
accounts.)
Of course, we were forced to use the name, class Record, for both records because
of the coding for class Database demanded it. The Java language lets us repair this
naming problem with a new construction, called a Java interface. We will return
to the database example in the next chapter and show how to use a Java interface
with class Database to resolve this difficulty.
Exercise
Write an application that uses class Database and classes Record and Key in Figure
5 to help users construct new bank accounts and do deposits on them.
8.7
Case Study: Playing Pieces for Card Games
Computerized games must model a game’s playing pieces, the playing board, and even
the game’s players. A classic example of “playing pieces” are playing cards. Even
8.7. CASE STUDY: PLAYING PIECES FOR CARD GAMES
425
if you have no interest in building or playing card games, modelling playing cards is
useful, because it shows how to model a set of pieces that must behave similarly.
Behavior of Cards and Decks
Perhaps we do not think of playing cards as having behaviors, but they are playing
pieces, and a playing piece, whether it be a chess pawn or a queen-of-hearts card, has
abilities or attributes that make it distinct from other playing pieces. A playing card
is a good example: It has a suit (diamonds, hearts, clubs, or spades) and count (ace
through ten, jack, queen, or king), e.g., hearts and queen. A card’s suit and count
are attributes and not behaviors in themselves, but a card game assigns behaviors
based on the attributes, e.g., in the card game, Blackjack, a card that has the count
of queen has the ability to score 10 points for the player who holds it.
In almost every game, playing pieces must be placed onto or into a container or
playing board—cards are collected into a container called a deck. Unlike the database
developed in the previous section, a card deck begins completely filled with cards. The
deck’s primary behavior is to surrender a card from itself whenever asked, until the
deck is finally empty.
Card games often use other forms of containers. For example, each player might
have its own personal container for cards, called a hand (of cards). A hand is initialized
so that it is empty and cards can be added to it, one by one.
For this small case study, we will design a subassembly consisting only of ordinary
playing cards and a deck to hold them. The architecture for the two classes is simple:
Deck
1
newCard(): Card
*
Card
suit
count
Specification
The specification for a card deck is presented in Table 7. Because it is a container for
cards, class Deck requires an attribute that is an array. The class’s methods include
one that returns a card and one that replies whether or not there are more cards to
return.
CardDeck collaborates with class Card, which we consider next. As noted earlier,
a playing card has two crucial attributes: its suit and its count, as seen in Table 8.
Of course, class Card will be coded to have accessor methods that return a card’s
suit and count.
Implementation
There is a technical issue that we should resolve before we write the two classes:
When people use playing cards, they perfer to use the names of suits and counts,
426
Figure 8.7: specification for class CardDeck
class CardDeck
Attribute
private Card[]
deck
Methods
newCard():
Card
moreCards():
boolean
models a deck of
cards
container for the
cards left in the
deck
return a card from
the deck; if the
deck is empty, return null
return true if more
cards remain in the
deck; return false,
otherwise
Figure 8.8: specification of class Card
class Card
Attributes
private String suit
private int count
models a playing card
the card’s suit, e.g., spades, hearts, diamonds, clubs
the card’s count, e.g., ace, 2, 3, ..., king
like “hearts,” “clubs,” “ace,” and “queen.” These are values, just like integers, and
we would like to use such values when we construct the playing cards, e.g., “new
Card(queen, hearts).”
We define values like “queen” and “hearts” in Java by declaring a public static
final variable for each such value, and place the public static final variables within
class Card. We see this in Figure 9, which presents class Card.
The public static final variables declared in class Card are public names that
can be used by the other components of an application; the names are used as if they
are new values. For example, other components can refer to the values, Card.ACE,
Card.DIAMOND, and so on. Now, we can say:
Card c = new Card(Card.HEARTS, Card.QUEEN)
to construct a queen-of-hearts object. And, we can ask,
if ( c.getCount() == Card.QUEEN ) { ... }
8.7. CASE STUDY: PLAYING PIECES FOR CARD GAMES
427
Figure 8.9: playing card
/** Card models a playing card */
public class Card
{ // definitions that one can use to describe the value of a card:
public static final String SPADES = "spades";
public static final String HEARTS = "hearts";
public static final String DIAMONDS = "diamonds";
public static final String CLUBS = "clubs";
public
public
public
public
static
static
static
static
final
final
final
final
int
int
int
int
ACE = 1;
JACK = 11;
QUEEN = 12;
KING = 13;
public static final int SIZE OF ONE SUIT = 13;
// These are the card’s attributes:
private String suit;
private int count;
/** Constructor Card sets the suit and count.
* @param s - the suit
* @param c - the count */
public Card(String s, int c)
{ suit = s;
count = c;
}
/** getSuit returns the card’s suit. */
public String getSuit()
{ return suit; }
/** getCount returns the card’s count. */
public int getCount()
{ return count; }
}
// how many cards in one suit
428
But remember that the public static final variables are merely names for integers
and strings. For example, since Card.QUEEN is the name of an integer, we can state,
if ( c.getCount() >= Card.QUEEN ) { ... }
because integers can be compared by greater-than.
As always, the keyword, public, means that the variable can be referenced by
other classes; the keyword, final, states that the variable name can not be changed
by an assignment; the value is forever constant. Finally, the keyword, static, ensures
that the variable is not copied as a field into any Card objects that are constructed
from class Card.
It is traditional to declare public static final variables with names that are all
upper-case letters.
We have used public static final variables already when in previous chapters
we used predefined Java-library values like Color.red (to paint on a graphics window)
and Calendar.DAY OF MONTH (to get the date from a Gregorian calendar object).
The remainder of class Card is simple—it contains a constructor method, which
initializes a card object’s suit and count, and two accessor methods, which return the
suit and count.
Next, we consider class CardDeck. Its attributes are
private int card_count; // how many cards remain in the deck
private Card[] deck = new Card[4 * Card.SIZE_OF_ONE_SUIT];
// invariant: elements deck[0]..deck[card_count - 1] hold cards
Array deck is constructed to hold the four suits’s worth of cards, where the quantity
of cards in a suit is the static variable defined in class Card.
The class’s constructor method must fill array deck with a complete collection of
cards. As Figure 8 shows, a helper method, createSuit knows how to generate one
complete suit of cards and insert it into the array; therefore, the constructor can be
written as
createSuit(Card.SPADES);
createSuit(Card.HEARTS);
createSuit(Card.CLUBS);
createSuit(Card.DIAMONDS);
The helper method is written so that it inserts the cards into the array in ascending
order. This is not the ideal state for the deck for playing a typical card game—the
deck’s cards are expected to be randomly mixed, or “shuffled.”
Rather than write a method that randomly mixes the elements in the array, we
can write the newCard method so that when it is asked to remove a card from the
array it randomly calculates an array index from which the card is extracted. The
algorithm might go like this:
8.7. CASE STUDY: PLAYING PIECES FOR CARD GAMES
429
1. Randomly calculate an integer in the range of 0 to card count - 1; call it index.
2. Remove the card at element deck[index]
3. Fill the empty element at index by shifting leftwards one element the cards in
the range, deck[index + 1] up to deck[card count - 1].
4. Decrease card count by one.
Step 1 can be done with the built-in Java method, Math.random(), which computes a
nonnegative pseudo-random fraction less than 1.0:
int index = (int)(Math.random() * card_count);
The computation, Math.random() * card count, generates a nonnegative fractional
number that must be less than card count (why?), and the cast, (int), truncates the
factional part, leaving an integer in the range, 0 to card count - 1.
Step 3 is performed with a simple loop:
for ( int i = index + 1; i != card_count; i = i + 1 )
// so far, cards from index+1 to i-1 have been shifted left
// in the array by one position
{ deck[i - 1] = deck[i]; }
The completed version of the method is in Figure 10.
Exercises
1. Write a test application that creates a new card, the queen of hearts, and then
asks the card what its suit and count are. The program prints the answers it
receives in the command window. Does your program print Queen or 12? What
solution do you propose so that the former is printed?
2. Write an application that creates a new deck of cards and asks the deck to deal
53 cards. (This is one more than the deck holds!) As the deck returns cards
one by one, print in the command window the count and suit of each card.
3. Write an application that lets a user request cards one by one, until the user
says, “stop.”
4. Card decks are used with card games. A typical card game has a dealer and
several players. A dealer owns a card deck and gives cards from the deck to
the players. The following specifications summarize the behavior of dealer and
player:
430
Figure 8.10: class CardDeck
/** CardDeck models a deck of cards. */
public class CardDeck
{
private int card count; // how many cards remain in the deck
private Card[] deck = new Card[4 * Card.SIZE OF ONE SUIT];
// invariant: elements deck[0]..deck[card count - 1] hold cards
/** Constructor CardDeck creates a new card deck with all its cards */
public CardDeck()
{ createSuit(Card.SPADES);
createSuit(Card.HEARTS);
createSuit(Card.CLUBS);
createSuit(Card.DIAMONDS);
}
/** newCard gets a new card from the deck.
* @return a card not used before, or return null, if no cards are left */
public Card newCard()
{ Card next card = null;
if ( card count == 0 )
{ System.out.println("CardDeck error: no more cards"); }
else { int index = (int)(Math.random() * card count); // randomly choose
next card = deck[index];
// once card is extracted from deck, shift other cards to fill gap:
for ( int i = index+1; i != card count; i = i + 1 )
// so far, cards from index+1 to i-1 have been shifted left
// in the array by one position
{ deck[i - 1] = deck[i]; }
card count = card count - 1;
}
return next card;
}
/** moreCards states whether the deck has more cards to give.
* @return whether the deck is nonempty */
public boolean moreCards()
{ return (card count > 0); }
/** createSuit creates a suit of cards for a new card deck. */
private void createSuit(String which suit)
{ for ( int i = 1; i <= Card.SIZE OF ONE SUIT; i = i + 1 )
{ deck[card count] = new Card(which suit, i);
card count = card count + 1;
}
}
}
431
8.8. TWO-DIMENSIONAL ARRAYS
class Dealer
models a dealer of
cards
Responsibilities
(methods)
dealTo(Player p)
gives cards, one
by one, to player
p, until p no
longer wants a
card
class Player
models a player of
a card game
Responsibilities
(methods)
wantsACard():
boolean
receiveCard(Card
c)
showCards():
Card[]
replies
as
to
whether another
card is desired
accepts card c and
adds it to its hand
returns an array
of the cards that it
holds
Write classes for these two specifications. (Write class Player so that a player
wants cards until it has exactly five cards.) Next, write a controller that creates
a dealer object and a player object. The controller tells the dealer to deal to
the player. Then, the controller asks the player to reveal its cards.
5. Revise the controller in the previous Exercise so that there is an array of 3
players; the dealer deals cards to each player in turn, and then all players show
their cards.
8.8
Two-Dimensional Arrays
Often, we display a collection of names and/or numbers in a table- or grid-like layout;
here is an example. Say that 4 candidates participate in a “national election,” where
the candidates compete for votes in 3 different regions of the country. (In the United
States, for example, there are 50 such regions—the 50 states of the union.) Therefore,
separate vote tallies must be kept for each of the 3 regions, and the votes are recorded
432
in a matrix:
Region
election
Candidate
0
1
2
3
0
0
0
0
0
1
0
0
0
0
2
0
0
0
0
The Candidates’ names are listed along the top of the matrix (for simplicity, we call
them Candidate 0, ..., Candidate 3), labelling the columns, and the regions are listed
along the left (they are Regions 0, 1, and 2), labelling the matrix’s rows—We say that
the matrix has three rows and four columns.
Thus, a vote in Region 1 for Candidate 3 would be recorded in the middle row
within its rightmost element:
Region
election
Candidate
0
1
2
3
0
0
0
0
0
1
0
0
0
1
2
0
0
0
0
Other votes are recorded this manner. When voting is completed, we can see which
candidate received the most votes overall by adding each of the four columns.
In programming, we use a two-dimensional array to model a matrix like the one
just seen. The above matrix is coded as a two-dimensional array in Java by stating
int[][] election = new int[3][4];
The data type of variable election is int[][] (“int array array”), indicating that
individual integers in the collection named election are uniquely identified by means
of two indexes. For example,
election[1][3]
identifies the integer element that holds the votes in Region 1 for Candidate 3.
The right-hand side of the initialization, new int[3][4], constructs the array object that holds the collection of twelve integers, organized into 3 rows of 4 elements,
each—three rows and four columns. It is helpful to visualize the collection as a matrix, like the ones just displayed. As usual for Java, the array’s elements are initialized
with zeros.
Let’s do some small exercises with array election: To add one more vote from
Region 1 for Candidate 3, we write
8.8. TWO-DIMENSIONAL ARRAYS
433
election[1][3] = election[1][3] + 1;
The phrase, election[1][3], indicates the specific element in the matrix.
To give every candidate in Region 2 exactly 100 votes, we would say
for ( int i = 0; i != 4; i = i + 1 )
{ election[2][i] = 100; }
Here, we use election[2][i] to indicate a cell within Row 2 of the matrix—the value
of i determines the specific cell in the row.
Similarly, to give Candidate 3 an additional 200 votes in every region, we would
say
for ( int i = 0; i != 3; i = i + 1 )
{ election[i][3] = election[i][3] + 200; }
To print each candidate’s grand total of votes, we write a nested for-loop that
totals each column of the matrix:
for ( int j = 0; j != 4; j = j + 1 )
{ int votes = 0;
for ( int i = 0; i != 3; i = i + 1 )
{ votes = votes + election[i][j]; }
System.out.println("Candidate " + j + " has " votes + " votes");
}
The previous for loop displays the standard pattern for examining each and every
element of a matrix. Yet another example is printing the total votes cast in each
region of the election, which requires that we total each row of the matrix:
for ( int i = 0; i != 3; i = i + 1 )
{ int total = 0;
for ( int j = 0; j != 4; j = j + 1 )
{ total = total + election[i][j]; }
System.out.println(total + " votes were cast in Region " + i);
}
In the above example, the order of the loops is reversed, because rows are traversed,
rather than columns.
Exercises
1. Create a two-dimensional array of integers, m, with 12 rows and 14 columns and
initialize it so that each m[i][j] holds i*j.
2. Create a two-dimensional array of Strings, m, with 7 rows and 5 columns and
initialize it so that each m[i][j] holds the string "Element " + i + " " + j.
434
3. Given this array, int[][] r = new int[4][4], and given this nested for-loop
that prints r’s contents,
for ( int i = 0; i != 4; i = i + 1 )
{ for ( int j = 0; j != 4; j = j + 1 )
{ System.out.print( r[i][j] + " " ); }
System.out.println();
}
write for-loops that initialize r so that the following patterns are printed:
(a) 1 0 0 0
1 2 0 0
1 2 3 0
1 2 3 4
(b) 1 2 3 4
0 3 4 5
0 0 5 6
0 0 0 7
(c) 1 0 1 0
0 1 0 1
1 0 1 0
0 1 0 1
4. Modify the application in Figure 1 to use the election array. (Of course, a vote
must be cast with a region number and a candidate number.)
8.9
Internal Structure of Two-Dimensional Arrays
We can imagine that a two-dimensional array is a matrix and draw it as such, but the
array’s true arrangement in computer storage is more complex: A two-dimensional
array is in fact an array of arrays. To see this, reconsider
int[][] election = new int[3][4];
435
8.9. INTERNAL STRUCTURE OF TWO-DIMENSIONAL ARRAYS
Here is its layout in computer storage:
a1 : int[3] [ ]
int[ ] [ ] election == a1
a2 : int[4]
0
a2
1
a3
2
a4
a3 : int[4]
a4 : int[4]
0
1
2
3
0
1
2
3
0
1
2
3
0
0
0
0
0
0
0
0
0
0
0
0
In Java, a two-dimensional array is built in terms of multiple one-dimensional array
objects. The diagram shows that the object at address a1 is a one dimensional array
that holds the addresses of the matrix’s rows.
For example, when we write, election[1][3], this is computed to a1[1][3], that
is, the array object at a1 is the one that will be indexed. Since element 1 within
object a1 is a3, the indexing is computed to a3[3], which identifies element 3 in the
array object at address a3.
Fortunately, this detailed address lookup is performed automatically and is hidden
from the programmer—it is indeed safe to pretend that election is the name of a
matrix of integer elements. But sometimes the knowledge of the matrix’s underlying
structure is exposed in the code that traverses the array, as in these for-loops, which
print the total votes for each region:
for ( int i = 0; i != 3; i = i + 1 )
{ int[] region = election[i];
// region holds the address of a row
int total = 0;
for ( int j = 0; j != 4; j = j + 1 )
{ total = total + region[j]; }
System.out.println(total + " votes were cast in Region " + i);
}
This exposes that each row of matrix election is itself a (one-dimensional) array.
Alas, we cannot treat a matrix’s columns in a similar way.
Another consequence of the storage layout is that the “length” of a matrix is the
number of rows it possesses. For the above example,
election.length
computes to 3. To learn the number of columns in a matrix, we ask for the length of
one of the matrix’s rows. For example,
436
election[0].length
computes to 4.
Finally, some care must be taken when asking for the number of columns of a
two-dimensional array. Consider this odd example:
double[][] ragged = new double[4][];
double[0] = new double[2];
double[2] = new double[1];
double[3] = new double[0];
The first statement constructs an array variable, ragged, that will have four rows and
an undetermined number of columns; it looks like this in storage:
a1 : double[4][]
double[][] ragged ==a1
0
null
1
null
2
null
3
null
The following three statements construct one-dimensional array objects and assign
them to ragged’s elements, giving us
a1 : double[4][]
double[][] ragged ==a1
a2 : double[2]
0
a2
1
null
2
a3
3
a4
a3 : double[1]
0
1
0
0.0
0.0
0.0
a4 : double[0]
This is an example of a “ragged array”—a two-dimensional array whose rows have
different lengths. For example, an election where different candidates are eligible in
different regions of a country might be modelled by a ragged array.
Notice that ragged[0].length equals 2, whereas ragged[2].length equals 1, whereas
ragged[1].length generates an exception (run-time error), because there is no array
of any length at the element. The array at element ragged[3] truly has length 0,
meaning that there are no elements at all that can be indexed in that row.
8.10. CASE STUDY: SLIDE-PUZZLE GAME
8.10
437
Case Study: Slide-Puzzle Game
Two-dimensional arrays prove helpful for modelling game boards in computer games.
As an example, we design and build a slide puzzle, which is a game board that holds
numbered pieces that are moved by the user, one piece at a time.
Behavior
The behavior of the puzzle game goes like this: the puzzle starts in this configuration:
The user instructs the puzzle to move a piece by typing a number (in this configuration, only 1 or 4 would be allowed), and the game responds by moving the requested
438
Figure 8.11: architecture of model for slide puzzle
SlidePuzzleBoard
private PuzzlePiece[][] board
move(int w): boolean
1
PuzzlePiece
* private int face value
piece:
The user may request similar moves for as long as she wishes.
Architecture, Specification, and Implementation of the Model
The application that implements the puzzle will need a model subassembly that
models the puzzle board and the pieces that move about the board; the model’s
architecture, presented in Figure 11, is simple and includes an initial specification of
the two classes that must be written.
The SlidePuzzleBoard’s sole responsibility is to move a puzzle piece when asked,
e.g., move(4) asks the board to move the piece whose face is labelled by 4.
A PuzzlePiece has only the attribute of the number on its face; it has no responsibilities. For this reason, its coding is simple and appears in Figure 12.
Next, we consider writing class SlidePuzzleBoard. The class’s primary attribute
is the array that holds the (addresses of the) puzzle pieces:
private PuzzlePiece[][] board; // the array that holds the pieces
// one position on the board must be an empty space:
private int empty_row;
439
8.10. CASE STUDY: SLIDE-PUZZLE GAME
Figure 8.12: class PuzzlePiece
/** PuzzlePiece defines a slide-puzzle playing piece */
public class PuzzlePiece
{ private int face value; // the value written on the piece’s face
/** Constructor PuzzlePiece creates a piece
* @param value - the value that appears on the face of the piece */
public PuzzlePiece(int value)
{ face value = value; }
/** valueOf returns the face value of the piece */
public int valueOf()
{ return face value; }
}
private int empty_col;
// representation invariant:
board[empty_row][empty_col] == null
Exactly one element within array board must be empty (hold null), and it is convenient to declare two fields, empty row and empty col, to remember the coordinates of
the empty space.
Method move(w) must move the piece labelled by integer w into the empty space.
For the move to succeed, the piece labelled by w must be adjacent to the empty space.
This means the algorithm for move(int w) must perform the appropriate checks:
1. If the playing piece labelled by w is immediately above the empty space (marked
by empty row and empty col), or if it is immediately below the empty space, or
immediately to the left or right of the empty space,
2. Then, move the piece labelled by w to the empty space, and reset the values of
empty row and empty col to be the position formerly occupied by w’s piece.
To write Step 1, we can make good use of this helper function, which looks at position,
row, col, to see if the piece labelled w is there:
/** found returns whether the piece at position
private boolean found(int w, int row, int col)
row, col
is labeled
w */
Then, Step 1 can be coded to ask the helper function to check the four positions
surrounding the empty space whether piece w is there. See Figure 13 for the completed
coding of method move.
The board’s constructor method creates the board array and fills it with newly
created puzzle pieces. Finally, we add a method that returns the contents of the
440
puzzle board. (This will be useful for painting the board on the display) The contents
method returns as its result the value of the board. It is best that contents not return
the address of its array board. (If it did, the client that received the address could
alter the contents of the array!) Instead, a new two-dimensional array is created, and
the addresses of the playing pieces are copied into the new array.
The Application’s Architecture
The model subassembly neatly fits into a standard model-view-controller architecture,
which we see in Figure 14.
Implementing the Controller
Figure 14’s class diagram specifies the methods for the controller and view components. Consider the controller first; its play method should let the user repeatedly
enter moves to the slide puzzle. The algorithm is merely a loop, which
1. tells the PuzzleWriter component to paint the current state of the puzzle;
2. reads the next move from the user;
3. tells the SlidePuzzleBoard to attempt the move
The controller and its move method is presented in Figure 15.
Implementing the View
The output-view class, PuzzleWriter, has the responsibility of painting the contents of the puzzle board in a graphics window. Because the controller, class
PuzzleController, sends its repaint requests via method, displayPuzzle, the displayPuzzle
method merely asks the view to repaint itself. The hard work of painting is done by
paintComponent, which must display the puzzle pieces as a grid. A nested for-loop
invokes the helper method, paintPiece, which paints each of the pieces, one by one.
Figure 16 shows the coding.
Exercises
1. Test class SlidePuzzleBoard by creating an object, board, from it and immediately asking its contents method for the the board. Display the board with
these loops:
PuzzlePiece[][] r = board.contents();
for ( int i = 0; i != r.length; i = i+1 )
{ for ( int j = 0; j != r[i].length; j = j+1 )
{ if ( r[i][j] == null )
8.10. CASE STUDY: SLIDE-PUZZLE GAME
Figure 8.13: class SlidePuzzleBoard
/** SlidePuzzleBoard models a slide puzzle. */
public class SlidePuzzleBoard
{ private int size;
// the board’s size
private PuzzlePiece[][] board; // the array that holds the pieces
// one position on the board must be an empty space:
private int empty row;
private int empty col;
// representation invariant: board[empty row][empty col] == null
/** Constructor SlidePuzzleBoard constructs the initial puzzle, which has
*
the pieces arranged in descending numerical order.
* @param s - the size of the puzzle, a positive integer (e.g., s==4 means
*
the puzzle is 4 x 4 and will have pieces numbered 15, 14, ..., 1) */
public SlidePuzzleBoard(int s)
{ size = s;
board = new PuzzlePiece[size][size];
// create the individual pieces and place on the board in reverse order:
for ( int num = 1; num != size * size; num = num + 1 )
{ PuzzlePiece p = new PuzzlePiece(num);
int row = num / size;
int col = num % size;
// set p in a ‘‘reversed position’’ on the board:
board[size - 1 - row][size - 1 - col] = p;
}
// remember the location on the board where initially there is no piece:
empty row = size - 1;
empty col = size - 1;
}
/** contents returns the current state of the puzzle
* @return a matrix that contains the addresses of the pieces */
public PuzzlePiece[][] contents()
{ PuzzlePiece[][] answer = new PuzzlePiece[size][size];
for ( int i = 0; i != size; i = i + 1 )
{ for ( int j = 0; j != size; j = j + 1 )
{ answer[i][j] = board[i][j]; }
}
return answer;
}
...
441
442
Figure 8.13: class SlidePuzzleBoard (concl.)
/** move moves a piece into the blank space, provided it is a legal move.
* @param w - the face value of the piece that one wishes to move
* @return true, if the piece labelled w was moved into the empty space;
* return false if the piece cannot be moved into the empty space */
public boolean move(int w)
{ int NOT FOUND = -1;
int row = NOT FOUND; // row and col will remember where piece w lives
int col = NOT FOUND;
// try to find w adjacent to the empty space on the board:
if ( found(w, empty row - 1, empty col) )
{ row = empty row - 1;
col = empty col;
}
else if ( found(w, empty row + 1, empty col) )
{ row = empty row + 1;
col = empty col;
}
else if ( found(w, empty row, empty col - 1) )
{ row = empty row;
col = empty col - 1;
}
else if ( found(w, empty row, empty col + 1) )
{ row = empty row;
col = empty col + 1;
}
if ( row != NOT FOUND )
{ // move the piece into the empty space:
board[empty row][empty col] = board[row][col];
// mark the new empty space on board:
empty row = row;
empty col = col;
board[empty row][empty col] = null;
}
return row != NOT FOUND;
}
/** found returns whether the piece at position row, col is labeled
private boolean found(int v, int row, int col)
{ boolean answer = false;
if ( row >= 0 && row < size && col >= 0 && col < size )
{ answer = ( board[row][col].valueOf() == v ); }
return answer;
}
}
v */
443
8.10. CASE STUDY: SLIDE-PUZZLE GAME
Figure 8.14: class diagram for slide-puzzle program
PuzzleController
play()
JOptionPane
SlidePuzzleBoard
private PuzzlePiece[][] board 1
move(int w): boolean
PuzzleWriter
displayPuzzle()
printError(String s)
PuzzlePiece
* private int face value
{ System.out.print("X "); }
else { System.out.print( r[i][j].valueOf() + " " ); }
}
System.out.println();
}
Next, use the object’s move method to ask the board to move several numbers.
Display the board resulting from each move.
2. Use the for-loops in the previous Exercise in an alternate implementation of
class PuzzleWriter that displays the puzzle in the console window.
3. Test class PuzzleWriter by creating these objects,
SlidePuzzleBoard board = new SlidePuzzleBoard(3);
PuzzleWriter writer = new PuzzleWriter(board, 3);
writer.displayPuzzle();
where you use this “dummy” class:
public class SlidePuzzleBoard
{ private int size;
public SlidePuzzleBoard(int s) { size = s; }
public PuzzlePiece[][] contents()
{ PuzzlePiece[][] answer = new PuzzlePiece[size][size];
int k = 0;
for ( int i = 0; i != size; i= i+1 )
{ for ( int j = 0; j != size; j = j+1 )
{ answer[i][j] = new PuzzlePiece(k);
444
Figure 8.15: controller for slide puzzle program
import javax.swing.*;
/** PuzzleController controls the moves of a slide puzzle */
public class PuzzleController
{ private SlidePuzzleBoard board; // model
private PuzzleWriter writer;
// output view
/** Constructor PuzzleController initializes the controller
* @param b - the model, the puzzle board
* @param w - the output view */
public PuzzleController(SlidePuzzleBoard b, PuzzleWriter w)
{ board = b;
writer = w;
}
/** play lets the user play the puzzle */
public void play()
{ while ( true )
{ writer.displayPuzzle();
int i = new Integer
(JOptionPane.showInputDialog("Your move:")).intValue();
boolean good outcome = board.move(i);
if ( !good outcome )
{ writer.printError("Bad move--puzzle remains the same."); }
}
}
}
/** SlidePuzzle implements a 4 x 4 slide puzzle. Input to the program
* is a sequence of integers between 1..15. The program never terminates. */
public class SlidePuzzle
{ public static void main(String[] args)
{ int size = 4; // a 4 x 4 slide puzzle
SlidePuzzleBoard board = new SlidePuzzleBoard(size);
PuzzleWriter writer = new PuzzleWriter(board, size);
PuzzleController controller = new PuzzleController(board, writer);
controller.play();
}
}
445
8.10. CASE STUDY: SLIDE-PUZZLE GAME
Figure 8.16: output-view class for puzzle game
import java.awt.*; import javax.swing.*;
/** PuzzleWriter displays the contents of a slide puzzle */
public class PuzzleWriter extends JPanel
{ private SlidePuzzleBoard board; // the board that is displayed
private int size;
// the board’s size
// the size of one playing piece, in pixels
private int piece size = 30;
// the panel’s width and height
private int panel width;
private int panel height;
/** Constructor PuzzleWriter builds the graphics window.
* @param b - the slide puzzle that is displayed
* @param s - the size of the slide puzzle, e.g., 4 means
public PuzzleWriter(SlidePuzzleBoard b, int s)
{ board = b;
size = s;
panel width = piece size * size + 100;
panel height = piece size * size + 100;
JFrame my frame = new JFrame();
my frame.getContentPane().add(this);
my frame.setTitle("Slide Puzzle");
my frame.setSize(panel width, panel height);
my frame.setVisible(true);
}
4 x 4
/** paintPiece draws piece p at position i,j in the window */
private void paintPiece(Graphics g, PuzzlePiece p, int i, int j)
{ int initial offset = piece size;
int x pos = initial offset + (piece size * j);
int y pos = initial offset + (piece size * i);
if ( p != null )
{ g.setColor(Color.white);
g.fillRect(x pos, y pos, piece size, piece size);
g.setColor(Color.black);
g.drawRect(x pos, y pos, piece size, piece size);
g.drawString(p.valueOf() + "", x pos + 10, y pos + 20);
}
else { g.setColor(Color.black);
g.fillRect(x pos, y pos, piece size, piece size);
}
}
...
*/
446
Figure 8.16: output-view class for puzzle game (concl.)
/** paintComponent displays the puzzle in the frame. */
public void paintComponent(Graphics g)
{ g.setColor(Color.yellow);
g.fillRect(0, 0, panel width, panel height);
PuzzlePiece[][] r = board.contents();
for ( int i = 0; i != size; i= i+1 )
{ for ( int j = 0; j != size; j = j+1 )
{ paintPiece(g, r[i][j], i, j); }
}
}
/** displayPuzzle displays the current state of the slide puzzle. */
public void displayPuzzle()
{ this.repaint(); }
/** printError displays an error message.
* @param s - the error message */
public void printError(String s)
{ JOptionPane.showMessageDialog(null, "PuzzleWriter error: " + s ); }
}
k = k + 1;
}
}
return answer;
}
}
8.11
Testing Programs with Arrays
Programs with arrays prove notoriously difficult to test thoroughly, because we often
use an arithmetic expression as an array index, and the expression might compute to
an unacceptable integer. Here is an example: We have this method, which attempts
to exchange two adjacent array elements:
/** exchange swaps the values in elements
public void exchange(int[] r, int i)
{ int temp = r[i];
r[i] = r[i - 1];
r[i - 1] = temp;
}
r[i]
and
r[i-1] */
8.11. TESTING PROGRAMS WITH ARRAYS
447
We wish to verify that the method behaves properly for all possible arguments. We
have success for simple test cases, like this one,
int[] test0 = new int[10];
test0[3] = 3;
exchange(test0, 4);
But what other tests should we attempt? To answer this, we should list all the
indexings of array r that appear in the method—they are r[i] and r[i - 1]—and
we should predict the range of values that the index expressions, i and i - 1, might
have. Remember that the values must fall in the range, 0 to r.length - 1. Now, do
they?
A bit of thought lets us invent this test,
int[] test1 = new int[10];
test0[0] = 3;
exchange(test0, 0);
which generates an exception, because i - 1 has a value that is invalid for array r.
Another test case,
int[] test1 = new int[10];
test0[9] = 3;
exchange(test0, 10);
shows that the attempted indexing, r[i], leads to an error.
The tests make clear that parameter i must be greater than zero and less than
the length of the array. We modify the method to read,
public void exchange(int[] r, int i)
{ if ( i > 0 && i < r.length )
{ int temp = r[i];
r[i] = r[i - 1];
r[i - 1] = temp;
}
else { ... announce there is a problem ... }
}
There is, alas, one more test that exposes an error that goes beyond index-value
calculation:
int[] test2 = null;
exchange(test2, 1);
This invocation of exchange leads to an exception when the array argument is referenced. If we are uncertain that we can validate that all the method’s invocations are
with proper arrays, then we must add one more test:
448
public void exchange(int[] r, int i)
{ if ( r != null && i > 0 && i < r.length )
{ int temp = r[i];
r[i] = r[i - 1];
r[i - 1] = temp;
}
else { ... announce there is a problem ... }
}
Testing the values of array indexes becomes harder still when loops are used to
examine an array’s elements, because it is crucial that the loop starts and terminates
appropriately. Consider again the first loop we saw in this chapter, which attempts
to select the largest number in an array:
double high_score = score[0];
for ( int i = 1; i <= 5; i = i + 1 )
{ if ( score[i] > high_score )
{ high_score = score[i]; }
}
System.out.println(high_score);
We note that the only array indexing is score[i], and we readily see that the range
of values denoted by index i is 0 to 5. But this range is sensible only if we are certain
that the length of array score is at least 6—indeed, it should equal 6. It is better to
use score.length in the loop’s termination test:
double high_score = score[0];
for ( int i = 1; i < score.length;
{ if ( score[i] > high_score )
{ high_score = score[i]; }
}
System.out.println(high_score);
i = i + 1 )
This ensures that the loop correctly examines all the elements of the array to select
the high score.
The general strategy for testing a component that uses arrays goes as follows:
1. Validate that every array-typed variable, r, is indeed assigned an array object as
its value;
2. For every indexing expression, r[e], calculate the range of values to which e
might evaluate, testing these values and especially 0 and r.length.
8.12
Summary
Here are the main points to remember from this chapter:
449
8.12. SUMMARY
New Constructions
• one-dimensional array (from Figure 1):
int num_candidates = 4;
int[] votes = new int[num_candidates];
...
votes[v] = votes[v] + 1;
• array initialization statement:
int[] r = {1, 2, 4, 8, 16, 32};
• array-length attribute:
int[] r = new int[6];
r[0] = 1;
for ( int i = 1; i < r.length;
{ r[i] = r[i - 1] * 2; }
i = i + 1 )
• two-dimensional array (from Figure 13):
private PuzzlePiece[][] board;
...
board = new PuzzlePiece[size][size];
..
board[size - 1 - row][size - 1 - col] = p;
• public static final variable (from Figure 9):
public static final int QUEEN = 12;
New Terminology
• array: an object that holds a collection of values, called elements, of the same
data type (e.g., a collection of integers or a collection of JPanel objects). The
elements are named or indexed by nonnegative integers. (See the above examples.)
• one-dimensional array: an array whose collection is a “sequence” of values, that
is, each element is named by a single integer index, e.g., votes[2] names the
third integer value in the sequence of values held by votes.
450
• database: a large collection of data values that must be maintained by means
of insertions, retrievals, and deletions of the data values.
• key: the identity code used to retrieve a data value saved in a database.
• final variable: a variable whose value cannot be changed after it is initialized.
• two-dimensional arrays: “an array of arrays” that is typically drawn as a
matrix or grid. Each element is named by a pair of integer indexes, e.g.,
election[i][j]. The first index is the row index, and the second index is the
column index, where the terms refer to the depiction of the array as a matrix.
Points to Remember
• In Java, an array is an object that must be constructed with the new keyword,
e.g., int[] r = new int[6], which creates an array that can hold 6 integers,
indexed from 0 to 5.
• Individual array elements are indexed by expressions and are used like ordinary
variables, e.g., r[i + 1] = 2 * r[i].
• The elements of an array can be (the addresses of) objects as well, e.g., Card[]
deck = new Card[52] is an array that can hold 52 Card objects. When the array
is constructed, it holds no objects—all elements have value null. The objects
held in the array must be explicitly constructed and assigned to the array’s
elements, e.g., deck[0] = new Card(Card.HEARTS, Card.QUEEN).
• Arrays can be constructed with multiple dimensions—a one-dimensional array
is a sequence of elements; a two-dimensional array is a matrix—an array of
arrays. The rows of a two-dimensional array can have different lengths.
8.13
Programming Projects
1. Extend the simplistic vote-counting application in Figure 1 in the following
ways:
(a) Each candidate has a name, an address, and an age. The application reads
this information first, saves it in objects, and uses the information to count
votes, which are now submitted by typing the candidates’ names.
(b) The election becomes a national election in 3 regions. Make the application
display the total vote counts for each region as well as the total vote counts
for each candidate.
451
8.13. PROGRAMMING PROJECTS
2. The classic algorithm for calculating the prime numbers in the range 2..n is
due to Eratosthenes:
initialize the set of primes, P, to be all integers in 2..n;
for ( i = 2; 2*i <= n; i = i+1 )
{ remove from P all multiples of i };
print the contents of P;
Implement this algorithm by modelling P as an array of booleans: boolean[] P
= new boolean[n + 1]. (Hint: P[i] == false means that i is definitely not a
prime.)
3. With the help of arrays, we can improve the output views for bar graphs, point
graphs, and pie charts from the Programming Projects in Chapter 5. Reprogram
each of the following.
(a) Here is the new specification for class BarGraphWriter:
class BarGraphWriter
helps a user draw bar graphs
Constructor
BarGraphWriter(int x pos,
int y pos, int x length, int
y height, String top label)
draw the x- and y-axes of the graph.
The pair, x pos, y pos, state the coordinates on the window where the two
axes begin. The x-axis extends from
x pos, y pos to the right for x length
pixels; the y-axis extends upwards from
x pos, y pos for y height pixels. The
label placed at the top of the y-axis is
top label. (The label placed at the bottom of the y axis is always 0.)
Method
setBar(String label, int
height, Color c)
draws a new bar in the graph, to the
right of the bars already drawn, where
the label underneath the bar is label,
the height of the bar, in pixels, is
height, and the bar’s color is c.
(b) Here is the new specification for class PointGraphWriter:
452
class PointGraphWriter
helps a user draw a graph of plotted
points
Constructor
PointGraphWriter(int x pos,
int y pos, int axis length,
String x label, String
y label)
draw the the vertical and horizontal
axes of the graph, such that the intersection point of the axes lies at position
x pos, y pos. Each axis has the length,
axis length pixels. The beginning labels of both the x- and y-axes are 0; the
label at the top of the y-axis is y label,
and the label at the end of the x-axis is
x label.
Method
setPoint(int x increment,
int y height)
plot another point of the graph, so that
its x-position is x increment pixels to
the right of the last point plotted, and
its y-position is y height on the y-axis.
(c) Here is the revised specification for class PieChartWriter:
class PieChartWriter
helps a user draw a pie chart
Method
setSlice(String label, int
amount, Color c)
add a new “slice” to the chart, such that
amount indicates the amount of the slice,
and c is the slice’s color. The label is
printed to the right of the pie, and it is
printed in the color, c.
4. Another form of coding words is by means of a transposition code, which encodes
a message by transposing its letters. The simplest form of transposition code
works as follows:
(a) A string, s, is read; say that s has length n.
(b) The smallest square matrix that can hold n characters is created.
(c) The characters in s are copied one by one into the columns of the matrix.
(d) The output is the rows of the matrix.
For example, the input string, abcdefghijklmn, would be stored into a 4-by-4
matrix as follows:
a e i m
b f j n
8.13. PROGRAMMING PROJECTS
453
c g k x
d h l x
The output would be the words, aeim, bfjn, cgkx, dhlx.
Write an application that implements this algorithm. Next, write an application
that decodes words produced by the algorithm.
5. To continue the development of the preceding Project, here is a slightly more
sophisticated transposition code, based on a numeric key:
(a) An integer “key” of m distinct digits is read, and a string, s of length n is
read.
(b) The smallest matrix with m columns that can hold n characters is created.
(c) The characters in s are copied one by one into the columns of the matrix.
(d) The digits in the key are sorted, and the columns of the array are accordingly rearranged.
(e) The output is the rows of the matrix.
For example, for input key 421 and abcdefghijklmn, a 5-by-3 matrix would be
built:
4 2 1
----a f k
b g l
c h m
d i n
e j x
Since 421 “sorted” is 124, the columns are rearranged to appear:
1 2 4
----k f a
l g b
m h c
n i d
x j e
and the output are the words, kfa, lgb, mhc, nid, and xje.
Write programs to encode and decode messages with this algorithm.
454
6. For each of the following entities, design a class that models the entity. Most
of the entity’s attributes are listed; feel free to add more. Design appropriate
methods.
(a) a library book: the book’s name, its author, its catalog (id) number, and
the id number of the person (if anyone) who has borrowed the book, the
due date for the book to be returned, and the number of times the book
has been borrowed.
(b) a patron’s library information: the patron’s name, address, id number, and
the catalog numbers of all books currently loaned to the person (maximum
of 6).
(c) an appointment record: the appointment’s date, time of day, and topic
(d) an inventory record: the name of a sales item, its id number, its wholesale
price, its retail price, and the quantity in stock.
(e) a purchase record: the id number of the purchaser, the (id numbers of the)
items and quantities ordered of each, and the means of payment/
(f) a purchaser (customer) record: the id number, name, and address of a
customer, the id numbers of the customer’s outstanding orders, and the
customer’s purchase history for the past 12 months
(g) an email message: the address of its sender, the address of its receiver, the
message’s subject, and its body (text)
7. Use the classes you defined in the previous exercise plus class Database from
Figure 4 to build the following applications:
(a) A library application, which maintains a database for the library’s books
and a database for the library’s borrowers. Both databases must be used
when books are borrowed and returned.
(b) A business accounting application, which uses databases of inventory records,
purchases, and purchasers. The databases are used when customers purchase items.
(c) An email postal service, which allows multiple users to login, send, and
receive email messages to/from one another.
8. Here are the rules for a simple card game: A player tries to obtain two cards
that total the highest possible score, where a card’s “score” is its count. (For
simplicity, ace is worth 1, 2 is worth 2, ..., king is worth 13.) The dealer gives
each player 2 cards. A player can surrender at most one card and accept a third
card as a replacement. Then, all players must reveal their hands.
8.13. PROGRAMMING PROJECTS
455
Write an application that lets a computerized dealer and two computerized
players play this game. (Make the computerized player smart enough that it
surrenders a card that has count 6 or less.)
Next, modify the application so that one human player plays against one computerized player.
Finally, modify the application so that two human players play against each
other; only the dealer is computerized.
9. Here are the rules for playing the card game, “21”: A player tries to collect
cards whose total score is 21 or as close as possible. The player with the highest
score not exceeding 21 wins. (If the player’s score is higher than 21, the player
is “busted” and loses.) A card’s has a point value based on its count (e.g., a
four of clubs is valued 4), but face cards are valued 10, and we will say that an
ace is valued 11.
Initially, each player receives two cards from the dealer. Then, each player can
request additional cards, one at a time, from the dealer. After all the players
have received desired additional cards, the players reveal their hands.
Write an application that lets a computerized dealer, a human player, and a
computerized player play 21. The computerized player should be smart enough
to request additional cards as long as the player’s total score is 16 or less.
10. Revise the “21” card game as follows:
(a) The dealer is also a player; therefore, the dealer must deal herself a hand.
(b) An ace has a value of either 1 or 11, based on the discretion of the player
who holds the card.
(c) In some casinos, a dealer deals from two decks of cards. Alter the application to deal alternately from two decks.
(d) If a player’s first two cards have identical counts (e.g., two eights or two
queens), the player can “split” the cards into two distinct hands and continue with two hands.
(e) The game lets the players play multiple rounds. In particular, this means
that the deck of cards must be “reshuffled” with the cards not in the
players’ hands when the deck is emptied of cards in the middle of a round.
11. Write an application that plays tic-tac-toe (noughts and crosses) with the user.
12. Here is a standard memory game, known as “Concentration” or “Husker Du”:
A matrix is filled with pairs of letters, and the letters are covered. Next, two
players take turns trying to discover the letter pairs: A player uncovers two
letters; if the letters match, the letters remain uncovered, the player scores one
456
point, and she is allowed to uncover two more letters. If the letters fail to match,
they are recovered and the next player tries. The players take turns until all
the letter pairs are uncovered. The player with the most points wins.
Build a computerized version of this game.
13. Write an application that lets two human players play checkers.
14. Write an animation that lets the computer play the game of “Life.” The game
goes as follows: the user specifies the size of game board, a matrix, and also
gives the starting positions of where some pebbles (“cells”) live. Every second,
the computer updates the board, creating and removing cells, according to the
following rules:
• an empty board position that is surrounded by exactly three cells gets a
cell placed on it. (The new cell “comes to life.”)
• a board position occupied by a cell retains the cell if the position was surrounded by exactly 2 other cells. (Otherwise the cell disappears—“dies”—
due to “loneliness” or “overcrowding.”)
Here is an example of two seconds of the animation on a 5-by-5 board, where
an X denotes a cell and . denotes an empty space:
.X.XX
X...X
XXX..
..X.X
X...X
...XX
....X
=> X.X..
X.X..
...X.
...XX
....X
=> ...X.
..XX.
.....
15. Write an appointments manager program. The program stores and retrieves
appointments that are listed by date and hour of day for one full week. (For
simplicity, assume that at most one appointment can be scheduled per hour.)
Include input commands for inserting appointments, listing the appointments
for a given day, deleting appointments, and printing appointments.
16. Write an application that reserves seats in an airplane based on input requests
in the following format:
• number of seats desired (should be seated in the same row, next to one
another, if possible)
• first class or economy
• aisle or window seat (if two or more seats requested, one seat should meet
this preference)
8.13. PROGRAMMING PROJECTS
457
17. Write a program that plays the card game, “War”: There are two players; one
is human, the other is computerized. Here are the rules: each player gets a
hand of 10 cards. The players play 10 rounds; each round goes as follows:
(a) A player places one of the cards from her hand face up on the table.
(b) The other player does the same.
(c) The player whose card has the larger value of the two cards takes all the
cards on the table and places them in her “winnings pile.” (Note: the
definition of “value” is given below. A “winnings pile” is a stack of cards
that is not used any more in the game. Each player keeps her own winnings
pile.) The winning player must start the next round.
(d) If the two cards on the table have the same value (and this is called a
“War”) , then all the cards on the table remain there for the next round.
The player who started this round must start the next round.
After all ten rounds are played, the winner is the player with more cards in her
winnings pile.
Here is the definition of “value”: regardless of suit, 2 has the lowest value, then
3, then 4, etc., then 9, then 10, then jack, then queen, then king, then ace.
There is a minimal amount of strategy that a player uses to win at War. Your
strategy for the computerized player must be at least this smart: (1) If the
computerized player plays the first card of a round, then any card remaining in
the computerized player’s hand can be played. (2) If the computerized player
plays the second card of a round, then the computerized player plays a card in
its hand whose value is greater than or equal to the value of the card that the
human just played. If the computerized player has no such card, then it plays
any card in its hand.
18. Choose another card game of your choosing, e.g., “Hearts” or “Crazy Eights”
and model it as a computer game. Or, chose a game that uses dice and implement it as a computer game.
19. Write an application that performs bin packing: The input consists of a sequence of “packages” whose sizes are coded as nonnegative integers along with
a sequence of “bins” whose capacities are are coded also by an integer. (For simplicity, we assume that all bins have the same capacity.) The program assigns
each package to a bin such that no bin’s capacity is exceeded. The objective
is to use the minimum number of bins to hold all the packages. Attempt these
implementations:
(a) Smallest packages first: The packages are sorted by size and smallest packages are used first.
458
(b) Largest packages first: The packages are sorted by size and largest packages
are used first.
(c) Random filling: The packages are used in the order they appear in the
input.
(Hint: read the Supplement section on sorting.)
After you have implemented all three programs, perform case studies to determine when one strategy performs better than another. This problem is famous
because there is no efficient algorithm for best filling the bins.
8.14
Beyond the Basics
8.14.1 Sorting
8.14.2 Searching
8.14.3 Time-Complexity Measures
8.14.4 Divide-and-Conquer Algorithms
8.14.5 Formal Description of Arrays
These optional sections expand upon the concepts presented in this chapter. In particular, we emphasize using arrays to sort collections of numbers and efficiently search
for numbers in a sorted collection.
8.14.1
Sorting
When an array is used as a database, where elements are fetched and updated frequently, there is a distinct advantage to ordering the elements by their keys—it becomes far easier to locate an element. The process of ordering an array’s elements is
called sorting.
Algorithms for sorting have a rich history, and we cannot do justice here. Instead,
we focus upon the development of two traditional sorting methods, selection sort and
insertion sort. To simplify the algorithms that follow, we work with arrays of integers,
where we sort the elements so that they are ordered in value from smallest integer
to largest. (Of course, we can use the same techniques to sort elements by their key
values.)
The idea behind selection sort is simple: Locate the least integer in the array, and
move it to the front. Then, find the next least integer, and move it second to the
front. Repeat this process until all integers have been selected in order of size. The
algorithm that sorts array r in this manner goes
459
8.14. BEYOND THE BASICS
for ( i = 0; i != r.length; i = i+1 )
{ Find the least element in r within the range
r[i] to r[r.length-1]; say that it is at r[j].
Exchange r[i] with r[j].
}
Here is the algorithm in action. Say that we have this array, r:
r
0
1
2
3
4
11
8
-2
7
10
When selection sorting starts, its loop finds the least element in the range r[0]..r[4]
at index 2 and exchanges the elements at indexes 0 and 2:
r
0
1
2
3
4
-2
8
11
7
10
The second loop iteration locates the least element in the range r[1]..r[4] at index
3, and the elements at indexes 1 and 3 are exchanged:
r
0
1
2
3
4
-2
7
11
8
10
The algorithm next considers the elements in range r[2]..r[4] and so on until it
reaches this end result:
r
0
1
2
3
4
-2
7
8
10
11
Figure 15 shows the method.
There is more than one way to sort an array; a second classic approach, called
insertion sort, rearranges elements the way most people sort a hand of playing cards:
Start with the first card (element), then take the second card (element) and insert it
either before or after the first card, so that the two cards are in order; then take the
third card and insert it in its proper position so that the three cards are ordered, and
so on. Eventually, all the cards are inserted where they belong in the ordering.
The algorithm based on this idea is simply stated as:
for ( i=1; i < r.length; i = i+1 )
{ Insert r[i] in its proper place within the already sorted prefix,
r[0]..r[i-1].
}
460
Figure 8.17: selection sort
/** selectionSort sorts the elements of its array parameter
* @param r - the array to be sorted */
public void selectionSort(int[] r)
{ for ( int i = 0; i != r.length; i = i+1 )
// invariant: subarray r[0]..r[i-1] is sorted
{ int j = findLeast(r, i, r.length-1); // get index of least element
int temp = r[i];
r[i] = r[j];
r[j] = temp;
}
}
/** findLeast finds the index of the least element in r[start]..r[end]
* @param r - the array to be searched
* @param start - the starting element for the search
* @param end - the ending element for the search
* @return the index of the smallest element in r[start]..r[end] */
private int findLeast(int[] r, int start, int end)
{ int least = start;
for ( int i = start+1; i <= end; i = i+1 )
// invariant: least is index of least element in range r[start]..r[i-1]
{ if ( r[i] < r[least] ) { least = i; } }
return least; }
If we apply the algorithm to the example array, r, seen above,
r
0
1
2
3
4
11
8
-2
7
10
we see that the algorithm first inserts the 8 where it belongs with respect to the 11:
r
0
1
2
3
4
8
11
-2
7
10
This makes the prefix, r[0]..r[1], correctly sorted. Next, the -2 must be inserted
in its proper place with respect to the sorted prefix:
r
0
1
2
3
4
-2
8
11
7
10
To make room for -2 at its proper position, r[0], the two elements, 8 and 11, must
be shifted one position to the right. Now, r[0]..r[2] is correctly sorted. The last
two elements are inserted similarly.
8.14. BEYOND THE BASICS
461
Figure 8.18: insertion sort
/** insertionSort sorts the elements of its array parameter
* @param r - the array to be sorted */
public static void insertionSort(int[] r)
{ for ( int i = 1; i < r.length; i = i+1 )
// invariant: prefix r[0]..r[i-1] is sorted
{ int v = r[i]; // v is the next element to insert into the prefix
int j = i;
while ( j != 0 && r[j-1] > v )
// invariants:
// (i) the original prefix, r[0]..r[i-1],
//
is now arranged as r[0]..r[j-1], r[j+1]..r[i];
// (ii) all of r[j+1]..r[i] are greater than v
{ r[j] = r[j-1];
j = j-1;
}
r[j] = v;
}
}
Figure 16 show the insertion sorting method. The method’s most delicate step is
searching the sorted prefix to find a space for v—the while-loop searches from right
to left, shifting values one by one, until it encounters a value that is not larger than v.
At all iterations, position r[j] is reserved for v; when the iterations stop, v is inserted
at r[j].
Exercises
1. Try selection sort and insertion sort on these arrays: {4, 3, 2, 2}; {1, 2, 3,
4}; {1}; { } (the array of length 0).
2. Explain which of the two sorting methods might finish faster when the array to
be sorted is already or nearly sorted; when the array’s elements are badly out
of order.
3. Explain why the for-loop in method selectionSort iterates one more time than
it truly needs.
4. Why is the test expression, j != 0, required in the while-loop in method insertionSort?
5. Another sorting technique is bubble sort: over and over, compare pairs of adjacent elements and exchange them if the one on the right is less than the one
462
on the left. In this way, the smaller elements move like “bubbles” to the left
(“top”) of the array. The algorithm goes:
boolean did_exchanges = true;
while ( did_exchanges )
{ did_exchanges = false;
for ( int i = 1; i < r.length; i = i+1 )
{ If r[i] < r[i-1},
then exchange them and assign did_exchanges = true.
}
}
Program this sorting method.
8.14.2
Searching
Once an array is sorted, it becomes simpler to locate an element within it—rather
than examining items one by one, from left to right, we can start searching in the
middle, at approximately where the item might appear in the sorted collection. (This
is what we do when we search for a word in a dictionary.) A standard searching
algorithm, called binary search, exploits this idea.
Given a sorted array of integers, r, we wish to determine where a value, item, lives
in r. We start searching in the middle of r; if item is not exactly the middle element,
we compare what we found to it: If item is less than the middle element, then we
next search the lower half of the array; if item is greater than the element, we search
the upper half of the array. We repeat this strategy until item is found or the range
of search narrows to nothing, which means that item is not present.
The algorithm goes
Set searching = true.
Set the lower bound of the search to be 0 and the upper bound of the
search to be the last index of array, r.
while ( searching && lower bound <= upper bound )
{ index = (lower bound + upper bound) / 2;
if
( item == r[index] ) { found the item---set searching = false; }
else if ( item < r[index] ) { reset upper bound = index-1; }
else { reset lower bound = index+1; }
}
Figure 17 shows the method, which is a standard example of the searching pattern of
iteration.
If we searched for the item 10 in the sorted array r seen in the examples in
the previous section, the first iteration of the loop in binarySearch gives us this
463
8.14. BEYOND THE BASICS
Figure 8.19: binary search
/** binarySearch searches for an item in a sorted array
* @param r - the array to be searched
* @param item - the desired item in array r
* @return the index where item resides in r; if item is not
*
found, then return -1 */
public int binarySearch(int[] r, int item)
{ int lower = 0;
int upper = r.length - 1;
int index = -1;
boolean searching = true;
while ( searching && lower <= upper )
// (1) searching == true implies item is in range r[lower]..r[upper],
//
if it exists in r at all.
// (2) searching == false implies that r[index] == item.
{ index = (lower + upper) / 2;
if ( r[index] == item )
{ searching = false; }
else if ( r[index] < item )
{ lower = index + 1; }
else { upper = index - 1; }
}
if ( searching )
{ index = -1; } // implies lower > upper, hence item not in r
return index;
}
configuration:
r
0
1
2
3
4
-2
7
8
10
11
int lower == 0
int upper == 4
int index == 2
The search starts exactly in the middle, and the loop examines r[2] to see if it is 10.
It is not, and since 10 is larger than 8, the value found at r[2], the search is revised
as follows:
r
0
1
2
3
4
-2
7
8
10
11
int lower == 3
int upper == 4
int index == 3
Searching the upper half of the array, which is just two elements, moves the search
to r[3], which locates the desired item.
Notice that a linear search, that is,
int index = 0;
464
boolean searching = true;
while ( searching && index != r.length )
{ if ( r[index] == item )
{ searching = false; }
else { index = index + 1; }
}
would examine four elements of the array to locate element 10. The binary search
examined just two. Binary search’s speedup for larger arrays is enormous and is
discussed in the next section.
Binary search is a well-known programming challenge because it is easy to formulate incorrect versions. (Although the loop in Figure 17 is small, its invariant
suggests that a lot of thought is embedded within it.) Also, small adjustments lead
to fascinating variations. Here is a clever reformulation, due to N. Wirth:
public int binarySearch(int[] r, int item)
{ int lower = 0;
int upper = r.length-1;
int index = -1;
while ( lower <= upper )
// (1) lower != upper+2 implies that item
is in range
//
r[lower]..r[upper], if it exists in r at all
// (2) lower == upper+2 implies that r[index] == item
{ index = (lower + upper) / 2;
if ( item <= r[index] )
{ upper = index - 1; };
if ( item >= r[index] )
{ lower = index + 1; };
}
if ( lower != upper+2 )
{ index = -1; }
return index;
}
This algorithm merges variable searching in Figure 17 with the lower and upper
bounds of the search so that the loop’s test becomes simpler. This alters the loop
invariant so that the discovery of item is indicated by lower == upper+2.
Both searching algorithms must terminate, because the expression, upper-lower
decreases in value at each iteration, ensuring that the loop test will eventually go
false.
Exercises
1. Use the binary search method in Figure 17 on the sorted array, {1, 2, 2, 4,
6}: Ask the method to search for 6; for 2; for 3. Write execution traces for these
searches.
465
8.14. BEYOND THE BASICS
2. Here is a binary search method due to R. Howell:
public int search(int[] r, int item)
{ int answer = -1;
if ( r.length > 0 )
{ int lower = 0;
int upper = r.length;
while ( upper - lower > 1 )
// item is in r[lower]..r[upper-1], if it is in
{ int index = (lower + upper) / 2;
if ( r[index] > item )
{ upper = index; }
else { lower = index; }
}
if ( r[lower]== item ) { answer = lower; }
}
return answer;
}
r
Explain why the invariant and the termination of the loop ensure that the
method returns a correct answer. Explain why the loop must terminate. (This
is not trivial because the loop makes one extra iteration before it quits.)
8.14.3
Time-Complexity Measures
The previous section stated that binary search computes its answer far faster than
does linear search. We can state how much faster by doing a form of counting analysis
on the respective algorithms. The analysis will introduce us to a standard method
for computing the time complexity of an algorithm. We then apply the method to
analyze the time complexity of selection sort and insertion sort.
To analyze a searching algorithm, one counts the number of elements the algorithm
must examine to find an item (or to report failure). Consider linear search: If array
r has, say, N elements, we know in the very worst case that a linear search must
examine all N elements to find the desired item or report failure. Of course, over
many randomly generated test cases, the number of elements examined will average
to about N/2, but in any case, the number of examinations is directly proportional
to the the array’ length, and we say that the algorithm has performance of order N
(also known as linear) time complexity.
For example, a linear search of an array of 256 elements will require at most 256
examinations and 128 examinations on the average.
Because it halves its range of search at each element examination, binary search
does significantly better than linear time complexity: For example, a worst case
binary search of a 256-element array makes one examination in the middle of the 256
466
elements, then one examination in the middle of the remaining 128 elements, then
one examination in the middle of the remaining 64 elements, and so on—a maximum
of only 9 examinations are required!
We can state this behavior more precisely with a recursive definition. Let E(N)
stand for the number of examinations binary search makes (in worst case) to find an
item in an array of N elements.
Here is the exact number of examinations binary search does:
E(N ) = 1 + E(N/2), for N > 1
E(1) = 1
The first equation states that a search of an array with multiple elements requires
an examination of the array’s middle element, and assuming the desired item is not
found in the middle, a subsequent search of an array of half the length. An array of
length 1 requires just one examination to terminate the search.
To simplify our analysis of the above equations, say the array’s length is a power
of 2, that is, N = 2M , for some positive M. (For example, for N = 256, M is 8. Of course,
not all arrays have a length that is exactly a power of 2, but we can always pretend
that an array is “padded” with extra elements to make its length a power of 2.)
Here are the equations again:
E(2M ) = 1 + E(2M −1 ), for M > 0
E(20 ) = 1
After several calculations with this definition (and a proof by induction—see the
Exercises), we can convince ourselves that
E(2M ) = M + 1
a remarkably small answer!
We say that the binary search algorithm has order log N (or logarithmic) time
complexity. (Recall that log N, or more precisely, log2 N, is N’s base-2 logarithm, that
is, the exponent, M, such that 2M equals N. For example, log 256 is 8, and log 100
falls between 6 and 7.) Because we started our analysis with the assumption that N
= 2M , we conclude that
E(N ) = (log N ) + 1
which shows that binary search has logarithmic time complexity.
It takes only a little experimentation to see, for large values of N, that log N is
significantly less than N itself. This is reflected in the speed of execution of binary
search, which behaves significantly better than linear search for large-sized arrays.
8.14. BEYOND THE BASICS
467
Analysis of Sorting Algorithms
Of course, binary search assumes that the array it searches is sorted, so we should
calculate as well the time complexity of the sorting algorithms we studied. The two
factors in the performance of a sorting algorithm are (i) the number of comparisons
of array elements, and (ii) the number of exchanges of array elements. If either of
these measures is high, this slows the algorithm.
Consider selection sort first (Figure 15); it locates and exchanges the smallest
element, then the next smallest element, and so on. For an array of length N, it
uses N-1 comparisons to find the smallest element, N-2 comparisons to find the next
smallest element, and so on. The total number of comparisons is therefore
(N-1) + (N-2) + ...downto... + 2 + 1
From number theory (and an induction proof), we can discover that this sequence
totals
N * (N - 1)
------------2
that is, (1/2)N 2 − (1/2)N . When N has a substantial positive value, only the N 2 factor
matters, so we say that the algorithm has order N 2 (quadratic) time complexity.
Algorithms with quadratic time complexity perform significantly slower than logarithmic and linear algorithms, and this slowness can be annoying when N is very
large (e.g., for N equals 100, N2 is 10,000).
It is easy to see that selection sort does exactly N-1 exchanges of elements—a
linear time complexity—so the exchanges are not the costly part of the algorithm.
Next, we consider insertion sort (Figure 16); recall that it shifts elements, one
by one, from right to left into their proper places. In worst case, insertion sort
encounters an array whose elements are in reverse order. In this case, the algorithm’s
first iteration makes one comparison and one exchange; the second iteration makes
two comparisons and two exchanges; and so on. The total number of comparisons
and exchanges are the same, namely,
1 + 2 + ... + (N-2) + N-1
This is the same sequence we encountered in our analysis of selection sort, so we
conclude that insertion sort also has quadratic time complexity.
Although selection sort’s time complexity is stable across all possible permutations
of arrays to be sorted, insertion sort executes much faster when it is given an almost
completely sorted array to sort. This is because insertion sort shifts elements only
when they are out of order. For example, if insertion sort is given an array of length
N+1 where only one element is out of order, it will take only order N (linear) time to
shift the element to its proper position. For this reason, insertion sort is preferred for
sorting almost-sorted arrays.
468
In contrast, insertion sort does badly at exchanging elements when sorting an arbitrary array—it makes order N2 exchanges, whereas selection sort limits its exchanges
to at worst order N. Therefore, selection sort is preferred if there is substantial difficulty in moving elements of the array. (But this is not normally the case for Java
arrays, because the elements of a Java array are either primitive values, like numbers,
or addresses of objects. These values are easy to exchange.)
Exercises
1. To get intuition about time complexities, calculate the values of N, 5*N, log N,
N2 , and (1/2)(N 2 ) − (1/2)N for each of the following values of N: 4; 64; 128; 512;
1024; 16384.
Then, reexamine the time complexities of the searching and sorting algorithms
and describe how the algorithms would behave on arrays of size N, for the above
values of N. (To give some perspective to the analysis, pretend that your computer is very slow and takes 0.1 seconds to perform a comparison or exchange
operation.)
2. Modify class Database in Figure 3 so that its insert method sorts the base
array after a new record is added. (Warning—watch for null values in the
array!) Because the contents of base are already sorted when a new element is
inserted, does this simplify the sorting process? What form of sorting is better
for this application—selection sort or insertion sort?
Next, modify locationOf so that it uses binary search.
3. Perform time-complexity analyses of the following methods:
(a) For Figure 1, Chapter 7, measure the time complexity of summation(N),
depending on the value of N. Count the number of assignments the method
makes.
(b) For Figure 3, Chapter 7, measure the time complexity of findChar(c,
s), depending on the lengths of string s. Count the number of charAt
operations the method makes.
(c) For Figure 13, measure the time complexity of paint, depending on the
size of array that must be painted. Count the number of invocations of
paintPiece.
4. Our time-complexity analyses are a bit simplistic: a precise time-complexity
analysis would count every operation that a computer’s processor makes, that
is, every arithmetic operation, every comparison operation, every variable reference, every assignment, every method invocation, every method return, etc.
Perform such a detailed analysis for the algorithms in the previous Exercise; for
8.14. BEYOND THE BASICS
469
linear search; for binary search. Are your answers significantly different than
before?
5. Use mathematical induction to prove that E(2M ) = M + 1, for all nonnegative
values of M. This requires that you prove these two claims:
• basis step: E(20 ) = 0 + 1
• induction step: Assume that E(2i ) = i + 1 holds true. Use this to prove
E(2i+1 ) = (i + 1) + 1.
6. Use mathematical induction to prove that (N-1) + (N-2) + ...downto... +
2 + 1 equals (1/2)(N 2 ) − (1/2)N , for all values of N that are 2 or larger. This
requires that you prove these two claims:
• basis step: (2-1) + (2-2) + ...downto... + 2 + 1 equals (1/2)(2 2 )−(1/2)2.
(Hint: read the sequence, 1 + ...downto... + 1 as being just the oneelement sequence, 1.)
• induction step: Assume that (i-1) + (i-2) + ...downto... + 2 + 1 equals
(1/2)(i2 )−(1/2)i. Use this to prove ((i+1)-1) + ((i+1)-2) + ...downto...
+ 2 + 1 equals (1/2)((i + 1)2 ) − (1/2)(i + 1).
8.14.4
Divide-and-Conquer Algorithms
In the previous section, we saw that the binary search algorithm has a significantly
better time complexity than the linear search algorithm. The time measurement for
binary search was expressed by a recursive definition, which suggests that a recursion
might be a factor in binary search’s performance. This is indeed the case—binary
search is an example of a style of recursion known as divide and conquer, which we
study in this section.
First, Figure 18 shows binary search written in recursive style. To search an entire
array, a, for a value, v, the method is invoked as binarySearch(a, v, 0, a.length-1).
The method clearly shows that, at each recursive invocation, the segment searched is
divided in half. Eventually, the desired item is found or the segment is divided into
nothing.
The method in the Figure is an example of a divide-and-conquer algorithm, so
called because the algorithm divides its argment, the array, into smaller segments at
each invocation. The divide-and-conquer pattern uses recursion correctly, because
each recursive invocation operates on parameters (the array segments) that grow
smaller until they reach a stopping value (size 0).
470
Figure 8.20: binary search by recursion
/** binarySearch searches for an item within a segment of a sorted array
* @param r - the array to be searched
* @param item - the desired item
* @param lower - the lower bound of the segment
* @param upper - the upper bound of the segment
* @return the index where item resides in r[lower]..r[upper];
* return -1, if item is not present in the segment of r */
public int binarySearch(int[] r, int item, int lower, int upper)
{ int answer = -1;
if ( lower <= upper )
{ int index = (lower + upper) / 2;
if ( r[index] == item )
{ answer = index; }
else if ( r[index] < item )
{ answer = binarySearch(r, item, index + 1, upper); }
else { answer = binarySearch(r, item, lower, index - 1); }
}
return answer;
}
Merge sort
Sorting can be accomplished with a divide-and-conquer algorithm, which proceeds as
follows: To sort a complete array, r,
1. Divide the array into two smaller segments, call them s1 and s2.
2. Sort s1.
3. Sort s2.
4. Merge the two sorted segments to form the completely sorted array.
The merge step goes as follows: Say that you have a deck of cards you wish to sort.
You divide the deck in half and somehow sort each half into its own pile. You merge
the two piles by playing this “game”: Turn over the top card from each pile. (The
top cards represent the lowest-valued cards of the two piles.) Take the lower-valued
of the two cards, form a new pile with it, and turn over the next card from the pile
from which you took the lower-valued card. Repeat the game until all the cards are
moved into the third pile, which will be the entire deck, sorted.
Figure 19 shows the method based on this algorithm, called merge sort. Like
the recursive version of binary search, mergeSort is first invoked as mergeSort(a, 0,
8.14. BEYOND THE BASICS
471
Figure 8.21: merge sort
/** mergeSort builds a sorted array segment
* @param r - the array
* @param lower - the lower bound of the segment to be sorted
* @param upper - the upper bound of the segment to be sorted
* @return a sorted array whose elements are those in r[lower]..r[upper]
public int[] mergeSort(int[] r, int lower, int upper)
{ int[] answer;
if ( lower > upper ) // is it an empty segment?
{ answer = new int[0]; }
else if ( lower == upper ) // is it a segment of just one element?
{ answer = new int[1];
answer[0] = r[lower];
}
else // it is a segment of length 2 or more, so divide and conquer:
{ int middle = (lower + upper) / 2;
int[] s1 = mergeSort(r, lower, middle);
int[] s2 = mergeSort(r, middle+1, upper);
answer = merge(s1, s2);
}
return answer;
}
*/
/** merge builds a sorted array by merging its two sorted arguments
* @param r1 - the first sorted array
* @param r2 - the second sorted array
* @return a sorted array whose elements are exactly those of r1 and r2 */
private int[] merge(int[] r1, int[] r2)
{ int length = r1.length + r2.length;
int[] answer = new int[length];
int index1 = 0;
int index2 = 0;
for ( int i = 0; i != length; i = i+1 )
// invariant: answer[0]..answer[i-1] is sorted and holds the elements of
//
r1[0]..r1[index1-1] and r2[0]..r2[index2-1]
{ if (
index1 == r1.length
|| ( index2 != r2.length && r2[index2] < r1[index1] ) )
{ answer[i] = r2[index2];
index2 = index2 + 1;
}
else { answer[i] = r1[index1];
index1 = index1 + 1;
}
}
return answer;
}
472
a.length-1) to indicate that all the elements in array a should be sorted. The method
returns a new array that contains a’s elements reordered.
Method mergeSort first verifies that the segment of the array it must sort has at
least two elements; if it does, the segment is divided in two, the subsegments are
sorted, and merge combines the two sorted subarrays into the answer.
The time complexity of merge sort is is significantly better than the other sorting
algorithms seen so far; we consider the number of comparisons the algorithm makes.
(The analysis of element exchanges goes the same.)
First, we note that merge(r1, r2) makes as many comparisons as there are elements in the shorter of its two array parameters, but it will be convenient to overestimate and state that no more than r1.length + r2.length comparisons are ever
made.
Next, we define the comparisons made by mergeSort on an array of length N as
the quantity, C(N):
C(N) = C(N / 2) + C(N / 2) + N,
C(1) = 0
if
N > 1
The first equation states that the total comparisons to sort an array of length 2 or
more is the sum of the comparisons needed to sort the left segment, the comparisons
needed to sort the right segment, and the comparisons needed to merge the two sorted
segments. Of course, an array of length 1 requires no comparisons.
Our analysis of these equations goes simpler if we we pretend the array’s length
is a power of 2, that is N = 2M , for some nonnegative M:
C(2M ) = C(2M −1 ) + C(2M −1 ) + 2M
C(20 ) = 0
These equations look like the ones discovered in the analysis of binary search.
Indeed, if we divide both sides of the first equation by 2M , we see the pattern in the
binary search equation:
C(2M )
C(2M −1 )
=
+1
2M
2M −1
As with the binary search equation, we can conclude that
C(2M )
=M
2M
When we multiply both sides of the above solution by 2M , we see that
C(2M ) = 2M ∗ M
and since N = 2M , we have that
C(N ) = N ∗ logN
8.14. BEYOND THE BASICS
473
We say that merge sort has order N log N time complexity. Such algorithms perform
almost as well as linear-time algorithms, so our discovery is significant.
Alas, mergeSort suffers from a significant flaw: When it sorts an array, it creates
additional arrays for merging—this will prove expensive when sorting large arrays.
The method in Figure 19 freely created many extra arrays, but if we are careful, we
can write a version of mergeSort that creates no more than one extra array the same
size as the original, unsorted array. For arrays that model large databases, even this
might be unacceptable, unfortunately.
Quicksort
A brilliant solution to the extra-array problem was presented by C.A.R. Hoare in
the guise of the “quicksort” algorithm. Like merge sort, quicksort uses the divideand-conquer technique, but it cleverly rebuilds the sorted array segments within the
original array: It replaces the merge step, which occurred after the recursive invocations, with a partitioning step, which occurs before the recursive invocations.
The idea behind partitioning can be understood this way: Say that you have a
deck of unsorted playing cards. You partition the cards by (i) choosing a card at
random from the deck and (ii) creating two piles from the remaining cards by placing
those cards whose values are less than the chosen card in one pile and placing those
cards whose values are greater than the chosen card in the other.
It is a small step from partitioning to sorting: If you sort the cards in each pile,
then the entire deck is sorted by just concatenating the piles. This is a classic divideand-conquer strategy and forms the algorithm for quicksort. Given an array, r, whose
elements are numbered r[lower] to r[upper]:
1. Rearrange (partition) r into two nonempty subarrays so that there is an index,
m, such that all the elements in r[lower]..r[m] are less than or equal to all
elements in r[m+1]..r[upper].
2. Sort the partition r[lower]..r[m].
3. Sort the partition r[m+1]..r[upper].
The end result must be the array entirely sorted.
Figure 20 gives the quickSort method, which is invoked as quickSort(r, 0,
r.length-1), for array r. The hard work is done by partition(r, lower, upper),
which partitions the elements in the range r[lower]..r[upper] into two groups. The
method uses the element at r[lower] as the “pivot” value for partitioning as it scans
the elements from left to right, moving those values less than the pivot to the left
side of the subarray. Once all the elements are scanned, the ones less than the pivot
form the first partition, and the ones greater-or-equal to the pivot form the second
partition.
474
Figure 8.22: quicksort
/** quickSort sorts an array within the indicated bounds
* @param r - the array to be sorted
* @param lower - the lower bound of the elements to be sorted
* @param upper - the upper bound of the elements to be sorted
public void quickSort(int[] r, int lower, int upper)
{ if ( lower < upper )
{ int middle = partition(r, lower, upper);
quickSort(r, lower, middle);
quickSort(r, middle+1, upper);
}
}
*/
/** partition rearranges an array’s elements into two nonempty partitions
* @param r - an array of length 2 or more
* @param lower - the lower bound of the elements to be partitioned
* @param upper - the upper bound of the elements to be partitioned
* @return the index, m, such that all elements in the nonempty partition,
*
r[lower]..r[m], are <= all elements in the nonempty partition,
*
r[m+1]..r[upper] */
private int partition(int[] r, int lower, int upper)
{ int v = r[lower];
// the ‘‘pivot’’ value used to make the partitions
int m = lower - 1; // marks the right end of the first partition
int i = lower + 1; // marks the right end of the second partition
while ( i <= upper )
// invariant: (i) all of r[lower]..r[m] are < v
//
(ii) all of r[m+1]..r[i-1] are >= v,
//
and the partition is nonempty
{ if ( r[i] < v )
{ // insert r[i] at the end of the first partition
// by exchanging it with r[m+1]:
m = m + 1;
int temp = r[i];
r[i] = r[m];
r[m] = temp;
}
i = i + 1;
}
if ( m == lower - 1 ) // after all the work, is the first partition empty?
{ m = m + 1; }
// then place r[lower], which is v, into it
return m;
}
475
8.14. BEYOND THE BASICS
It is essential that both of the partitions created by partition are nonempty. For
this reason, a conditional statement after the while-loop asks whether the partition
of elements less than the pivot is empty. If it is, this means the pivot is the smallest
value in the subarray, no exchanges were made, and the pivot remains at r[lower].
In this case, the pivot value itself becomes the first partition.
We can see partitioning at work in an example. Say that we invoke quickSort(r,
0 ,6), which immediately invokes partition(r, 0, 6) for the array r shown below.
The variables in partition are initialized as follows:
0
1
2
3
4
5
6
r
5
8
4
1
7
3
9
m
i
int v == 5
int i == 0
int m == -1
We position m and i under the array to indicate the variables’ values. The pivot value
is r[0]—5. Values less than the pivot will be moved to the left; the other values will
move to the right.
Within partition’s while-loop, i moves right, searching for a value less than 5; it
finds one at element 2. This causes r[2] to be moved to the end of the first partition—
it is exchanged with r[m+1], and both m and i are incremented. Here is the resulting
situation:
r
0
1
2
3
4
5
6
4
8
5
1
7
3
9
m
i
A check of the loop invariant verifies that the elements in the range r[0] to r[m] are
less than the pivot, and the values in the range r[m+1] to r[i-1] are greater-or-equal
to the pivot.
Immediately, i has located another value to be moved to the first partition. An
exchange is undertaken between r[1] and r[3], producing the following:
r
0
1
2
3
4
5
6
4
1
5
8
7
3
9
m
i
The process continues; one more exchange is made. When the method finishes, here
is the partitioned array:
r
0
1
2
3
4
5
6
4
1
3
8
7
5
9
m
Since m is 2, the partitions are r[0]..r[2] and r[3]..r[6].
Once a partitioning step is complete, quickSort recursively sorts the two partitions. This causes each subarray, r[0]..r[2] and r[3]..r[6], to be partitioned and
476
recursively sorted. (That is, the invocation, quicksort(r, 0, 2) invokes partition(r,
0, 2), and quicksort(r, 3, 6) invokes partition(r, 3, 6), and so on.) Eventually,
partitions of size 1 are reached, stopping the recursive invocations.
For quickSort to perform at its best, the partition method must generate partitions that are equally sized. In such a case, each recursive invocation of quickSort
operates on an array segment half the size of the previous one, and the time complexity is the same as mergesort—order N log N. But there is no guarantee that partition
will always break an array into two equally sized partitions—if the pivot value, v, is
the largest (or smallest) value in an array segment of size N, then partition creates
one partition of size 1 and one of size N-1. For example, if array r was already sorted
r
0
1
2
3
4
5
6
1
3
4
5
7
8
9
and we invoked partition(r, 0, 6), then partition would choose the pivot to be 1
and would create the partitions r[0] and r[1]..r[6]. The subsequent recursive invocation to quickSort(r, 1, 6) causes another such partitioning: r[1] and r[2]..r[6].
This behavior repeats for all the recursive calls.
In a case as the above, quickSort degenerates into a variation of insertion sort
and operates with order N2 time complexity. Obviously, if quickSort is applied often
to sorted or almost-sorted arrays, then partition should choose a pivot value from
the middle of the array rather than from the end (see the Exercises below). Studies of
randomly generated arrays shows that quicksort behaves, on the average, with order
N log N time complexity.
Exercises
1. To gain understanding, apply iterative binarySearch in Figure 17 and recursive
binarySearch in Figure 18 to locate the value, 9, in the array, int[] r = {-2,
5, 8, 9, 11, 14}. Write execution traces.
2. Write an execution trace of mergeSort applied to the array int[] r = {5, 8,
-2, 11, 9}.
3. Rewrite mergeSort in Figure 19 so that it does not create multiple new arrays.
Instead, use this variant:
/** mergeSort sorts a segment of an array, r
* @param r - the array whose elements must be sorted
* @param scratch - an extra array that is the same length as r
* @param lower - the lower bound of the segment to be sorted
* @param upper - the upper bound of the segment to be sorted */
public void mergeSort(int[] r, int[] scratch, int lower, int upper)
477
8.14. BEYOND THE BASICS
{ ...
mergeSort(r, scratch, lower, middle);
mergeSort(r, scratch, middle+1, upper);
...
}
The method is initially invoked as follows: mergeSort(a, new int[a.length],
0, a.length-1).
4. Finish the execution traces for the example in this section that uses quickSort.
5. Write a partition algorithm for use by quickSort that chooses a pivot value in
the middle of the subarray to be partitioned.
6. Because quickSort’s partition method is sensitive to the pivot value it chooses
for partitioning, a standard improvement is to revise partition so that, when it
partitions a subarray of size 3 or larger, partition chooses 3 array elements from
the subarray and picks the median (the “middle value”) as the pivot. Revise
partition in this way.
8.14.5
Formal Description of Arrays
Arrays cause us to augment the syntax of data types, object construction, and variables to our Java subset. First, for every data type, T, T[] is the data type of
“T-arrays.” The precise syntax of data types now reads
TYPE ::= PRIMITIVE_TYPE | REFERENCE_TYPE
PRIMITIVE_TYPE ::= boolean | ... | int |
REFERENCE_TYPE ::= IDENTIFIER | TYPE[]
...
The syntax allows one- and multi-dimensional array types, e.g., int[] as well as
GregorianCalendar[][].
Array Constructors
Array variables are declared like ordinary variables; within the initialization statement, we can construct an array object explicitly, e.g., int[] r = new int[4] or by
means of a set-like initialization expression, e.g., int[] r = {1, 2, 4, 8}. Here is a
syntax definition that includes the two formats:
DECLARATION ::=
TYPE IDENTIFIER [[ = INITIAL_EXPRESSION ]]? ;
INITIAL_EXPRESSION ::=
EXPRESSION
| { [[ INITIAL_EXPRESSION_LIST ]]? }
INITIAL_EXPRESSION_LIST ::= INITIAL_EXPRESSION [[ , INITIAL_EXPRESSION ]]*
478
(Recall that [[ E ]]? means that a phrase, E, is optional and [[ E ]]* means that
phrase E can be repeated zero or more times.)
The syntax, { [[ INITIAL EXPRESSION LIST ]]? } defines the set notation for array object construction. The syntax makes clear that multi-dimensional arrays can
be constructed from nested set expressions:
double[][] d = { {0.1, 0.2}, {}, {2.3, 2.4, 2.6}};
This constructs an array with three rows of varying lengths and assigns it to d.
An array object can be constructed by a set expression only within an initialization
statement. The compiler verifies that the dimensions of the set expression and the
data types of the individual elements in the set expression are compatible with the
data type listed with the variable declared. Only elements of primitive type can be
listed.
An array constructed with the new keyword is defined by means of an OBJECT CONSTRUCTION
of the form, new ARRAY ELEMENT TYPE DIMENSIONS:
EXPRESSION ::= ... | STATEMENT_EXPRESSION
STATEMENT_EXPRESSION ::= OBJECT_CONSTRUCTION |
OBJECT_CONSTRUCTION ::=
...
...
| new ARRAY_ELEMENT_TYPE DIMENSIONS
ARRAY_ELEMENT_TYPE ::= PRIMITIVE_TYPE | IDENTIFIER
DIMENSIONS ::= [ EXPRESSION ] [[ [ EXPRESSION ] ]]* [[
[]
]]*
That is, ARRAY ELEMENT TYPE, the data type of the array’s individual elements, is
listed first, followed by the all the array’s dimensions. The quantity of at least the
first dimension must be given; the quantities of the dimensions that follow can be
omitted. For example, new int[4][] constructs a two-dimensional array object with
4 rows and an unspecified number of columns per row, and new int[4][3] constructs
a two-dimensional array object with 4 rows and 3 columns. The phrase, new int[],
is unacceptable.
An array construction, new ARRAY ELEMENT TYPE DIMENSIONS, is type checked to
validate that all expressions embedded in the DIMENSIONS have data types that are subtypes of int. The compiler calculates the data type of the phrase as ARRAY ELEMENT TYPE
followed by the number of dimensions in DIMENSIONS.
The execution semantics of an array construction goes as follows: For simplicity,
consider just a one-dimensional object, new ARRAY ELEMENT TYPE[EXPRESSSION]:
1. EXPRESSION is computed to an integer value, v. (If v is negative, an exception
results.)
2. An object is constructed with v distinct elements. The data type, ARRAY ELEMENT TYPE[v],
is saved within the object. Say that the object has storage address, a.
479
8.14. BEYOND THE BASICS
3. If ARRAY ELEMENT TYPE is a numeric type, the elements in the object are initialized
to 0. If it is boolean, the elements are initialized to false. Otherwise, the
elements are initialized to null.
4. The object’s address, a, is returned as the result.
When an array variable is initialized with an array object, as in int[] r = new
int[3], data-type checking and execution semantics proceed the same as with any
other variable initialization: The data type of the right-hand-side expression must
be a subtype of the left-hand-side type, and the address of the constructed object is
assigned to the left-hand-side variable’s cell.
References and Assignments
Elements of arrays are referenced with bracket notation, e.g., r[i + 1] = r[0]. Here
is the syntax for expressions and assignments extended to arrays:
ASSIGNMENT := VARIABLE = EXPRESSION
VARIABLE ::= IDENTIFIER | ... | RECEIVER [ EXPRESSION ]
EXPRESSION ::= ... | VARIABLE
RECEIVER ::=
IDENTIFIER |
...
| RECEIVER [ EXPRESSION ]
Recall that VARIABLE phrases must compute to addresses of storage cells (to which
are assigned values); RECEIVERs must compute to addresses of objects that can receive
messages; and EXPRESSIONs must compute to values that can be stored in cells.
The syntax allows an array to use multiple indexes, e.g., d[3][2] = 4.5. More
importantly, since an array is an object, it is a “receiver” of messages that ask for
indexings, e.g., r[0] sends a “message” to the object named r, asking it to index itself
at element 0 and return the value in that cell.
For an indexing expression, RECEIVER[EXPRESSION], the compiler verifies that the
data type of EXPRESSION is a subtype of int, and it verifies that the data type of
RECEIVER is an array type. When the indexing expression appears as a VARIABLE on
the left-hand side of an assignment, the compiler verifies, as usual, that the data type
of the right-hand side expression is a subtype of the left-hand side variable’s type.
When used as a VARIABLE on the left-hand side of an assignment, the semantics
of the phrase, RECEIVER[EXPRESSION], computes an address:
1. RECEIVER is computed to its result, which will be an address, a, of an array
object.
2. EXPRESSION is computed to its result, which must be an integer, v.
3. If v is nonnegative and is less than the length of the array at address a, then
the address, a[v], is returned as the result; otherwise, an exception is thrown.
480
For example, say that r holds the address, a1, of an array object. Then, the assignment, r[1 + 2] = 4, causes r[1 + 2] to compute to the address, a1[3], and inserts
4 into the cell at that address.
When the phrase, RECEIVER[EXPRESSION], is used as a RECEIVER or as an EXPRESSION,
then of course the addressed cell is dereferenced and the value in the cell is returned
as the result. For example, System.out.println(r[3]) prints the value found in the
cell addressed by r[3].
Here is a more complex example. For the arrays,
int[ ] r == a1
int[ ] s == a2
a1 : int[2][ ]
0
1
a2
null
a2 : int[4]
0
1
0
2
2
3
0
7
the assignment, r[0][2] = r[0][s[1] + 1], would execute these steps:
1. The variable part, r[0][2], computes to an address:
(a) The leftmost r computes to a1.
(b) r[0] computes to the address, a1[0], but this phrase is used as a receiver
(of the message, [2]), so a1[0] is dereferenced, producing a2.
(c) The address, a2[2], is formed as the address of the left-hand side variable.
This is the target of the assignment.
2. The right-hand side, r[0][s[1] + 1], computes to an integer:
(a) Since r has value a1, and r[0] is the receiver of the message, [s[1] + 1],
the address, a1[0] is dereferenced to the value a2.
(b) The expression, s[1] + 1, computes to 3, because s has value a2, s[1]
appears as an expression, hence the address a2[1] is dereferenced to 2 and
1 is added to it.
(c) Because r[0] computed to a2 and s[1] + 1 computed to 3, the address
a2[3] is formed. Since this appears as an expression, it is dereferenced to
produce 7.
3. 7 is assigned to address a2[2].
The above description of array assignment omits an important subtlety that is
specific to the Java language: When this assignment is executed,
RECEIVER [ EXPRESSION1 ] = EXPRESSSION2
The complete listing of execution steps goes as follows:
8.14. BEYOND THE BASICS
481
1. RECEIVER is computed to its value, which will be an address, a, of an array
object. The run-time type information is extracted from a; say that it is
element type[size].
2. EXPRESSION1 is computed to an integer, v. If v is nonnegative and less than
size, then the address, a[v], is formed as the target of the assignment.
3. EXPRESSSION2 is computed to its result, w.
4. This is the surprising, additional step: If w is not a primitive value, then it is an
address of an object—the run-time type, t, is fetched from the object at address
w and is compared to element type to verify that t is a subtype of element type.
5. If the types are compatible, w is assigned to the cell at a[v]; otherwise, an
exception is thrown.
In most programming languages, Step 4 is not required, because the type checking
already performed by the compiler suffices. But the additional type checking at
execution is forced upon Java because of Java’s subtyping laws for object (reference)
types.
To see this, here is an example. Perhaps we write this method:
public void assignPanel(JPanel[] r, JPanel f)
{ r[0] = f; }
The Java compiler examines the method and judges it acceptable. Next, we write
this class:
public class MyPanel extends JPanel
{ public MyPanel() { }
public void paintComponent(Graphics g) { }
public void newMethod() { }
}
This class is also acceptable to the Java compiler. But now, we play a trick:
MyPanel[] panels = new MyPanels[2];
JPanel x = new JPanel();
assignPanel(panels, x);
panels[0].newMethod();
Because MyPanel is a subtype of JPanel, panels is an acceptable actual parameter to
assignPanel, which apparently assigns a JPanel object into an array that is meant
to hold only MyPanel objects. If the assignment is allowed to proceed, then disaster
482
strikes at panels[0].newMethod(), which sends a message to an object that has no
newMethod.
This is the reason why every assignment to an array element must be type checked
at execution even though it was type checked previously by the Java compiler.
Chapter 9
Programming to Interfaces
9.1 Why We Need Specifications
9.2 Java Interfaces
9.2.1 Case Study: Databases
9.3 Inheritance
9.4 Reference Types, Subtypes, and instanceof
9.5 Abstract Classes
9.5.1 Case Study: Card Players
9.5.2 Class Hierarchies
9.5.3 Frameworks and Abstract Classes
9.6 Subtypes versus Subclasses
9.7 class Object and Wrappers
9.8 Packages
9.8.1 Generating Package APIs with javadoc
9.9 Case Study: An Adventure Game
9.9.1 Interfaces and Inheritance Together
9.9.2 Inheritance of Interfaces
9.10 Summary
9.11 Programming Projects
9.12 Beyond the Basics
When we write a class that matches a specification, we say that the class implements the specification. In this chapter, we learn four Java constructions for designing
classes and connecting them to their collaborating classes:
• Java interfaces, which define specifications that a coded class must implement.
• Java inheritance (extends), which defines a new class by adding methods onto
an already written class;
484
• Java abstract classes, which are “incomplete classes,” part specification and
part implementation.
• Java packages, which group a collection of classes under one name.
This chapter’s title comes from a slogan that practicing programmers follow when
they build an application:
Program to the interface, not the implementation!
That is, when you write a class that depends on another, collaborator class, you should
rely on the collaborator’s interface—its specification—and not its coding (its implementation). With this approach, you can design and implement classes separately yet
ensure that the assembled collection of classes collaborate successfully.
9.1
Why We Need Specifications
A program is assembled from a collection of classes that must “work together” or
“fit together.” What does it mean for two classes to fit together? For some insight,
consider the following situation.
Say that you receive a portable disc player as a gift. When you try to operate
the player, nothing happens — the player requires batteries. What batteries fit into
the player? Fortunately, on the back of the player is the specification, “This player
requires two AA batteries.” With this information, you can obtain the correctly sized
components (the batteries) and fit them into the player. The completed “assembly”
operates.
The specification of the disc player’s batteries served several useful purposes:
• The specification told the user which component must be fitted to the player
to ensure correct operation.
• The specification told the manufacturer of the disc player what size to build
the player’s battery chamber and what voltage and amperage to use within the
player’s electronics.
• The specification told the battery manufacturer what size, voltage, and amperage to build batteries so that others can use them.
These three facts are important in themselves, but they also imply that the user, the
disc manufacturer, and the battery manufacturer need not communicate directly with
each other — the specification of the battery is all that is needed for each party to
perform its own task independently of the other two.
Without size specifications of items like batteries, clothing, and auto parts, everyday life would would be a disaster.
9.2. JAVA INTERFACES
485
When we assemble a program from components, the components must fit together.
When a class, A, invokes methods from an object constructed from class B, class A
assumes that B possesses the methods invoked and that the methods behave in some
expected way — in the way they are specified. Just like batteries have specifications
(e.g., sizes AAA, AA, C,...), classes have specifications also. This explains why we
have been writing specifications for the Java classes we code.
For example, in Chapter 7, we encountered a case study of a simulation of a ball
bouncing in a box. The specifications of the ball and the box proved crucial to both
writing the simulation’s classes and fitting the classes together in the program.
The Java language and the Java compiler can help us write specifications of classes
and check that a class correctly matches (implements) its specification. In this chapter
we study several Java constructions for designing programs in separate classes:
1. the interface construction, which lets us code in Java the information we specify in a class diagram;
2. the extends construction, which lets us code a class by adding methods to a
class that already exists;
3. the abstract class construction, which lets us code an incomplete class that
can be finished by another class.
Finally, to help us group together a collection of related classes into the same folder,
we use Java’s package construction.
We will study each of the constructions in turn in this chapter.
9.2
Java Interfaces
In the previous chapters, we used informal specifications to design classes and to connect them to their collaborator classes. But the Java language provides a construct,
called an interface, that lets us include a specification as an actual Java component
of an application—we type the interface into a file and compile it, just like a class.
Then, we use the compiled interface in two ways:
• We write classes that match or implement the interface, and the Java compiler
verifies this is so.
• We write classes that rely upon the interface, and the Java compiler verifies
this is so.
These ideas are best explained with a small example: Say that you and a friend must
write two classes—one class models a bank account and the other class uses the bank
account to make monthly payments on a mortgage. You will model the bank account,
486
Figure 9.1: Java interface
/** BankAccountSpecification specifies the behavior of a bank account.
public interface BankAccountSpecification
{ /** deposit adds money to the account
* @param amount - the amount of the deposit, a nonnegative integer
public void deposit(int amount);
*/
*/
/** withdraw deducts money from the account, if possible
* @param amount - the amount of the withdrawal, a nonnegative integer
* @return true, if the the withdrawal was successful;
* return false, otherwise. */
public boolean withdraw(int amount);
}
your friend will write the monthly-payment class, and the two of you will work simultaneously. But how can your friend write his class without yours? To do this, the two
of you agree on the Java interface stated in Figure 1, which specifies, in the Java language, the format of the yet-to-be written bank-account class. The interface states
that, whatever class is finally written to implement a BankAccountSpecification,
the class must contain two methods, deposit and withdraw, which behave as stated.
Compare Figure 1 to Table 10 of Chapter 6, which presented a similar, but informal,
specification.
A Java interface is a collection of header lines of methods for a class that is not
yet written. The interface is not itself a class—it is a listing of methods that some
class might have.
The syntax of a Java interface is simply
public interface NAME
{ METHOD_HEADER_LINES }
where METHOD HEADER LINES is a sequence of header lines of methods, terminated by
semicolons. As a matter of policy, we insert a comment with each header line that
describes the intended behavior of the method.
The BankAccountSpecification interface is placed in its own file, BankAccountSpecification.java,
and is compiled like any other component. Once the interface is compiled, other
classes can use it in their codings, as we now see.
Your friend starts work on the mortgage-payment class. Although your friend does
not have the coding of the bank-account class, it does not matter — whatever the coding will be, it will possess the methods listed in interface BankAccountSpecification.
Therefore, your friend writes the class in Figure 2, which uses the BankAccountSpecification
as the data type for the yet-to-be-written bank-account class. The interface name,
BankAccountSpecification, is used as a data type, just like class names are used as
9.2. JAVA INTERFACES
487
Figure 9.2: class that references a Java interface
/** MortgagePaymentCalculator makes mortgage payments */
public class MortgagePaymentCalculator
{ private BankAccountSpecification bank account; // holds the address of
// an object that implements the BankAccountSpecification
/** Constructor MortgagePaymentCalculator initializes the calculator.
* @param account - the address of the bank account from which we
*
make deposits and withdrawals */
public MortgagePaymentCalculator(BankAccountSpecification account)
{ bank account = account; }
/** makeMortgagePayment makes a mortgage payment from the bank account.
* @param amount - the amount of the mortgage payment */
public void makeMortgagePayment(int amount)
{ boolean ok = bank account.withdraw(amount);
if ( ok )
{ System.out.println("Payment made: " + amount); }
else { ... error ... }
}
...
}
data types. This lets us write a constructor method that accepts (the address of) an
object that has the behavior specified in interface BankAccountSpecification and
it lets us use this object’s withdraw method in the method, makeMortgagePayment.
Class MortgagePaymentCalculator can now be compiled; the Java compiler validates that the class is using correctly the methods listed in interface BankAccountSpecification.
(That is, the methods are spelled correctly and are receiving the correct forms of arguments, and the results that the methods return are used correctly.) In this way,
your friend completes the class that makes mortgage payments.
Meanwhile, you are writing the class that implements interface BankAccountSpecification;
this might look like Figure 3. This is essentially Figure 11 of Chapter 6, but notice in
the class’s header line the phrase, implements BankAccountSpecification. This tells
the Java compiler that class BankAccount can be connected to those classes that use
BankAccountSpecifications. When you compile class BankAccount, the Java compiler verifies, for each method named in interface BankAccountSpecification, that
class BankAccount contains a matching method. (By “matching method,” we mean
that the class’s method’s header line is the same as the header line in the interface—
the number of parameters is the same, the types of the parameters are the same, and
the result type is the same. But the names of the formal parameters need not be
488
Figure 9.3: a class that implements a Java interface
/** BankAccount manages a single bank account; as stated in its
* header line, it implements the BankAccountSpecification: */
public class BankAccount implements BankAccountSpecification
{ private int balance; // the account’s balance
/** Constructor BankAccount initializes the account */
public BankAccount()
{ balance = 0; }
// notice that methods deposit and withdraw match the same-named
// methods in interface BankAccountSpecification:
public void deposit(int amount)
{ balance = balance + amount; }
public boolean withdraw(int amount)
{ boolean result = false;
if ( amount <= balance )
{ balance = balance - amount;
result = true;
}
return result;
}
/** getBalance reports the current account balance
* @return the balance */
public int getBalance()
{ return balance; }
}
exactly the same, and the order of the methods in the class need not be the same as
the order of the methods in the interface.)
Notice that class BankAccount has an additional method that is not mentioned
in the interface; this is acceptable.
To connect together the two classes, we write a start-up method with statements
like the following:
BankAccount my_account = new BankAccount();
MortgageCalculator calc = new MortgageCalculator(my_account);
...
calc.makeMortgagePayment(500);
489
9.2. JAVA INTERFACES
Figure 9.4: class diagram of Java interface
MortgageCalculator
makeMortagagePayment()
BankAccountSpecification
deposit(int amount)
withdraw(int amount): boolean
BankAccount
deposit(int amount)
withdraw(int amount): boolean
balanceOf(): int
Since the Java compiler verified that BankAccount implements BankAccountSpecification,
the my account object can be an argument to the constructor method of MortgageCalculator.
A major advantage of using Java interfaces is this:
Class MortgageCalculator is not rewritten or changed to refer to BankAccount —
once
class MortgageCalculator
is
correctly
compiled
with
interface BankAccountSpecification, it is ready for use.
In this way, components can be separately designed, written, compiled, and later
connected together into an application, just like the disc player and batteries were
separately manufactured and later connected together.
Figure 4 shows the class diagram for the example in Figures 1 to 3. To distinguish it from the classes, the Java interface is written in italics. Since class
MortageCalculator depends on (references) the interface, a dotted arrow is drawn
from it to the interface. Since class BankAccount implements the interface, a dotted
arrow with a large arrowhead is drawn from it to the interface.
The class diagram in Figure 4 shows that MortgageCalculator and BankAccount
do not couple-to/depend-on each other (in the sense of Chapter 6)—they both depend
on interface BankAccountSpecification, which is the “connection point” for the two
classes. Indeed, MortgageCalculator and BankAccount are simple examples of “subassemblies” that connect together through the BankAccountSpecification interface.
This makes it easy to remove BankAccount from the picture and readily replace it by
some other class that also implements BankAccountSpecification. (See the Exercises
that follow this section.)
It is exactly this use of Java interfaces that allows teams of programmers build
large applications: Say that a team of programmers must design and build a complex application, which will require dozens of classes. Perhaps the programmers are
divided into three groups, each group agrees to write one-third of the application,
and the three subassemblies will be connected together. If all the programmers first
agree on the interfaces where the three subassemblies connect, then each group can
independently develop their own subassembly so that it properly fits into the final
product
490
Exercises
1. Given this interface,
public interface Convertable
{ public double convert(int i); }
which of the following classes will the Java compiler accept as correctly implementing the interface? Justify your answers.
(a) public class C1 implements Convertable
{ private int x;
public C1(int a) { x = a; }
public double convert(int j)
{ return x + j; }
}
(b) public class C2 implements Convertable
{ private int x;
public C1(int a) { x = a; }
public int convert(int i)
{ return i; }
}
(c) public class C3
{ private int x;
public C3(int a) { x = a; }
public double convert(int i)
{ return (double)x; }
}
2. Given this interface,
public interface Convertable
{ public double convert(int i); }
which of the following classes use the interface correctly? Justify your answers.
(a) public class Compute1
{ private Convertable convertor;
public Compute1(Convertable c)
{ convertor = c; }
public void printConversion(int x)
{ System.out.println(convertor.convert(x)); }
}
9.2. JAVA INTERFACES
491
(b) public class Compute2 uses Convertable
{ public Compute2() { }
public void printIt(double x)
{ System.out.println(Convertable.convert(x)); }
}
(c) public class Compute3
{ Convertable c;
public Compute3()
{ c = new Convertable(); }
public void printIt(int v)
{ System.out.println(c.compute(v)); }
}
3. After you have answered the previous two questions, do the following:
• Place interface Convertable in the file, Convertable.java, and compile
it. Next, copy class C1 to the file, C1.java, in the same folder as Convertable.java,
and compile it.
• Place class Compute1 in the file, Compute1.java, in the same folder as
Convertable.java, and compile it.
• Now, place the following code in the file, Start.java, in the same folder
as Convertable.java, and compile it:
public class Start
{ public static void main(String[] args)
{ C1 c = new C1(1);
Compute1 computer = new Computer(c);
computer.printConversion(3);
}
}
Execute Start.
Notice that both C1.java as well as Compute1.java use the same compiled instance of Convertable.java. This arrangement is a bit awkward when two
different people write the two classes — they must share the same folder. We
will repair this difficulty when we study Java packages later in this chapter.
4. Reconsider interface BankAccountSpecification from Figure 1. We will use it
to improve the bank-accounts manager program from Section 8 of Chapter 6.
492
(a) Revise class BankAccount from Figure 11 of Chapter 6 so that it implements BankAccountSpecification. Next, revise class BankWriter (Figure
15, Chapter 6) and class AccountController (Figure 16, Chapter 6) so
that they invoke the methods of an object of type BankAccountSpecification
(and not BankAccount). Why does AccountController compile correctly
but BankWriter does not?
Repair interface BankAccountSpecification so that class BankWriter
compiles without errors. Does class AccountManager of Figure 16 require
any changes?
(b) Next, discard class BankAccount from Figure 11 of Chapter 6 and replace
it by this class:
public class SillyAccount implements BankAccountSpecification
{ public SillyAccount() { }
public void deposit(int amount) { }
public boolean withdraw(int amount) { return true; }
public int getBalance() { return 0; }
}
Change class AccountManager in Figure 16 so that it declares,
BankAccountSpecification account = new SillyAccount();
Does AccountManager compile without error? Do any of the other classes
require changes to compile?
This exercise demonstrates that Java interfaces make it simple to replace
one class in an assembly without rewriting the other classes.
5. Review the moving-ball animation in Chapter 7, Section 9.
(a) First, write a Java interface that describes MovingObjectBehavior as defined in Table 10 of Chapter 7. Then make class MovingBall in Figure 12, Chapter 7, implement MovingObjectBehavior, and change classes
BounceController and BallWriter so that they mention only MovingObjectBehavior
(and not MovingBall).
(b) Based on the changes you made, redraw the class diagram in Figure 9,
Chapter 7.
(c) Next, replace class MovingBall by this class:
/** ThrobbingBall models a stationary ball that changes size */
public class ThrobbingBall implements MovingObjectBehavior
{ private int max_radius = 60;
private int increment = 10;
private int current_radius; // invariant: 0 <= current_radius < max_radius
private int position = 100;
9.2. JAVA INTERFACES
493
public ThrobbingBall() { current_radius = 0; }
public int xPosition() { return position; }
public int yPosition() { return position; }
public int radiusOf() { return current_radius; }
public void move()
{ current_radius = (current_radius + increment) % max_radius; }
}
and in Figure 16, Chapter 7, replace the MovingBall object constructed
within class BounceTheBall by
ThrobbingBall ball = new ThrobbingBall();
Recompile class BounceTheBall and execute the animation.
9.2.1
Case Study: Databases
In Chapter 8, Section 6, we designed a database, named class Database, to hold
a collection of “record” objects, each of which possessed a unique “key” object to
identify it. The database was designed to be general purpose, in the sense that
records might be library-book objects or bank-account objects, or tax records. As
stated in Chapter 8, the crucial behaviors were
1. The Database holds a collection of Record objects, where each Record holds a Key
object. The remaining structure of the Records is unimportant and unknown
to the database.
2. The Database will possess insert, find, and delete methods.
3. Records, regardless of their internal structure, will possess a getKey method that
returns the Record’s Key object when asked.
4. Key objects, regardless of their internal structure, will have an equals method
that compares two Keys for equality and returns true or false as the answer.
Based on these ideas, informal specifications of Record and Key were written (see
Table 3 of Chapter 8), and class Database was written to use the methods defined
in the specifications; see Figure 4 of Chapter 8.
Clearly, the types Record and Key are not meant to be specific classes; they should
be the names of two Java interfaces, so that we can compile class Database now
and decide later how to implement the two interfaces.
Figure 5 shows how to transform the informal specifications of Record and Key
from Table 3 of Chapter 8 into interfaces. These interfaces are compiled first, then
class Database from Figure 4, Chapter 8, can be compiled — please review that
Figure, now. Note how class Database refers to Record and Key in its coding. In
494
Figure 9.5: interfaces for Record and Key
/** Record is a data item that can be stored in a database */
public interface Record
{ /** getKey returns the key that uniquely identifies the record
* @return the key */
public Key getKey();
}
/** Key is an identification, or ‘‘key,’’ value
*/
public interface Key
{ /** equals compares itself to another key, m, for equality
* @param m - the other key
* @return true, if this key and m have the same key value;
* return false, otherwise */
public boolean equals(Key m);
}
particular, Record’s keyOf method and Key’s equals method are used in crucial ways
to insert and find records in the database.
As noted in Section 8.6.5, we might use the database to hold information about
bank accounts that are identified by integer keys. Figure 5 of Chapter 8, which
defines bank accounts and integer keys, should be rewritten — Figure 6 shows these
two classes revised so that they implement the Record and Key interfaces, respectively.
Note that both classes are properly named and implement their respective interfaces.
The first class, BankAccount, keeps its key as an attribute and gives it away with
its getKeyOf method; the class knows nothing about its key’s implementation. The
second class, IntegerKey, uses an integer attribute as its internal state. Its equals
method must compare its internal integer to the integer held in its argument, c. To
do this, object c must be cast into its underlying type, IntegerKey, so that the getInt
method can be queried for c’s integer. (From the perspective of the Java compiler, an
object whose data type is Key does not necessarily possess a getInt method; the cast
is necessary to tell the compiler that c is actually an IntegerKey, which does possess
a getInt method.)
Unfortunately, we cannot avoid the cast by writing equals’s header line as
public boolean equals(IntegerKey c)
because the parameter’s data type would not match the data type of the parameter
of equals in interface Key. We must live with this clumsiness.
Now, we can build a database that holds BankAccount records; study carefully the
following, which shows how to insert and retrieve bank acccounts:
9.2. JAVA INTERFACES
Figure 9.6: implementing the database interfaces
/** BankAccount models a bank account with an identification key */
public class BankAccount implements Record
{ private int balance; // the account’s balance
private Key id;
// the identification key
/** Constructor BankAccount initializes the account
* @param initial amount - the starting account balance, a nonnegative.
* @param id - the account’s identification key */
public BankAccount(int initial amount, Key id)
{ balance = initial amount;
key = id;
}
/** deposit adds money to the account.
* @param amount - the amount of money to be added, a nonnegative int */
public void deposit(int amount)
{ balance = balance + amount; }
/** getBalance reports the current account balance
* @return the balance */
public int getBalance() { return balance; }
/** getKey returns the account’s key
* @return the key */
public int getKey() { return key; }
}
/** IntegerKey models an integer key */
public class IntegerKey implements Key
{ private int k; // the integer key
/** Constructor IntegerKey constructs the key
* @param i - the integer that uniquely defines the key */
public IntegerKey(int i) { k = i; }
/** equals compares this Key to another for equality
* @param c - the other key
* @return true, if this key equals k’s; return false, otherwise */
public boolean equals(Key c)
{ return ( k == ((IntegerKey)c).getInt() ); }
/** getInt returns the integer value held within this key */
public int getInt() { return k; }
}
495
496
Figure 9.7: class diagram with interfaces
Database
insert(Record r): boolean
find(Key k): Record
delete(Key k): boolean
Record
getKey(): Key
Database db = new Database(4);
Key
equals(Key m): boolean
BankAccount
deposit(int amount)
getBalance(): int
getKey(): Key
IntegerKey
equals(Key m): boolean
getInt(): int
// see Figure 4, Chapter 8
BankAccount a = new BankAccount(500, new IntegerKey(1234));
boolean result1 = db.insert(a);
IntegerKey k = new IntegerKey(567);
BankAccount b = new BankAccount(1000, k);
boolean result2 = db.insert(b);
Record r = db.find(k); // retrieve object indexed by Key k
System.out.println(((BankAccount)r).getBalance()); // why is the cast needed?
Since Database’s find method returns an object that is known to be only a Record,
a cast to BankAccount is required by the Java compiler in the last statement of the
above example.
Figure 7 shows the database example’s class diagram. The diagram shows that
Database is coupled only to the two interfaces and not to the classes that implement
the interfaces. This is a clear signal that other classes of records and keys can be
used with class Database. (For example, Figure 6 of Chapter 8 shows codings of
classes of library books and catalog numbers; by recoding the two classes so that they
implement the Record and Key interfaces, the two classes can be readily used with
the database.)
Also, note how BankAccount and IntegerKey are not coupled to each other, making
it easy to “unplug” and replace both classes. Finally, by transitivity, the dotted arrows
from BankAccount to Record to Key let us infer correctly that BankAccount depends
on interface Key as well as interface Record.
Exercises
Return to Section 8.6, “Case Study: Databases,” in Chapter 8. Rework Figure 6 so
that its classes implement Record and Key.
9.3. INHERITANCE
9.3
497
Inheritance
In the previous chapters, we built many applications that paint onto a panel within
a graphics window, like this:
import java.awt.*;
import javax.swing.*;
public class MyPanel extends JPanel
{ ...
public void paintComponent(Graphics g)
{ ... instructions for painting on a panel ... }
}
This tactic worked because, within the package javax.swing, there is a prewritten
class, class JPanel, which contains the instructions for constructing a blank graphics
panel that can be displayed on a monitor. We exploit this already written class by
• writing a new class that extends JPanel
• writing a method, paintComponent, that contains instructions for painting on
the panel.
When we construct an object from our class, say,
MyPanel p = new MyPanel(...);
the object we construct has all the private fields and methods within class JPanel
plus the new methods and fields within class MyPanel. This gives object p the ability
to display a panel on the display as well as paint shapes, colors, and text onto it.
This style of “connecting” to an already written class and adding new methods
is called inheritance. We say that MyPanel is a subclass of JPanel (and JPanel is a
superclass of MyPanel).
To understand inheritance further, study this small example developed from scratch:
Say that a friend has written class Person:
public class Person
{ private String name;
public Person(String n)
{ name = n; }
public String getName()
{ return name; }
... // Other clever methods are here.
}
498
Pretend this class has proven popular, and many programs use it.
Next, say that you must write a new application that models persons with their
addresses. You would like to “connect” an address to a person and reuse the coding
within class Person to save you time writing your new class. You can use inheritance
to do this:
public class PersonAddress extends Person
{ private String address;
public PersonAddress(String the_name, String the_addr)
{ super(the_name); // this gives the_name to Person’s
address = the_addr;
}
constructor
public String getAddress()
{ return address; }
...
}
Because its title line states, extends Person, class PersonAddress inherits the fields
and methods of class Person. When we construct an object from the new class, say,
PersonAddress x = new PersonAddress("fred", "new york");
the object constructed in computer storage contains an address variable and also a
name variable. Indeed, the first statement in the constructor method for PersonAddress,
super(the_name);
invokes the constructor method within Person (the superclass), so that the person’s
name is inserted into the name field. (When this tactic is used, the super instruction
must be the first statement within the subclass’s constructor method.)
When we use the object we constructed, e.g.,
System.out.println("Name: " + x.getName());
System.out.println("Address: " + x.getAddress());
the methods from class Person can be used alongside the methods from class PersonAddress.
It is striking that we can say, x.getName(), even though there is no getName method
within class PersonAddress; the inheritance technique “connects” the methods of
Person to PersonAddress.
We use a large arrowhead in class-diagram notation to denote inheritance:
FIGURE HERE:
PersonAddress
---|>
Person
499
9.3. INHERITANCE
Inheritance is fundamentally different from Java interfaces, because inheritance
builds upon classes that are already written, whereas interfaces specify classes that
are not yet written (or unavailable). Inheritance is often used when someone has
written a basic class that contains clever methods, and other people wish to use the
clever methods in their own classes without copying the code — they write subclasses.
A good example is our extension of class JPanel, at the beginning of this section.
Here is another situation where inheritance can be useful: Again, say that class
Person has proved popular, and someone has written a useful class that writes information about persons:
public class WritePerson
{ ...
public void writeName(Person p)
{ ... clever instructions to write
p’s
name ... }
}
If we write an application that manages persons plus their addresses, we can
exploit both class Person and class WritePerson by writing class PersonAddress
extends Person as shown above. Then, we can do this:
PersonAddress x = new PersonAddress("fred", "new york");
WritePerson writer = new WritePerson(...);
writer.writeName(x);
System.out.println("Address: " + x.getAddress());
The statement, writer.writeName(x) is crucial, because the writeName method expects an argument that has data type Person. Because x has data type PersonAddress
and because PersonAddress extends Person, x is acceptable to writeName. This behavior is based on subtyping and is explored in the next section.
Exercises
1. Given these classes,
public class Person
{ private String name;
public Person(String n) { name = n; }
public String getName() { return name; }
public boolean sameName(Person other)
{ return getName().equals(other.getName(); }
}
500
public class PersonAddress extends Person
{ private String address;
public PersonAddress(String the_name, String the_addr)
{ super(the_name);
address = the_addr;
}
public String getAddress() { return address; }
public boolean same(PersonAddress other)
{ return
sameName(other) && address.equals(other.getAddress()); }
}
and these declarations:
Person p = new Person("fred");
Person q = new PersonAddress("ethel", "new york");
Which of the following statement sequences are acceptable to the Java compiler?
If a statement sequence is acceptable, what does it print when executed?
(a) System.out.println(p.getAddress());
(b) System.out.println(q.getName());
(c) System.out.println(p.sameName(p));
(d) System.out.println(q.sameName(p));
(e) System.out.println(q.same(p));
This example is studied further in the next Section.
9.4
Reference Types, Subtypes, and
instanceof
When we first encountered data types in Chapter 3, we treated them as “species”
of values—int, double, and boolean were examples of such species. The classifying
values into species prevents inappropriate combinations of values, such as true && 3
— the Java compiler does data-type checking to spot such errors. Data-type checking
also spots bad actual-formal parameter combinations, such as Math.sqrt(true).
Numerical, boolean, and character values are primitive (non-object), and their
data types are called primitive types. The numeric primitive types are related by
subtyping: int is a subtype of double, written int <= double, because an integer can
be used in any situation where a double is required. For example, the integer 2 can
be used within
9.4. REFERENCE TYPES, SUBTYPES, AND INSTANCEOF
501
double d = 4.5 / 2;
because a double answer can be produced by dividing the double, 4.5, by 2. Similarly,
if a method expects an argument that is a double, as in
public double inverseOf(double d)
{ return 1.0 / d; }
it is acceptable to send the method an actual parameter that is an integer, e.g.,
inverseOf(3). The Java compiler uses the subtyping relationship, int <= double, to
check the well formedness of these examples.
Subtyping relationships simplify our programming; in particular, cumbersome cast
expressions are not required. For example, it is technically correct but ugly to write
double d = 4.5 / ((double)2), and thanks to subtyping, we can omit the cast.
Begin footnote: Here is a listing of the subtyping relationships between the numeric
types:
byte
<=
int
<=
long
<=
float
<=
double
Thus, byte <= int, int <= long, byte <= long, etc. End footnote
In addition to the primitive data types, there are object or reference data types:
Every class C defines a reference data type, named C — its values are the objects
constructed from the class. This explains why we write declarations like
Person p = new Person("fred");
class Person defines data type Person, and variable p may hold only addresses of
objects that have data type Person.
Java interfaces and inheritance generate subtyping relationships between reference
types as well. If we write the class,
public class MyPanel extends JPanel
{ ... }
then this subtyping relationship is generated: MyPanel <= JPanel. This means that
the object, new MyPanel(), can be used in any situation where a value of data type
JPanel is expected. We have taken for granted this fact, but it proves crucial when
we construct, a new MyPanel object and insert it into a JFrame object:
// This example comes from Figure 12, Chapter 4:
import java.awt.*;
import javax.swing.*;
public class MyPanel extends JPanel
{ ... }
public class FrameTest3
{ public static void main(String[] args)
502
{ JFrame my_frame = new JFrame();
// insert a new panel into the frame:
my_frame.getContentPane().add(new MyPanel());
...
}
}
The add method invoked within main expects a JPanel object as its argument. But
since MyPanel <= JPanel, the newly constructed MyPanel object is acceptable.
This same phenomenon appeared at the end of the previous section when we used
a PersonAddress object as an argument to the writeName method of PersonWriter.
Similar subtyping principles also apply to Java interfaces: The Java compiler
uses interface names as data type names, and the compiler enforces a subtyping
relationship when an interface is implemented: if class C implements I, then C <=
I. This proves crucial when connecting together classes:
Reconsider the database example in Figures 5-7; we might write
Database db = new Database(4);
IntegerKey k = new IntegerKey(1234);
BankAccount b = new BankAccount(500, k);
boolean success = db.insert(b);
The database method, insert, expects an arguments with data type Record, but
it operates properly with one of type BankAccount, because BankAccount <= Record.
This subtyping can be justified by noting that a BankAccount has all the methods
expected of a Record, so insert executes as expected.
If we peek inside computer storage, we see that the above statements constructed
these objects:
a1 : Database
Database db == a1
IntegerKey k ==
1
int count ==
BankAccount b == a4
boolean success == true
a2 : Record[4]
0
1
2
a4
public boolean insert(Record r) {...}
public Record find(Key k) {...}
public boolean delete(Key k) {...}
private int locationOf(Key k) {...}
3
null null null
a4 : BankAccount
int balance ==
a3 : IntegerKey
int id ==
...
1234
a2
Record[] base ==
a3
Key id ==
...
a3
500
9.4. REFERENCE TYPES, SUBTYPES, AND INSTANCEOF
503
The diagram indicates that every object in storage is labelled with the name of the
class from which the object was constructed. This is the run-time data type of the
object. The diagram also illustrates that run-time data types are distinct from the
data types that appear in assignments. For example, the object at address a4 retains
its run-time data type, BankAccount, even though it was assigned into an element of
an array declared to hold Records. (Look at a2’s run-time data type.) The situation
is acceptable because of the subtyping relationship.
Consider the following statements, which build on the above:
Record r = db.find(k);
System.out.println( ((BankAccount)r).getBalance() );
The first statement extracts from the data base the record matching k, that is, a4 is
assigned to r. But we cannot say, immediately thereafter, r.getBalance(), because
variable r was declared to have data type Record, and there is no getBalance method
listed in interface Record. The problem is that the data type in the statement,
Record r = db.find(k), is distinct from the run-time data type attached to the object
that is assigned to r.
If we try to repair the situation with the assignment, BankAccount r = db.find(k),
the Java compiler complains again, because the find method was declared to return
a result of data type Record, and Record is not a subtype of BankAccount!
This is frustrating to the programmer, who knows that db is holding BankAccount
objects, but Java’s compiler and interpreter are not intelligent enough to deduce
this fact. Therefore, the programmer must write an explicit cast upon r, namely,
(BankAccount)r, to tell the Java compiler that r holds an address of an object whose
run-time type is BankAccount. Only then, can the getBalance message be sent.
If the programmer encounters a situation where she is not certain herself what is
extracted from the database, then the instanceof operation can be used to ask the
extracted record its data type, e.g.,
Record mystery_record = db.find(mystery_key);
if ( mystery_record instanceof BankAccount)
{ System.out.println( ((BankAccount)mystery_record).getBalance() ); }
else { System.out.println("unknown record type"); }
Stated precisely, the phrase, EXPRESSION instanceof TYPE, returns true exactly when
the run-time data type attached to the object computed by EXPRESSION is a subtype
of TYPE.
We can use the instanceof method to repair a small problem in the database
example in the previous section. In addition to class IntegerKey implements Key,
say that the programmer writes this new class:
/** StringKey models a key that is a string */
public class StringKey implements Key
{ private String s;
504
public StringKey(String j)
{ s = j; }
public String getString()
{ return s; }
public boolean equals(Key m) { ... }
}
and say that she intends to construct some records that use IntegerKeys and some
that use StringKeys. This seems ill-advised, because we see a problem when an
IntegerKey object is asked to check equality against a StringKey object:
IntegerKey k1 = new IntegerKey(2);
StringKey k2 = new StringKey("two");
boolean answer = k1.equals(k2);
Surprisingly, the Java compiler will accept these statements as well written, and it
is only when the program executes that the problem is spotted—execution stops in the
middle
of
IntegerKey’s
equals
method
at
the
statement,
int m = ((IntegerKey)another key).getInt(), and this exception message appears:
Exception in thread "main" java.lang.ClassCastException: StringKey
at IntegerKey.equals(...)
Despite the declaration in its header line, IntegerKey’s equals method is unprepared
to deal with all possible actual parameters whose data types are subtypes of Key!
If an application will be constructing both IntegerKeys and StringKeys, then we
should improve IntegerKey’s equals method to protect itself against alien keys. We
use the instanceof operation:
public boolean equals(Key another_key)
{ boolean answer;
// ask if another_key’s run-time data type is IntegerKey:
if ( another_key instanceof IntegerKey )
{ int m = ((IntegerKey)another_key).getInt();
answer = (id == m);
}
else // another_key is not an IntegerKey, so don’t compare:
{ answer = false; }
return answer;
}
The phrase,
if ( another_key instanceof IntegerKey )
9.4. REFERENCE TYPES, SUBTYPES, AND INSTANCEOF
505
determines the address of the object named by another key, locates the object in
storage, extracts the run-time data type stored in the object, and determines whether
that data type is a subtype of IntegerKey. If it is, true is the answer. If not, false
is the result.
Exercises
1. Given these classes,
public class Person
{ private String name;
public Person(String n) { name = n; }
public String getName() { return name; }
public boolean sameName(Person other)
{ return getName().equals(other.getName(); }
}
public class PersonAddress extends Person
{ private String address;
public PersonAddress(String the_name, String the_addr)
{ super(the_name);
address = the_addr;
}
public String getAddress() { return address; }
public boolean same(PersonAddress other)
{ return
sameName(other) && address.equals(other.getAddress()); }
}
and these declarations:
Person p = new Person("fred");
Person q = new PersonAddress("ethel", "new york");
Which of the following statement sequences are acceptable to the Java compiler?
If a statement sequence is acceptable, what does it print when executed?
(a) System.out.println(p.sameName(q));
(b) Person x = q; System.out.println(x.getName());
506
(c) PersonAddress x = p; System.out.println(x.getAddress());
(d) Person x = q; System.out.println(x.getAddress());
(e) System.out.println(q.same(p));
2. Explain why this example fails to compile:
public class C
{ private int x;
public C() { x = 0; }
}
public class D extends C
{ public D() { super(); }
public void increment() { x = x + 1; }
}
If you are interested in repairing the example, read the section, “Subclasses and
Method Overriding,” at the end of the Chapter.
3. Use the instanceof operation to improve the existing implementations of interface
Key:
(a) Insert the above coding of equals into class IntegerKey in Figure 6.
(b) Finish writing class StringKey so that it has equals method like the one
you wrote in the previous exercise. (Hint: Use the compareTo method, in
Table 5, Chapter 3, to write the lessthan method.)
(c) Write a test class that executes these statements:
Database db = new Database(4);
// see Figure 3, Chapter 8
BankAccount b = new BankAccount(500, new IntegerKey(1234));
IntegerKey k = new StringKey("lucy");
BankAccount lucy = new BankAccount(1000, k);
boolean result1 = db.insert(b);
boolean result2 = db.insert(lucy);
Record p = db.find(k);
BankAccount q = (BankAccount)p;
System.out.println(q.getBalance());
Key k = q.getKey();
if ( k instanceof IntegerKey )
{ System.out.println( ((IntegerKey)k).getInt() ); }
else if ( k instance of StringKey )
9.4. REFERENCE TYPES, SUBTYPES, AND INSTANCEOF
507
{ System.out.println( ((StringKey)k).getString() ); }
else { System.out.println("unknown key value"); }
What appears on the display?
4. Given these interfaces and classes,
public interface I
{ public int f(int i); }
public class C implements I
{ public C() { }
public int f(int i) { return i + 1; }
public void g() { }
}
public class D
{ public D() { }
public int f(int i) { return i + 1; }
}
(a) Which of the following subtyping relations hold true? C <= I; D <= I; C
<= D.
(b) Given these initializations,
I x = new C();
C y = new C();
C z = (C)x;
Which of the following expressions evaluate to true? x instanceof I; x
instanceof C; x instanceof D; y instanceof I; y instanceof C; y instanceof
D; z instanceof I; z instanceof C.
(c) Add casts, where necessary, so that the following statement sequence passes
the scrutiny of the Java compiler:
I x = new C();
x.f(21);
x.g();
C y = x;
y.g();
if ( x instanceof C )
{ x.g(); }
(d) Explain why the Java compiler complains about these statements:
508
D a = new D();
I b = a;
if ( a instanceof C )
{ System.out.println("!");
9.5
Abstract Classes
Simply stated, an abstract class is a class with missing method bodies. The missing
bodies are supplied by subclasses that extend the abstract class. Why would we use
such a construction? Here is a small example:
In the previous section, we pretended that a class Person was an important
component of many programs. Perhaps the author of class Person intended that
Person objects must have addresses, but the author did not wish to code the address
part. (The address might be a string or an integer or a color or ....) An abstract class
can state exactly the author’s wishes:
public abstract class Person
{ private String name;
// note the keyword,
abstract
public Person(String n)
{ name = n; }
public String getName()
{ return name; }
public abstract String getAddress();
// method will be written later
...
}
This variant of class Person has two additions from the one seen earlier:
1. In the title line, the keyword, abstract, announces that we cannot construct
Person objects, because there are missing method bodies.
2. The title line for method getAddress contains the keyword, abstract, and the
method is missing its body. The title line is terminated by a semicolon.
Because the class is abstract, we cannot construct new Person(...) objects. Instead,
we can only extend the class with its missing method body. This extension, seen
earlier, works fine:
public class PersonAddress extends Person
{ private String address;
509
9.5. ABSTRACT CLASSES
public PersonAddress(String the_name, String the_addr)
{ super(the_name); // this gives the_name to Person’s
address = the_addr;
}
constructor
public String getAddress()
{ return address; }
...
}
We can construct new PersonAddress(...) objects, as before. And, we can construct
other variants, e.g.,
public class PersonWithInt extends Person
{ private int address;
public PersonWithInt(String the_name, int the_addr)
{ super(the_name);
address = the_addr;
}
public String getAddress()
{ return "" + address; } // make the int into a string
...
}
Note that the getAddress method must have the same title line as the one in the
superclass; this forces the integer to be converted into a string when it is returned.
9.5.1
Case Study: Card Players
An abstract class is “half interface” and “half superclass,” and it is most useful for
grouping, under a single data type name, classes that share methods.
Here is an example that illustrates good use of interfaces with abstract classes: In
Chapter 8, we saw how to design cards and card decks for a card game. (See Tables
7 and 8 and Figures 9 and 10 from that Chapter.) If we continue developing the card
game, we will design a class Dealer, which collaborates with cards and card decks
as well as with objects that are card players. At this point, we do not know exactly
how the card players will behave (this is dependent on the rules of the card game),
but class Dealer would expect that a card player has a method to receive a card
and has a method that replies whether a player wants to receive additional cards.
The obvious step is to write an interface; Figure 8 shows it. Now we can write a
510
Figure 9.8: interface for card playing
/** CardPlayerBehavior defines expected behaviors of card players */
public interface CardPlayerBehavior
{ /** wantsACard replies whether the player wants one more new card
* @return whether a card is wanted */
public boolean wantsACard();
/** receiveCard accepts a card and adds it to the player’s hand
* @param c - the card */
public void receiveCard(Card c);
}
class Dealer whose coding uses the interface to deal cards to players. (See Exercise
1, below.)
Next, we consider implementations of the CardPlayerBehavior interface. Perhaps
there are two formats of card players—computerized players and human players. (A
computerized player is an object that does card playing all by itself; a human player
is an object that helps the human user join in the play.) The two classes of player
receive cards in the same way, but when a human-player object is asked if it wants
a card, it asks the user for a decision. In contrast, a computerized-player object will
make the decision based on an algorithm — that is, the two players share the same
implementation of the receiveCard method but use different implementations of the
wantsACard method.
To accommodate the situation, we invent the abstract class, CardPlayer, that
acts as the superclass of both computerized and human card players. Figure 9 shows
abstract class CardPlayer; its subclasses, ComputerPlayer and HumanPlayer, appear
in Figure 10.
CardPlayer is labelled abstract because it is incomplete: It is missing its wantsACard
method (which it needs to implement CardPlayerBehavior). Only the header line
for wantsACard appears; it contains the keyword, abstract, and is terminated by a
semicolon. In contrast, receiveCard and another method, showCards, are written in
entirety.
The two classes in Figure 8, HumanPlayer and ComputerPlayer, extend the abstract
class, meaning the classes get the attributes and methods of a CardPlayer. The two
classes also supply their own versions of the missing wantsACard method.
At this point, there are no more missing methods, and we say that the classes
HumanPlayer and ComputerPlayer are concrete classes. Objects can be constructed
from concrete classes. (Recall that they cannot be constructed from abstract classes.)
Say that we construct two players:
ComputerPlayer p = new ComputerPlayer(3);
511
9.5. ABSTRACT CLASSES
Figure 9.9: abstract class for card players
/** CardPlayer models an abstract form of card player */
public abstract class CardPlayer implements CardPlayerBehavior
{ private Card[] my hand; // the player’s cards
private int card count; // how many cards are held in the hand
/** CardPlayer builds the player
* @param max cards - the maximum cards the player can hold.
public CardPlayer(int max cards)
{ my hand = new Card[max cards];
card count = 0;
}
*/
/** wantsACard replies whether the player wants one more new card
* @return whether a card is wanted */
public abstract boolean wantsACard();
// method will be written later
public void receiveCard(Card c)
{ my hand[card count] = c;
card count = card count + 1;
}
/** showCards displays the player’s hand
* @return an array holding the cards in the hand
public Card[] showCards()
{ Card[] answer = new Card[card count];
for ( int i = 0; i != card count; i = i + 1 )
{ answer[i] = my hand[i]; }
return answer;
}
}
*/
512
Figure 9.10: subclasses of CardPlayer
/** HumanPlayer models a
public class HumanPlayer
{ /** HumanPlayer builds
* @param max cards public HumanPlayer(int
{ super(max cards); }
human who plays cards */
extends CardPlayer
the player
the maximum cards the player can hold
max cards)
// invoke constructor in superclass
*/
public boolean wantsACard()
{ String response = JOptionPane.showInputDialog
("Do you want another card (Y or N)?");
return response.equals("Y");
}
}
/** ComputerPlayer models a computerized card player */
public class ComputerPlayer extends CardPlayer
{ /** ComputerPlayer builds the player
* @param max cards - the maximum cards the player can hold.
public ComputerPlayer(int max cards)
{ super(max cards); } // invoke constructor in superclass
public boolean wantsACard()
{ boolean decision;
Card[] what i have = showCards();
... statements go here that examine
calculate a decision ...
return decision;
}
}
what i have
and
*/
513
9.5. ABSTRACT CLASSES
HumanPlayer h = new HumanPlayer(3);
CardPlayer someone = p;
CardPlayerBehavior another = someone;
The following situation appears in computer storage:
a1 : ComputerPlayer
ComputerPlayer p == a1
public boolean wantsACard() { ...
// from CardPlayer:
HumanPlayer h == a3
public void receiveCard(Card c) { ...
public Card[] showCards() { ... }
a3 : HumanPlayer
public boolean wantsACard() { ...
// from CardPlayer:
int card count ==
0
int card count ==
CardPlayerBehavior another == a1
Card[] my hand ==
a2
Card[] my hand ==
CardPlayer someone == a1
}
}
a2 : Card[3]
}
.
.
.
a4
a4 : Card[3]
0
public void receiveCard(Card c) { ...
public Card[] showCards() { ... }
.
.
.
}
The fields (and methods) of class CardPlayer are adjoined to those of class ComputerPlayer
to make a ComputerPlayer object. The same happens for the HumanPlayer object.
The assignment, CardPlayer someone = p, reminds us that the data types ComputerPlayer
and HumanPlayer are subtypes of CardPlayer; objects of either of the first two types
can be used in situations where a CardPlayer object is required. This means we can
send the message, boolean ask = someone.wantsACard(), because this is a behavior
expected of a CardPlayer (even though no method was written for wantsACard in the
abstract class!).
The last assignment, CardPlayerBehavior another = someone, is also acceptable,
because interface names are data types. Although it is legal to say, boolean b =
another.wantsACard(), the Java compiler will complain about another.showCards(),
because the showCards method is not listed in interface CardPlayerBehavior; a cast
would be necessary: e.g.,
if ( another instanceof CardPlayer )
{ ((CardPlayer)another).showCards(); }
The instanceof operation examines the data type stored within the object named by
another and verifies that the type is a subtype of CardPlayer.
The above example has created these subtyping relationships:
514
Figure 9.11: architecture of dealer and card players
Dealer
CardPlayer
CardPlayerBehavior
wantsACard(): boolean
receiveCard(Card c)
wantsACard(): boolean
receiveCard(Card c)
showCards(): Card[]
HumanPlayer
wantsACard(): boolean
ComputerPlayer
wantsACard(): boolean
ComputerPlayer <=
CardPlayer
HumanPlayer
<=
CardPlayerBehavior
<=
Although the assignment, CardPlayer someone = p, was legal, this initialization
is not—CardPlayer someone = new CardPlayer(3)—because objects cannot be constructed from abstract classes.
Figure 11 shows the architecture we have assembled with the example. The diagram tells us that the Dealer collaborates through the CardPlayerBehavior interface,
which is implemented by the abstract class, CardPlayer. (The abstract parts are
stated in italics.)
Exercises
1. Write class Dealer based on this specification:
class Dealer
models a dealer of cards
Responsibilities (methods)
dealTo(CardPlayerBehavior p)
gives cards, one by one, to player p, until p no
longer wants a card
Collaborators:
CardPlayerBehavior, CardDeck, Card
2. In the above specification, replace all occurrences of CardPlayerBehavior by
CardPlayer. Rewrite class Dealer accordingly. Compare the advantages and
disadvantages of the two variants of class Dealer.
3. One abstract class can extend another; here is an example:
/** Point models a geometric point */
public class Point
{ private int x;
9.5. ABSTRACT CLASSES
515
private int y;
public Point(int a, int b)
{ x = a;
y = b;
}
public int xPosition() { return x; }
public int yPosition() { return y; }
}
/** Shape models a two-dimensional geometric shape */
public abstract class Shape
{ private Point upper_left_corner;
public Shape(Point location)
{ upper_left_corner = location; }
/** locationOf returns the location of the shape’s upper left corner */
public Point locationOf()
{ return upper_left_corner; }
/** widthOf returns the width of the shape, starting from its left corner */
public abstract int widthOf();
/** depthOf returns the depth of the shape, starting from its left corner */
public abstract int depthOf();
}
/** Polygon models a polygon shape */
public abstract class Polygon extends Shape
{ private Point[] end_points; // the nodes (corners) of the polygon
public Polygon(Point upper_left_corner)
{ super(upper_left_corner); }
/** setCorners remembers the nodes (corners) of the polygon */
public void setCorners(Point[] corners)
{ end_points = corners; }
}
Now, write a concrete class, Rectangle, that extends Polygon. The constructor
method for class Rectangle can look like this:
public Rectangle(Point location, int width, int height)
516
Figure 9.12: hierarchy of animals
Animal
WarmBlooded (mammal)
Feline
Lion
Tiger
. . .
Equine
Bovine
. . .
Horse
Zebra
. . .
ColdBlooded (reptile)
. . .
Next, write a concrete class, Circle, that extends Shape.
9.5.2
Class Hierarchies
When we write programs that manipulate many different types of objects, we will
find it helpful to draw the classes as a “hierarchy” or “taxonomy” based on their
subclass relationships.
This approach comes from real-life taxonomies, like the one in Figure 12, which
simplistically models the zoology of part of the animal kingdom.
The characteristics of animals appear at the internal positions of the hierarchy,
and actual animals appear at the end positions—the “leaves” of the “tree.” (Animal
is the “root” of the “tree.”) When one characteristic is listed beneath another, it
means that the first is more specific or a “customization” of the second, in the sense
that the first has all the behaviors and characteristics of the second. For example,
being Equine means having all the characteristics of a WarmBlooded entity.
The zoological taxonomy helps us make sense of the variety of animals and reduces
the amount of analysis we apply to the animal kingdom. In a similar way, in a
taxonomy of classes, when a class, A is a superclass of class B, we place B underneath
A in the hierarchy.
For example, if we designed a computer program that worked with the animalkingdom taxonomy, the program would create objects for the classes at the leaves,
for example,
Horse black_beauty = new Horse(...);
and it might assign,
9.5. ABSTRACT CLASSES
517
Equine a_good_specimen = black_beauty;
But the program would not create objects from the non-leaf classes, such as new
Equine(), because there are no such animals.
This example suggests that the non-leaf entries of a class hierarchy represent
typically (but not always!) abstract classes, and the leaves must be concrete classes
from which objects are constructed. The hierarchy can be converted into a collection
of classes, that extend one another, e.g.,
public abstract class Animal
{
// fields and methods that describe an animal
}
public abstract class WarmBlooded extends Animal
{
// additional fields and methods specific to warm-blooded animals
}
public abstract class Equine extends WarmBlooded
{
// fields and methods specific to equines
}
public class Horse extends Equine
{
// fields and completed methods specific to horses
}
Here is a more computing-oriented example: Figure 13 shows a hierarchy that
might arise from listing the forms one would draw in a graphics window. The forms
that one actually draws reside (primarily) at the leaves of the tree; the non-leaf entries
are adjectives that list aspects or partial behaviors. (Although this rule is not hard
and fast—arbitrary Polygon objects are possible.)
The primary benefit of working with class hierarchies is that a hierarchy collects
together related classes, meaning that commonly used attributes and methods can be
written within abstract classes and shared by concrete classes. The major negative
aspect of a class hierarchy is its size—one actual object might be constructed by
extending many classes, which might be located in many different files. For example,
a Triangle object constructed from Figure 13 is a composite of classes Triangle,
Polygon, Shape, and Form; a programmer will quickly grow weary from reading four
(or more) distinct classes to understand the interface and internal structure of just
one object! If possible, limit the hierarchies you write to a depth of two or three
classes.
518
Figure 9.13: hierarchy of forms for drawing
Form
Point
Line
Straight
Jagged
Curved
Shape
Curved
Circle
Ellipse
Polygon
Triangle
Rectangle
A documentation tool, like javadoc, can generate helpful interface documentation
for a class hierarchy; see the section, “Generating Package APIs with javadoc,” which
follows.
Exercises
1. Reread the Exercise from the section, “Abstract Classes,” that displayed several geometric forms. Use the classes in that exercise to write classes for the
hierarchy in Figure 13. Say that class Form goes as follows:
/** Form is the root of the geometric forms hierarchy */
public abstract class Form { }
Alter classes Point and Shape so that they fit into the hierarchy. Next, write
the classes for Line and Straight. (Remember that a line has two end points.
A straight line has no additional points, whereas jagged lines require additional
points and curves require additional information.)
2. Write taxonomies of the following:
(a) the people—teachers, students, and staff—who work in a school;
(b) the different forms of motorized vehicles (cars, trucks, cycles);
(c) forms of fruits and vegetables;
9.6. SUBTYPES VERSUS SUBCLASSES
519
(d) forms of music;
(e) forms of dance;
(f) computer input/output devices;
(g) people who work for a candy company (managers, chefs, assembly-line
workers, tasters, salespeople)
9.5.3
Frameworks and Abstract Classes
An abstract class is an “incomplete program,” and we might use a collection of
abstract classes to write a complex, powerful, but incomplete program that another
programmer finishes by writing one or two concrete classes.
A framework is such an incomplete program—it is a collection of classes and
interfaces organized into an architecture for a particular application area, such as
building graphics windows, or generating animations, or building spreadsheets, or
writing music, or creating card games. Some of the framework’s classes are left
abstract—incomplete. This is deliberate—a programmer uses the framework to build
a specific graphics window (or a specific animation, or spreadsheet, or song, or game)
by writing concrete classes that extend the abstract ones. The result is a complete
application that creates what the programmer desires with little effort.
For example, say that someone wrote for us a framework for building graphics
windows. Such a framework would contain classes that do the hard work of calculating colors, shapes, and sizes and displaying them on the computer console. Most
importantly, the framework would also contain an abstract class, say, by the name of
class GraphicsWindow, that already contains methods like setSize, and setVisible
but lacks a paint method. A programmer would use the framework and class
GraphicsWindow in particular to write concrete classes that extend GraphicsWindow
with paint methods. By using the framework, the programmer generates graphics
windows with minimal effort.
We have been doing something similar, of course, when we used Java’s Abstract
Window Toolkit (java.awt) and Swing (javax.swing) packages to build graphics windows. These two packages form a framework for a range of graphics applications,
and by extending class JPanel, we “complete” the framework and create graphics
windows with little effort on our own part.
The next chapter surveys the AWT/Swing framework.
9.6
Subtypes versus Subclasses
It is time to review the crucial distinctions between Java interfaces and subclasses:
• An interface defines a behavioral specification or “connection point” between
classes or subassemblies that are developed separately. When a class implements
520
an interface, the class provides, with its methods, the behavior promised by the
interface.
• A subclass provides codings—implementations—of some methods that are omitted from the superclass. When a class extends another, it inherits the existing
coded methods and provides codings for additional ones.
In a nutshell,
Interfaces list behaviors, subclasses list codings.
Therefore,
• Use an abstract class or a superclass when you are building a “family” of related
classes whose internal structures are similar; the superclass holds the coding
common to all the subclasses.
• Use an interface when you are connecting classes together, and you do not know
or care how the classes will be coded.
Because interfaces and abstract classes have different purposes, it is acceptable to
use both when a subassembly, as we saw in the case study with the varieties of card
players.
One reason why interfaces and subclasses are confused is because both of them
define subtype relationships. As noted earlier in the Chapter, if class C implements
I, then data type C is a subtype of I written, C <= I. Similarly, if class B extends
A, then B <= A. But remember that “subtype” is different from “subclass” — if C <=
D, it does not mean that C and D are classes and C is a subclass of D. Indeed, D and
even C might be interfaces.
9.7
class Object
and Wrappers
In the section, “Class Hierarchies,” we saw how collections of classes are grouped into
hierarchies. The Java compiler forces such a hierarchy upon a user whether she desires
it or not: Within the package java.lang, there is a class Object, which defines basic
coding that all Java objects must have for construction in computer storage. The
Java compiler automatically attaches extends Object to every class that does not
already extend another.
For example, if we wrote
public class A {...}
public class B extends A {...}
the Java compiler treats the first class as if it were written
9.7. CLASS OBJECT AND WRAPPERS
521
public class A extends Object {...}
The practical upshot of this transformation is C <= Object, for every class, C. In
this way, class Object defines a type Object which is the “data type of all objects.”
Some programmers exploit the situation by writing methods whose arguments use
type Object, e.g., here is a class that can hold any two objects whatsoever:
public class Pair
{ Object[] r = new Object[2];
public Pair(Object ob1, Object ob2)
{ r[0] = ob1;
r[1] = ob2;
}
public Object getFirst()
{ return r[0]; }
public Object getSecond()
{ return r[1]; }
}
For example,
Pair p = new Pair(new JPanel(), new int[3]);
Object item = p.getFirst();
if ( item instanceof JPanel )
{ (JPanel)item.setVisible(true); }
Because class Pair is written to hold and deliver objects of type Object, a cast is
needed to do any useful work with the objects returned from its methods.
Of course, primitive values like integers, booleans, and doubles are not objects,
so they cannot be used with class Pair. But it is possible to use class Integer,
class Boolean, and class Double as so-called wrappers and embed primitive values
into objects. For example, if we write
Integer wrapped_int = new Integer(3);
this creates an object of type Integer that holds 3. Now, we might use it with class
Pair:
Pair p = new Pair(wrapped_int, new int[3]);
Object item = p.getFirst();
if ( item instanceof Integer )
{ System.out.println( ((Integer)item).intValue() + 4 ); }
522
In a similar way, we can “wrap” a boolean within a new Boolean and later use
the booleanValue() method and wrap a double within a new Double and use the
doubleValue() method.
Finally, since every object is built from a class that extends Object, every object
inherits from class Object a method, toString, that returns a string representation
of an object’s “identity.” For example, if we write
Integer wrapped_int = new Integer(3);
String s = "abc";
Pair p = new Pair(wrapped_int, s);
System.out.println(wrapped_int.toString());
System.out.println(s.toString());
System.out.println(p.toString());
}
We receive this output:
3
abc
Pair@1f14c60
The third line is the string representation of p; it displays the type of p’s object as
saved in computer storage and a coding, called a hash code, of the object’s storage
address. The toString method can prove useful for printing tracing information when
locating program errors.
9.8
Packages
When you write an application or a subassembly that consists of multiple classes,
it is best to keep the classes together in their own folder (disk directory). (If you
have been using an IDE to develop your applications, the IDE has done this for you,
under the guise of a “project name” for each application you write.) If you keep
each application’s classes in its own folder, you will better manage your continuously
growing collection of files, and other programmers will find it easier to use your files,
because they need only to remember the folder names where the application live.
A Java package is a folder of classes where the classes in the folder are marked as
belonging together. We have used several Java packages already, such as java.util,
java.awt, and javax.swing. When you write a program that requires components
from a package named P, you must insert an import P.* statement at the beginning
of your program. The import statement alerts the Java compiler to search inside
package P for the classes your program requires.
We can make our own packages. Say that we wish to group the components
written for bank accounting, listed in Figures 1 and 3, into a package named Bank.
We do the following:
9.8. PACKAGES
523
• Create a folder (called a “project package,” if you are using an IDE) with the
name Bank, and move the classes into this folder.
• Insert, as the first line of each class, the statement package Bank. For example,
Figure 1 is revised to read,
package Bank;
/** BankAccountSpecification specifies the expected behavior of a
* bank account.
*/
public interface BankAccountSpecification
{
... // the body of the interface remains the same
}
and Figure 3 is revised as follows:
package Bank;
/** BankAccount manages a single bank account; as stated in its
* header line, it _implements_ the BankAccountSpecification: */
public class BankAccount implements BankAccountSpecification
{
... // the body of the class remains the same
}
• Compile each of the classes. If you are using an IDE, this step is the usual
one. If you are using the JDK, you must first close the folder, and then you
compile each class by mentioning both its folder and file names, e.g., javac
Bank\BankAccount.java
• Say that the package you assembled contained a class that has a main method,
and you wish to execute that class. To execute an application that is contained
in a package, you may proceed as usual when you use an IDE.
If you are using the JDK, then you must state both the package name and the
class name to execute. For example, pretend that the Bank package contained
a file, MortgagePaymentApplication.java, that has a main method. We compile
this file like the others, e.g., javac Bank\MortgagePaymentApplication.java.
We execute the class by typing java Bank.MortgagePaymentApplication. Note
the dot rather than the slash.
Once a collection of classes is grouped in a package, other applications can import
the package, just like you have imported javax.swing to use its graphics classes. For
example, perhaps we write a new application, class MyCheckingAccount, so that it
uses classes in package Bank. to do this, insert import Bank.* at the beginning of the
class. This makes the components in package Bank immediately available, e.g.,
524
import Bank.*;
/** MyCheckingAccount contains methods for managing my account */
public class MyCheckingAccount
{ private BankAccount my_account = new BankAccount();
private MortgagePaymentCalculator calculator
= new MortgagePaymentCalculator(my_account);
...
}
If other applications will import the Bank package, then the package must be
placed where the applications can find it. You do this by adding Bank’s directory
path to the Java compiler’s classpath.
(Begin Footnote: A classpath is a list of paths to folders that hold packages.
Whenever you compile and execute a Java program, a classpath is automatically
used to locate the needed classes. The usual classpath includes paths to the standard
packages, java.lang, java.util, etc. If you use an IDE, you can read and update the
classpath by selecting the appropriate menu item. (Usually, you “Edit” the “Project
Preferences” to see and change the classpath.) If you use the JDK, you must edit the
environment variable, CLASSPATH, to change the path. End Footnote)
For example, if you created the package, Bank, as a folder within the folder,
C:\JavaPgms, then add C:\JavaPgms to the Java compiler’s classpath. If you do not
wish to fight classpaths but wish to experiment with packages nonetheless, you can
move the Bank folder into the folder where you develop your programs. (For example,
if you do your development work in the folder, A:\MyWork\JavaExperiments, move
Bank into that folder.) This also works when one package contains classes that use
classes from another package—keep both packages within the same folder.
Exercise
Create a package from an application you have written. (Or, create the package,
Bounce, from the classes of the moving ball animation in Figures 8 through 10, Chapter
7.)
9.8.1
Generating Package APIs with
javadoc
Recall that a class’s Application Programming Interface (API) documents the class’s
interface in a readable format, say, as a web page. This can be done for a package,
also. Given a package, P, located in a folder of the same name, we can type at the
command line,
javadoc P
(If you use an IDE, consult the IDE’s user guide for information about javadoc.) The
javadoc program extracts the commentary from each class and creates a collection of
9.8. PACKAGES
525
HTML pages, most notably, package-summary.html, which contains links to the API
pages for each class in the package.
For example, here is the page created for the Bank package:
By clicking on the link for, say, BankAccountSpecification, we see the documentation
526
for the interface:
Exercise
Use javadoc to generate the API documentation for a package you have created.
9.9
Case Study: An Adventure Game
(Note: This section can be skipped on first reading.)
527
9.9. CASE STUDY: AN ADVENTURE GAME
Figure 9.14: Interfaces for an adventure game
/** RoomBehavior defines the behavior of a room */
public interface RoomBehavior
{ /** enter lets a player enter a room
* @param p - the player who wishes to enter
* @return whether the player sucessfully opened the door and entered. */
public boolean enter(PlayerBehavior p);
/** exit ejects a player from the room.
* @param p - the player who wishes to leave the room
public void exit(PlayerBehavior p);
*/
/** occupantOf returns the identity of the room’s occupant
* @return the address of the occupant object;
*
if room unoccupied, return null */
public PlayerBehavior occupantOf();
}
/** PlayerBehavior defines the behavior of a player of an adventure game */
public interface PlayerBehavior
{ /** speak lets the player say one word
* @return the word */
public String speak();
/** explore attempts to enter a room and explore it
* @param r - the room that will be explored
* @return whether the room was successfully entered */
public boolean explore(RoomBehavior r);
}
We finish the chapter by applying interfaces and inheritance to design a complex
model whose components connect together in multiple, unpredictable ways: We are
building an “adventure game,” where players can enter and exit rooms. A player
enters an unoccupied room by speaking the “secret word” that opens the room’s
door. A player exits a room whenever she chooses.
We want the adventure game to be as general as possible, so we retrict as little as
possible the notions of “player” and “room.” To start, we write specifications (Java
interfaces) that state the minimal expected behaviors of rooms and players. The
interfaces might appear as in Figure 14. The two interfaces depend on each other for
stating their respective behaviors, so the two interfaces must be compiled together.
(To do this, place the files, RoomBehavior.java and PlayerBehavior.java in the same
folder and compile one of them; the Java compiler will automatically compile both.)
528
Although we know nothing about the kinds of players in the adventure game, we
can use the interfaces to write a basic class of room for the game; see Figure 15.
A BasicRoom object remembers the address of the player that occupies it, and it is
initialized with a null address for its occupant. When a PlayerBehavior object wishes
to enter the room, it sends an enter message, enclosing its address as an argument.
The BasicRoom is not so interesting, but it is a good “building block” for designing
more elaborate rooms.
Next, we might write a class of player that remembers who it is and where it is.
Figure 16 shows the class.
Method explore of class Explorer uses the enter method of interface RoomBehavior
when it explores a room. The novelty within the method is the keyword, this. When
we write,
boolean went_inside = r.enter(this);
the keyword, this, computes to the address of this very object that sends the message
to object r. Whenever you see the keyword, this, read it as “this very object that is
sending the message.”
To understand this, consider this example:
RoomBehavior[] ground_floor = new RoomBehavior[4];
ground_floor[0] = new BasicRoom("kitchen", "pasta");
ground_floor[3] = new BasicRoom("lounge", "swordfish");
Explorer harpo = new Explorer("Harpo Marx", "swordfish");
Explorer chico = new Explorer("Chico Marx", "tomato");
boolean success = harpo.explore(ground_floor[3]);
Upon completion of the last statment, where the harpo.explore method is invoked,
529
9.9. CASE STUDY: AN ADVENTURE GAME
Figure 9.15: room for an adventure game
/** BasicRoom models a room that can have at most one resident at a time */
public class BasicRoom implements RoomBehavior
{ private PlayerBehavior occupant; // who is inside the room at the moment
private String rooms name;
private String secret word; // the password for room entry
/** Constructor BasicRoom builds the room.
* @param name - the room’s name
* @param password - the secret word for entry into the room */
public BasicRoom(String name, String password)
{ occupant = null; // no one is in the room initially
rooms name = name;
secret word = password;
}
public boolean enter(PlayerBehavior p)
{ boolean result = false;
if ( occupant == null && secret word.equals(p.speak())
{ occupant = p;
result = true;
}
return result;
}
)
public void exit(PlayerBehavior p)
{ if ( occupant == p ) // is p indeed in this room at the moment?
{ occupant = null; }
}
public PlayerBehavior occupantOf()
{ return occupant; }
}
530
Figure 9.16: a player that can explore rooms
/** Explorer models a player who explores rooms */
public class Explorer implements PlayerBehavior
{ private String my name;
private String my secret word; // a password for entering rooms
private RoomBehavior where I am now; // the room this object occupies
/** Constructor Explorer builds the Player
* @param name - the player’s name
* @param word - the password the player can speak */
public Explorer(String name, String word)
{ my name = name;
my secret word = word;
where I am now = null; // player does not start inside any room
}
public String speak()
{ return my secret word; }
/** exitRoom causes the player to leave the room it occupies, if any */
public void exitRoom()
{ if ( where I am now != null )
{ where I am now.exit(this);
// exit the room
where I am now = null;
}
}
public boolean explore(RoomBehavior r)
{ if ( where I am now != null )
{ exitRoom(); } // exit current room to go to room r:
boolean went inside = r.enter(this); // ‘‘this’’ means ‘‘this object’’
if ( went inside )
{ where I am now = r; }
return went inside;
}
/** locationOf returns the room that is occupied by this player */
public RoomBehavior locationOf()
{ return where I am now; }
}
531
9.9. CASE STUDY: AN ADVENTURE GAME
we have this storage configuration, which shows that object a4 has entered room a3:
a1 : RoomBehavior[4]
RoomRoomBehavior[] ground floor == a1
Explorer harpo ==
a4
Explorer chico ==
a5
0
a2
a2 : BasicRoom
null
3
null null a3
private PlayerBehavior occupant ==
"kitchen"
private String rooms name ==
private String secret word ==
private String rooms name ==
"pasta"
public boolean enter(PlayerBehavior p) { ...
public void exit(PlayerBehavior p) { ... }
public PlayerBehavior occupantOf() { ... }
private String secret word ==
a4
"lounge"
"swordfish"
public boolean enter(PlayerBehavior p) { ...
public void exit(PlayerBehavior p) { ... }
public PlayerBehavior occupantOf() { ... }
}
a4 : Explorer
}
a5 : Explorer
"Harpo Marx"
private String my secret word ==
"swordfish"
private RoomBehavior where I am now ==
public
public
public
public
2
a3 : BasicRoom
private PlayerBehavior occupant ==
private String my name ==
1
String speak()
void exitRoom()
boolean explore(RoomBehavior r)
RoomBehavior locationOf()
a3
private String my name ==
"Chico Marx"
private String my secret word ==
"tomato"
private RoomBehavior where I am now ==
public
public
public
public
null
String speak()
void exitRoom()
boolean explore(RoomBehavior r)
RoomBehavior locationOf()
Because the object named harpo lives at address a4, the invocation, harpo.explore(ground floor[3]),
computes to a4.explore(a3). Within a4’s explore method, there is the invocation,
r.enter(this), which computes to a3.enter(a4), because this computes to a4, the
address of this object in which the invocation appears.
Exercises
1. Write this class:
class Dungeon implements RoomBehavior
so that any object with PlayerBehavior can enter the room; no object that
enters the room can ever exit it; and when asked by means of its occupantOf
method, the Dungeon replies that no one is in the room.
532
Figure 9.17: interface for treasures
/** TreasureProperty defines a treasure object */
public interface TreasureProperty
{ /** contentsOf explains what the treasure is
* @return the explanation */
public String contentsOf();
}
2. After working the previous exercise, construct an array of size 3, so that the first
two rooms in the array are BasicRoom objects named "kitchen" and "lounge",
respectively, and the third object is a Dungeon. Then, construct
Explorer harpo = new Explorer("Harpo Marx", "swordfish");
and write a for-loop that makes harpo systematically try to enter and then exit
each room in the array.
3. A close examination of Figures 15 and 16 shows us that it is possible for one
Explorer object to occupy simultaneously multiple BasicRooms. Write a sequence of Java statements that makes this happen. (Hint: in the example at
the end of the section, make both rooms use the same password.)
How can we make a BasicRoom object “smart” enough so that it refuses to let
a player be its occupant when the player currently occupies another room? To
do this, add this method to interface PlayerBehavior:
/** locationOf returns the room that is occupied by this player */
public RoomBehavior locationOf();
Now, rewrite the enter method of BasicRoom.
9.9.1
Interfaces and Inheritance Together
Classes BasicRoom and Explorer are too simplistic for an interesting game. We might
design rooms that contain “treasures” so that players can take the treasures from the
rooms they enter.
Since a treasure might be a variety of things, it is simplest to define an interface
that gives the basic property of being a treasure. See Figure 17. Next, we can define
an interface that defines what it means for an object to hold a treasure. We might
write the interface in Figure 18.
A room that possesses an entry and exit as well as a treasure must implement
both interface RoomBehavior as well as interface Treasury. It is indeed acceptable
for one class to implement two interfaces, and it might look like this:
9.9. CASE STUDY: AN ADVENTURE GAME
533
Figure 9.18: interface for a treasury
/** Treasury describes the method an object has for yielding
* a treasure */
public interface Treasury
{ /** yieldTreasure surrenders the room’s treasure
* @param p - the player who requests the treasure
* @return the treasure (or null, if the treasure is already taken) */
public TreasureProperty yieldTreasure(PlayerBehavior p);
}
public class Vault implements RoomBehavior, Treasury
{ private PlayerBehavior occupant; // who is inside the room at the moment
private String rooms_name;
private String secret_word; // the password for room entry
private TreasureProperty valuable;
// the treasure item held in this room
/** Constructor Vault builds the room.
* @param name - the room’s name
* @param password - the secret word for entry into the room
* @param item - the treasure to be saved in the room */
public Vault(String name, String password, TreasureProperty item)
{ occupant = null;
rooms_name = name;
secret_word = password;
valuable = item;
}
public boolean enter(PlayerBehavior p)
{ ... } // insert coding from Figure 15
public void exit(PlayerBehavor p)
{ ... } // insert coding from Figure 15
public PlayerBehavior occupantOf()
{ ... } // insert coding from Figure 15
public TreasureProperty yieldTreasure(PlayerBehavior p)
{ TreasureProperty answer = null;
if ( p == occupant )
{ answer = valuable;
valuable = null;
534
Figure 9.19: VaultRoom defined by inheritance
/** VaultRoom is a room that holds a treasure---it is constructed from
*
class BasicRoom, by means of inheritance, plus the structure below. */
public class VaultRoom extends BasicRoom implements Treasury
// because BasicRoom implements RoomBehavior, so does VaultRoom
{ private TreasureProperty valuable; // the treasure item held in this room
/** Constructor VaultRoom builds the room.
* @param name - the room’s name
* @param password - the secret word for entry into the room
* @param item - the treasure to be saved in the room */
public VaultRoom(String name, String password, TreasureProperty item)
{ // first, invoke class BasicRoom’s constructor method:
super(name, password); // ‘‘super’’ means ‘‘superclass’’
valuable = item;
}
public TreasureProperty yieldTreasure(PlayerBehavior p)
{ TreasureProperty answer = null;
if ( p == occupantOf() ) // invokes the occupantOf method in BasicRoom
// You can also state, super.occupantOf()
// Another format is,
this.occupantOf()
{ answer = valuable;
valuable = null;
}
return answer;
}
}
}
return answer;
}
}
In the general case, a class can implement an arbitrary number of interfaces; the
interfaces are listed in the implements clause, separated by commas, within the class’s
header line.
The coding of class Vault possesses a major flaw: It copies the codings of methods
already present in class BasicRoom. For this reason, we should rewrite the class so
that it uses inheritance to use BasicRoom’s methods. Figure 19 shows the corrected
coding.
The header line of class VaultRoom indicates that VaultRoom extends BasicRoom;
this makes VaultRoom a subclass of the superclass BasicRoom. The class also imple-
9.9. CASE STUDY: AN ADVENTURE GAME
535
ments the interface, Treasury, because it has a yieldTreasure method that matches
the one in that interface. (Indeed, the class also implements interface RoomBehavior,
because it extends BasicRoom and BasicRoom implements RoomBehavior.)
Within VaultRoom’s constructor method, we see super(name, password), which
invokes the constructor method of the superclass, ensuring that the private fields
within BasicRoom are initialized. (Recall that when the super-constructor is invoked,
it must be invoked as the first statement in the subclass’s constructor.)
Within the yieldTreasure method, we see an invocation of the occupantOf method
of BasicRoom:
if ( p == occupantOf() )
The invocation asks this object to execute its own occupantOf method to learn the
player that occupies it. The invocation is required because the field occupant is
declared as private to class BasicRoom, so subclass VaultRoom cannot reference it
directly.
As the comment next to the invocation states, we can also invoke this particular
method by
if ( p == super.occupantOf() )
This format asserts that the occupantOf method must be located in a superclass part
of this object. (If the method is not found in a superclass of VaultRoom, the Java
compiler will announce an error.) Finally, it is also acceptable to state
if ( p == this.occupantOf() )
which is equivalent to the first invocation and documents that the invocation message
is sent to this very object.
Using the above classes, say that we construct one BasicRoom object and one
VaultRoom object:
BasicRoom a = new BasicRoom("The Lounge", "Hello");
TreasureProperty the_treasure = new Jewel("diamond");
VaultRoom b = new VaultRoom("The Vault", "Open, please!", the_treasure);
where we define class Jewel this simply:
public class Jewel implements TreasureProperty
{ private String name; // the name of the jewel
public Jewel(String id)
{ name = id; }
public String contentsOf()
{ return name; }
}
536
Here is a picture of storage after the statements execute:
a1 : BasicRoom
BasicRoom a ==
a1
private PlayerBehavior occupant ==
TreasureProperty the treasure == a2
private String rooms name ==
VaultRoom b == a3
private String secret word ==
null
"The Lounge"
"Hello"
public boolean enter(PlayerBehavior p) { ...
public void exit(PlayerBehavior p) { ... }
public PlayerBehavior occupantOf() { ... }
a2 : Jewel
private String name ==
}
"diamond"
public String contentsOf() { ...
}
a3 : VaultRoom
// these fields and methods are from class VaultRoom:
private TreasureProperty valuable ==| a2 |
public TreasureProperty yieldTreasure(PlayerBehavior p) { ...
// these fields and method are from class BasicRoom:
private PlayerBehavior occupant ==
private String rooms name ==
private String secret word ==
}
null
"The Vault"
"Open, Please!"
public boolean enter(PlayerBehavior p) { ...
public void exit(PlayerBehavior p) { ... }
public PlayerBehavior occupantOf() { ... }
}
The objects in storage show us that the structure of class BasicRoom is used to build
the objects at addresses a1 and a3. In particular, the VaultRoom object at a3 inherited
the BasicRoom structure plus its own—all the methods of a BasicRoom object can be
used with a VaultRoom object.
Because VaultRoom extends BasicRoom, the resulting data types are related by
subtyping—data type VaultRoom is a subtype of data type BasicRoom. Further, since
BasicRoom is a subtype of RoomBehavior, then by transitivity, VaultRoom is a subtype
of RoomBehavior as well. Here is a drawing of the situation:
<=
BasicRoom
<=
Treasury
<=
RoomBehavior
VaultRoom
The Java compiler uses these subtyping relationships to verify that VaultRoom objects
are sent appropriate messages.
Finally, the diagram for the classes defined in this and the previous sections appears in Figure 20. The diagram shows how interfaces act as the connection points
537
9.9. CASE STUDY: AN ADVENTURE GAME
Figure 9.20: diagram for model of adventure game
RoomBehavior
BasicRoom
Treasury
PlayerBehavior
Explorer
VaultRoom
TreasureProperty
for the concrete classes; it shows that the classes BasicRoom and VaultRoom form a
subassembly that is kept separate from Explorer. We also see that a VaultRoom has
RoomBehavior and depends on PlayerBehavior. Indeed, as we study the diagram further, we might conclude that an Explorer might be profitably extended by methods
that fetch treasures from the rooms explored; this is left for the Exercises.
Exercises
1. Write a class that implements TreasureProperty:
public class GoldCoin implements TreasureProperty
and construct a Vault object with the name, "lounge" and the password,
"swordfish", to hold an object constructed from class GoldCoin.
2. Modify the method, explore, in Figure 10 so that once the player successfully
enters the room, it tries to extract the treasure from it. Note that not all rooms
will implement interface Treasury, however. (Hint: use instanceof.) Then,
construct an explorer named "Harpo Marx" that explores the "lounge".
3. Here is an interface:
/** TreasureHunterBehavior describes the behavior of a player that tries
* to take treasures */
public interface TreasureHunterBehavior
{ /** takeTreasure tries to extract the treasure from the current room
* that this player occupies.
* @return true if the treasure was succesfully extracted from the
*
room and saved by this player; return false otherwise */
public boolean takeTreasure();
538
/** getTreasures returns an array of all the treasures located so far
* by this player
* @return the array of treasures */
public TreasureProperty[] getTreasures();
}
Write this class:
/** TreasureHunter explores rooms and saves all the treasures it extracts
* from the rooms. */
public class TreasureHunter extends Explorer implements TreasureHunterBehavior
4. The TreasureHunter defined in the previous exercise does not automatically try
to take a treasure each time it successfully enter a room. (The TreasureHunter
must be sent an enter message followed by a takeTreasure message.)
We can create a treasure hunter that has more aggressive behavior: First write
this method:
/** explore attempts to enter a room and explore it. If the room is
* successfully entered, then an attempt is made to take the treasure
* from the room and keep it (if the room indeed holds a treasure).
* @param r - the room that will be explored
* @return whether the room was successfully entered */
public boolean explore(RoomBehavior r)
Now, insert your coding of the new explore into this class:
public class RuthlessHunter extends TreasureHunter
{ ...
public boolean explore(RoomBehavior r)
{ ... }
}
When we construct a RuthlessHunter, e.g.,
VaultRoom bank = new VaultRoom("Bank", "bah!", new Jewel("ruby"));
RuthlessHunter bart = new RuthlessHunter("Black Bart", "bah!");
bart.explore(bank);
The new explore method of the RuthlessHunter executes instead of the old,
same-named method in the superclass, Explorer. We say that the new explore
method overrides the old one.
Construct this object:
9.10. SUMMARY
539
Explorer indiana = new Explorer("Indiana Jones", "hello");
indiana.explore(bank);
Which version of explore method executes? Method overriding is studied in
the “Beyond the Basics” section at the end of this Chapter.
9.9.2
Inheritance of Interfaces
Interfaces can be connected by inheritance, just like classes are. For example, perhaps we want to ensure that any object that has Treasury behavior must also have
RoomBehavior. This can be enforced by changing the interface for the Treasury to
inherit RoomBehavior:
/** TreasuryBehavior describes a room that holds a treasure */
public interface TreasuryBehavior extends RoomBehavior
{ /** yieldTreasure surrenders the room’s treasure
* @param p - the player who requests the treasure
* @return the treasure (or null, if the treasure is already taken) */
public TreasureProperty yieldTreasure(PlayerBehavior p);
}
Because it extends RoomBehavior, interface TreasuryBehavior requires all the methods of RoomBehavior plus its own. It so happens that class VaultRoom in Figure
13, because it extends BasicRoom, also is capable of implementing TreasuryBehavior.
Indeed, we can change its header line accordingly:
public class VaultRoom extends BasicRoom implements Treasury, TreasuryBehavior
of course, if we no longer use interface Treasury in the game’s architecture, we can
shorten the header line to read merely
public class VaultRoom extends BasicRoom implements TreasuryBehavior
Because TreasuryBehavior extends RoomBehavior, the expected subtyping relation is established: TreasuryBehavior <= RoomBehavior. The Java compiler uses the
subtyping to check compatibility of classes to interfaces.
9.10
Summary
This chapter has presented four constructions that can improve the design and implementation of programs: the interface, subclass, abstract class, and package.
540
New Constructions
Here are examples of the constructions introduced in this chapter:
• interface (from Figure 1):
public interface BankAccountSpecification
{ public void deposit(int amount);
public boolean withdraw(int amount);
}
• inheritance (from Figure 10):
public class HumanPlayer extends CardPlayer
{ public HumanPlayer(int max_cards)
{ super(max_cards); }
public boolean wantsACard()
{ String response = JOptionPane.showInputDialog
("Do you want another card (Y or N)?");
return response.equals("Y");
}
}
• abstract class (from Figure 9):
public abstract class CardPlayer implements CardPlayerBehavior
{ private Card[] my_hand;
private int card_count;
public CardPlayer(int max_cards)
{ my_hand = new Card[max_cards];
card_count = 0;
}
public abstract boolean wantsACard();
public void receiveCard(Card c)
{ my_hand[card_count] = c;
card_count = card_count + 1;
}
}
• package:
// method will be written later
541
9.10. SUMMARY
package Bank;
/** BankAccount manages a single bank account; as stated in its
* header line, it _implements_ the BankAccountSpecification: */
public class BankAccount implements BankAccountSpecification
{ ... }
• instanceof operation:
Record mystery_record = db.find(mystery_key);
if ( mystery_record instanceof BasicPerson )
{ System.out.println( ((BasicPerson)mystery_record).nameOf() ); }
else { System.out.println("unknown record type"); }
• super:
public class PersonAddress extends Person
{ private String address;
public PersonAddress(String the_name, String the_addr)
{ super(the_name); // this gives the_name to Person’s
address = the_addr;
}
constructor
public String getAddress()
{ return address; }
}
New Terminology
• interface: a specification of the behaviors (methods) expected of a class. A Java
interface is a named collection of header lines of public methods.
• implementing an interface: writing a class that contains methods whose header
lines match the ones named in the interface.
• inheritance: writing a class that includes in itself the fields and methods of an
existing class and adding new ones. The keyword, extends, precedes the name
of the existing class that will be included in the new one.
• abstract class: a class that lacks some of its methods; the missing methods are
noted by a header line that contains the keyword, abstract.
• concrete class: a “normal” class, all of whose methods have bodies.
542
• package: a collection of classes that are grouped together in a folder and labelled
with a common name.
• subtyping relationship: a relationship between two data types; We write C <= D
to state that C is a subtype of D, meaning that C-typed values can be used in
any context where a D-typed values is expected.
• run-time data type: the data type that is saved inside an object when the object
is constructed in computer storage. The saved data type is the name of the class
from which the object was constructed.
• instanceof: a Java operation that examines the run-time data type of an object;
ob instanceof C returns true exactly when the run-time data type of ob is a
subtype of C.
• super: the statement that invokes the constructor method of a superclass within
the constructor method of its subclass.
• abstract method: a method without its body found in an abstract class. The
method’s body is supplied in a subclass of the abstract class.
• class hierarchy: a collection of abstract and concrete classes, arranged by their
super/subclass relationships, usually depicted as a tree structure.
• framework: a collection of classes designed to be augmented with only a few
additional classes to construct complete applications in a problem domain.
• class Object: a built-in Java class that is automatically a superclass to all
other classes.
• wrapper class: a class whose primary purpose is to hold a single primitive value,
thereby allowing the primitive value to be used as if it were an object, e.g., new
Integer(2) makes 2 into an object.
Points to Remember
• Interfaces are used to specify the behavior of a class that to be written; other
classes can refer to the interface in their codings. Interfaces are also used to
specify “connection points” to which subassemblies of an application may connect.
• A class implements an interface by having methods whose header lines match
the ones named in the interface.
• A Java interface defines a data type name, and when a class implements the
interface, the class’s data type name is a subtype of the interface name.
9.11. PROGRAMMING PROJECTS
543
• Just as you have written graphics-window classes that extend JPanel, you may
write your own class C and extend it by class D extends C. This makes D a
subclass of C, meaning that all of C’s structure is included within the structure
of a D-constructed object. Further, data type D is a subtype of C.
• An abstract class is used when you wish to specify a partially written class,
which has codings (bodies) for some of its methods. The classes that extend
the abstract class use the methods coded in the abstract class.
• Use packages to group together collections of classes that are likely to be used
together.
9.11
Programming Projects
1. Return to an application that you wrote in response to a Programming Project
in Chapter 7 or 8. Redesign and reimplement the application with the assistance
of interfaces and abstract classes. (If you did not work a substantial project
from either of those two chapters, then build the library database application
described in Project 6, Chapter 8.)
2. Build a computerized “adventure game,” where one or more players explore
rooms and collect treasures. Rooms are entered and exited through doors,
and every door connects to a passageway, which itself leads to zero or more
doors to other rooms. To orient herself, a player can ask a room its name.
Invent additional rules for the game (e.g., a player can leave behind in a room
a “message” for another player to find; treasures have point values; a player
“loses energy” as it goes from room to room and must find “food” to eat in a
room to regain energy, etc.)
3. Say that a company that sells chocolates, and the company requires a database
that remembers its salespeople and how many chocolates each has sold. The
database must also remember the managers of the salespeople and how well
each managers’ people have done at selling chocolates. Design and implement
the database; use interfaces where appropriate.
4. Design and build a telephone directory database program. The program must
manage a collection of patrons and their telephone numbers. Patrons are either
individuals or businesses; telephone numbers are either listed or unlisted. Input
to the program is a sequence of queries. Queries can take these forms:
• an individual’s name or a business’s name or a name that might be either.
(The program prints the patrons with that surname and their telephone
numbers.)
544
• a telephone number (The program prints the patron that owns that number.)
Remember that a patron might own more than one telephone number and that
unlisted numbers cannot be included in the response to a query that supplies a
patron’s name.
5. Say that a bank deals with two forms of customers: regular and preferred. (A
preferred customer is charged no service charge on her accounts.) The bank
offers three kinds of accounts: checking, savings, and checking-with-interest.
(See the Exercises for the “Abstract Classes” section for details about the three
forms of accounts.)
Design and implement two databases: one for the bank’s accounts and one
for the bank’s customers. Remember that a customer might own more than
one form of bank account, and every bank account has an owner, which is a
customer.
6. Construct a “framework” to help a programmer build card games where one
human player interacts with a dealer and competes against zero or more computerized players.
7. One of the Projects in Chapter 8 suggested that you implement a database
program for maintaining a sales inventory. Each sales item had this associated
information: item’s name, id number, wholesale price, retail price, and quantity
in stock.
Extend this problem as follows: Say that the inventory is for an automobile
company (or computer company or bicycle company or ...) that services several, related models of car (computer, bicycle). The parts inventory must be
organized so that each part is associated with the models of car that use it. Design a database that implements the parts inventory so that a user can obtain
(among other output data), for a specific model of car, the portions of a car,
the parts contained in each portion, and the prices of all the parts.
9.12
Beyond the Basics
9.12.1 Subclasses and Method Overriding
9.12.2 Semantics of Overriding
9.12.3 final components
9.12.4 Method Overloading
9.12. BEYOND THE BASICS
545
9.12.5 Semantics of Overloading
The optional sections that follow examine the consequences that arise when the same
name is given to more than one method. There are two variants of this phenomenon—
overriding and overloading
9.12.1
Subclasses and Method Overriding
As noted earlier in this chapter, we used inheritance to construct graphics panels:
public class MyPanel extends JPanel
{ ...
public void paintComponent(Graphics g) { ... }
...
}
The paintComponent method contains the instructions for painting on the panel.
There is an important detail which was skipper earlier: There already is a method
named paintComponent inside class JPanel, but the method does nothing! Nonetheless, the Java language lets us write a new, second coding of paintComponent, which
will be used with new MyPanel objects. The new coding of paintComponent overrides
the useless version.
(Begin Footnote: Perhaps class JPanel should have been written as an abstract
class, where its paintComponent method should have been written as public abstract
void paintComponent(Graphics g). But class JPanel is written so that its “default”
paintComponent lets a beginner easily construct a blank panel. End Footnote)
When one writes a class, C2, that extends a class, C1, and class C2 contains a
method whose name and formal parameters are exactly the same one in class C1,
then we say that the new method overrides the same-named method in C1. Here is a
small example: Given class C:
public class C
{ public C() { }
public int add(int i, int j)
{ return i + j; }
}
we can extend C and override its method:
public class D extends C
{ public D() { }
public int add(int i, int j)
{ return i * j; }
546
public int sub(in i, int j)
{ return i / j; }
}
The add method in D overrides the one in C. The intent is: new C() objects use the
add method coded in class C, and new D() objects use the add method in class D.
If we write these statements,
C a = new C();
D b = new D();
System.out.println(b.add(3, 2));
System.out.println(a.add(3, 2));
6 and then 5 appear on the display.
Method overriding is intended for “method improvement,” and the situation with
JPanel’s paintComponent method is typical: An existing class has an unexciting
method that can be improved within a subclass. Method override is also used to
make a subclass’s method “more intelligent” by using fields that are declared only in
the subclass. But method overriding can be complex, so we study more examples.
Figure 21 shows two classes that model bank accounts. A BasicAccount allows
only deposits (an escrow account, which holds one’s loan payment money, might be
an example); a CheckingAccount (“current” account) is a BasicAccount extended with
the ability to make withdrawals.
There is a problem: CheckingAccount’s withdraw method must alter the private
balance field of BasicAccount. Java allows such behavior, if the field in the superclass
is relabelled as protected. (Public access is still disallowed of a protected field, but
subclasses—and alas, other classes in the same package as these two—can alter the
field.)
This arrangement is not completely satisfactory, because it means that class
CheckingAccount depends on the internals of class BasicAccount, so our freedom to
alter and improve the components of BasicAccount is restricted. (Some people say
this situation “breaks encapsulation,” because the internals are “exposed” to another
class.) It might be be better to redesign the classes in the Figure so that they both
extend an abstract class, but we press on.
Next, Figure 22 introduces a class AccountWithInterest that models a checking
account that pays interest, provided that the balance in the account never falls below
a stated minimum. Here, the withdraw method must be “more intelligent” and monitor whether a withdrawal causes the account’s balance to fall below the minimum
amount for interest payment, and the new version of withdraw overrides the one in
the superclass.
The body of the new, overriding method uses the invocation, super.withdraw(amount),
to make the physical removal of the amount: The keyword, super, forces the method
547
9.12. BEYOND THE BASICS
Figure 9.21: two classes for bank accounts
/** BasicAccount is a bank account that holds money */
public class BasicAccount
{ protected int balance; // the money; note the keyword, ‘‘protected’’
/** BasicAccount creates the account
* @param initial amount - the starting balance */
public BasicAccount(int initial amount)
{ balance = initial amount; }
/** deposit adds money to the account
* @param amount - the amount to be deposited
public void deposit(int amount)
{ if ( amount > 0 )
{ balance = balance + amount; }
}
*/
/** balanceOf returns the current balance
* @return the balance */
public int balanceOf()
{ return balance; }
}
/** CheckingAccount is a basic account from which withdrawals can be made */
public class CheckingAccount extends BasicAccount
{
/** CheckingAccount creates the account
* @param initial amount - the starting balance */
public CheckingAccount(int initial amount)
{ super(initial amount); }
/** withdraw removes money from the account, if possible
* @param amount - the amount to be removed
* @return true, only if the balance is enough to make the withdrawal
public boolean withdraw(int amount)
{ boolean outcome = false;
if ( amount <= balance )
{ balance = balance - amount;
outcome = true;
}
return outcome;
}
}
*/
548
Figure 9.22: checking account with interest
/** AccountWithInterest models an interest bearing checking account */
public class AccountWithInterest extends CheckingAccount
{ int minimum balance; // the amount required to generate an interest payment
boolean eligible for interest; // whether the account has maintained
// the minimum balance for this time period
/** AccountWithInterest creates the account
* @param required minimum - the minimum balance that must be maintained
*
to qualify for an interest payment
* @param initial amount - the starting balance */
public AccountWithInterest(int required minimum, int initial balance)
{ super(initial balance);
minimum balance = required minimum;
eligible for interest = (initial balance > minimum balance);
}
/** withdraw removes money from the account, if possible
* @param amount - the amount to be removed
* @return true, only if the balance is enough to make the withdrawal */
public boolean withdraw(int amount)
{ boolean ok = super.withdraw(amount);
eligible for interest = eligible for interest
// is minimum maintained?
&& balanceOf() > minimum balance;
return ok;
}
/** computeInterest deposits an interest payment, if the account qualifies
* @param interest rate - the rate of interest, e.g., 0.05 for 5%
* @return whether the account qualified for an interest payment. */
public boolean computeInterest(double interest rate)
{ boolean outcome = false;
if ( eligible for interest )
{ int interest = (int)(balanceOf() * interest rate);
deposit(interest);
outcome = true;
}
eligible for interest = (balanceOf() > minimum balance); // reset
return outcome;
}
}
549
9.12. BEYOND THE BASICS
in the superclass—here, method withdraw in CheckingAccount—to be used. In this
way, the new withdraw method exploits the behavior of the method it overrides.
The override of withdraw in Figure 22 is acceptable programming style, because it
maintains the same responsibility of the withdraw method in Figure 21—to withdraw
money—but does so in a more intelligent way.
The classes in Figures 21 and 22 let us create a variety of different bank accounts
simultaneously, e.g.,
BasicAccount b = new BasicAccount(100);
CheckingAccount c = new BasicAccount(0);
AccountWithInterest i = new AccountWithInterest(2500, 4000);
creates these objects:
BasicAccount b ==
a1
CheckingAccount c ==
AccountWithInterest i ==
a1 : BasicAccount
int balance ==
a2
100
public void deposit(int amount) {...}
public int balanceOf() {...}
a3
a2 : CheckingAccount
public boolean withdraw(int amount)
// from BasicAccount:
int balance ==
0
public void deposit(int amount) {...}
public int balanceOf() {...}
a3 : AccountWithInterest
int minimum balance ==
2500
boolean eligible for interest ==
true
public boolean withdraw(int amount) {... super.withdraw(amount) ...}
public boolean computeInterest(double interest rate) {...}
// from CheckingAccount:
public boolean withdraw(int amount) {...}
// from BasicAccount:
int balance ==
4000
public void deposit(int amount) {...}
public int balanceOf() {...}
Each of its three objects has its own combination of fields and methods and behaves
uniquely to specific messages. At this point, if we send the message, i.withdraw(500),
the object at a3 uses its “newest” withdraw method, the one from class AccountWithInterest,
to make the withdrawal. This method itself invokes the older withdraw method to
complete the transaction. (The keyword, super, forces the older method to be used.)
550
9.12.2
Semantics of Overriding
The semantics of method override possesses a surprising feature: The method that
the Java compiler selects as the receiver of an invocation might be different from the
method that is selected when the invocation is executed!
You can avoid reading this section if you promise never to override a superclass’s
method that is invoked by other methods in the superclass. That is, don’t do this:
public class C1
{ ...
public ... f(...)
{ ... }
public ... g(...)
{ ... f(...) ... }
}
public class C2 extends C1
{ ...
public ... f(...)
{ ... }
}
This form of example causes surprising behavior when g is invoked.
To understand why, we start by reviewing the main points from the section, “Formal Description of Methods,” at the end of Chapter 5. There, we learned that the
Java compiler checks the data typing of every method invocation by selecting the
method that is the target of the invocation. For simplicity, we consider normal (nonstatic) invocations. As described in Chapter 5, data-type checking is a four-step
process:
Given an invocation,
[[ RECEIVER . ]]?
NAME0 ( EXPRESSION1, EXPRESSION2, ..., EXPRESSIONn)
the compiler
1. determines the data type of RECEIVER, which will be a class name or an interface
name, C0. (Note: Since Chapter 5, we have learned that RECEIVER can be the
keyword, super. In this case, the data type is the superclass of the class where
the method invocation appears. There is one more keyword, this, which can
appear as a RECEIVER. The data type of this is the class where the method
invocation appears.)
2. selects the best matching method for NAME0. This will be a method of the form
VISIBILITY TYPE0 NAME0(TYPE1 NAME1, TYPE2 NAME2, ..., TYPEn NAMEn)
9.12. BEYOND THE BASICS
551
that is found within class or interface C0 (or, if not in C0, then in C0’s superclass,
C1, or then in C1’s superclass, C2, and so on—see Chapter 5 for the algorithm).
3. attaches the header-line information to the invocation. The method invocation
now looks like this:
[[ RECEIVER . ]]? NAME0 (EXPRESSION1: TYPE1, EXPRESSION2: TYPE2,
..., EXPRESSIONn: TYPEn) SUFFIX;
where SUFFIX is one of
• public: the selected method is a public method
• private Ck: the selected method is a private method that resides in class
Ck
• super Ck: the RECEIVER is super, and the selected method is a public
method that resides in class Ck
4. returns TYPE0 as the result type of the invocation.
When a method invocation is executed, the Java Virtual Machine uses the typing
information attached by the Java compiler attached to locate the invoked method.
As stated in Chapter 5, given the annotated invocation,
[[
RECEIVER .
]]? NAME0 ( EXPRESSION1 : TYPE1, EXPRESSION2 : TYPE2,
..., EXPRESSIONn : TYPEn ) SUFFIX
the execution follows these five steps:
1. RECEIVER computes to an address of an object, a. (Note: If RECEIVER is omitted,
or is super or this, use the address of the object in which this invocation is
executing.)
2. Within the object at address a, select the method NAME0. This step depends on
the value of the SUFFIX:
• If it is private Ck, select the private method that came from class Ck
whose header line has this form:
private ... NAME0(TYPE1 NAME1, TYPE2 NAME2, ..., TYPEn NAMEn)
• If it is super Ck, select the public method that came from class Ck whose
header line has this form:
public ... NAME0(TYPE1 NAME1, TYPE2 NAME2, ..., TYPEn NAMEn)
• If it is public, look at the data type attached to object a; say that it is C0.
Search the public methods starting with the ones that came from class
C0, for a method whose header line has the form,
552
public ... NAME0(TYPE1 NAME1, TYPE2 NAME2, ..., TYPEn NAMEn)
If the method is found in class C0, select it. Otherwise, search the public
methods that came from C0’s superclass; repeat the search until the method
is found.
3. Evaluate the actual parameters.
4. Bind the actual parameters to the formal parameters.
5. Execute the body of the selected method.
Step 2 is of interest to us when an invocation’s SUFFIX is marked public. In this
case, the Java interpreter makes a search for a method named NAME0, and the method
that is selected might be different from the one that the Java compiler selected. We
see this in the example that follows.
Figure 23 defines two classes, C1 and C2, that hold integers and report their values.
Since the second class, C2, augments the integer held in its superclass, C1, with one
of its own, it overrides the stringVal function so that both integers are included
in the answer returned by the function. This raises an interesting question: Which
version of stringVal will be invoked within print? The answer depends on the object
created. For this case,
C1 c1 = new C1(3);
c1.print();
it is easy to guess correctly that value is C1: 3 is printed; print invokes the
stringVal method in C1. Indeed, when the Java compiler type checked class C1,
it came to the conclusion that print invokes stringVal in C1.
But if we write,
C2 c2 = new C2(4,5);
c2.print();
this code’s execution produces value is C1: 4 C2: 5. This occurs because print
is executed within a C2 object, and therefore uses the object’s “newest” version of
stringVal, which is the C2 version. This is not what was selected by the Java compiler,
but the execution works properly because the data typing of the newer version of
stringVal is identical to the data typing of the older version. This means the Java
compiler’s type checking efforts are not harmed.
We can see the rationale behind this execution result by drawing the object created by the above declaration. The drawing displays the annotations that the Java
9.12. BEYOND THE BASICS
Figure 9.23: example of method override
public class C1
{ private int i;
public C1(int x)
{ i = x; }
public String stringVal()
{ return f(i); }
public void print()
{ System.out.println( "value is " + stringVal() ); }
private String f(int x)
{ return "C1: " + x; }
}
public class C2 extends C1
{ private int j;
public C2(int x, int y)
{ super(x);
j = y;
}
public String stringVal()
{ return super.stringVal() + f(j) ; }
private String f(int x)
{ return " C2: " + x; }
}
553
554
compiler attached to each method invocation:
a2 : C2
C2 c2 ==
a2
private int j ==
5
public String stringVal()
{ return (super.stringVal() super C1) + (f(j: int) private C2); }
private String f(int x) { return " C2: " + x; }
// from class C1:
private int i ==
4
public String stringVal()
{ return (f(i: int) private C1); }
public void print()
{ System.out.println( "value is " + (stringVal() public) ); }
private String f(int x) { return "C1: " + x; }
The annotations, private Ci and super Ci, select specific methods for execution;
there is no re-search. But a method invocation that is labelled public requires a
search of all the public methods to find the newest version of the invoked method
name.
For the example, the compiler labels the starting invocation as c2.print() public.
First, c2 computes to address a2, and the public methods of a2 are searched for the
newest version of print. The only version, the one from class C1, is located, so the
statement,
System.out.println( "value is " + (stringVal() public) );
executes. This sends the message, stringVal() public, which is directed again to a2,
because there is no RECEIVER on the method name.
Another search proceeds, this time for the newest version of stringVal. This
time, the version from class C2 is selected. (This is a different result than the Java
compiler’s search, and certainly the author of class C1 might be a bit surprised as
well!)
The selected method executes this statement:
return
(super.stringVal() super C1) + (f(j: int) private C2);
The first message, super.stringVal() super C1, is directed again to a2, which is
forced to use the stringVal method from class C1, because of the SUFFIX, super C1.
The body of C1’s stringVal states,
return
(f(i: int) private C1);
which forces the private method named f from class C1 to execute. Therefore, the
string, "C1: 4", is returned as the answer.
Similarly, the invocation, f(j: int) private C2 forces class C2’s f method to
execute, producing "C2: 5". These strings are combined and produce the printed
answer, value is C1: 4 C2: 5.
9.12. BEYOND THE BASICS
555
In summary, the Java compiler selects precisely and correctly the private and
“super” methods used by invocations. But it is misled about invocations of public
methods—the method the compiler selects might be overridden by a newer version
(fortunately, with the same data typing!) when objects are created at execution.
Whether this is a positive feature of the Java language is subject to debate, but
in any case, you must be well aware of these execution behaviors when you write
overriding methods.
9.12.3
final
components
You can label a class or any of its components as final, if you wish to prohibit modifications to it.
A final class is labelled,
public final class C
{ ... }
Now, the Java compiler will not allow any other class to use the class as a superclass,
that is, public class D extends C is disallowed. One consequence is that no method
in C can be overridden.
When a method is labelled final, then it cannot be overridden. For example, if
the override of C1’s stringVal method in Figure 23 is upsetting, we can modify C1 to
read
public class C1
{ private int i;
public final String stringVal()
{ return f(i); }
public void print()
{ System.out.println( "value is " + stringVal() ); }
private String f(int x)
{ return "C1: " + x; }
}
This makes it impossible to override stringVal in class C2 (and likely forces C2 to
override the print method, which is a more benign override).
A third use of final is to restrict a variable so that once it is initialized, it can
not be altered. A simple example is,
private final int MAX_SIZE = 50;
which permanently fixes the value of MAX SIZE to 50.
A final variable can be initialized from within a constructor:
556
public class FrozenBankAccount
{ private final int balance;
public FrozenBankAccount(int final_balance)
{ balance = final_balance; }
...
// here, assignments to balance are disallowed
}
9.12.4
Method Overloading
In Chapter 6, Figure 15, we saw two methods in class BankWriter with the same
name:
/** BankWriter writes bank transactions */
public class BankWriter extends JPanel
{ ...
/** showTransaction displays the result of a monetary bank transaction
* @param message - the transaction
* @param amount - the amount of the transaction */
public void showTransaction(String message, int amount)
{ last_transaction = message + " " + unconvert(amount);
repaint();
}
/** showTransaction displays the result of a bank transation
* @param message - the transaction */
public void showTransaction(String message)
{ last_transaction = message;
repaint();
}
}
The two methods differ in the number of parameters they require; the method name,
showTransaction, is overloaded.
A method name is said to be overloaded when there are two methods that are
labelled with the same name but have different quantities or data types of formal
parameters; the two same-named methods appear in the same class (or one appears
in a class and another in a superclass).
The overloading technique is promoted as a “memory aid” for a programmer: If
there is a collection of methods that more-or-less act the “same way” but differ in the
parameters they require to “act,” then the methods can be overloaded. The above
example is typical—both variants of showTransaction display a transaction result,
9.12. BEYOND THE BASICS
557
but the first handles transactions that include text and numerical values, and the
second handles text-only transactions.
Overloading often appears in a class’s constructor methods:
public class BankAccount
{ private int balance;
public BankAccount()
{ balance = 0; }
public BankAccount(int initial_balance)
{ balance = initial_balance; }
...
}
Both constructor methods are initializing the bank account, but they differ in the
parameters they use to do so. Now, a bank-account object can be created two ways—
by new BankAccount() or by new BankAccount(1000), say.
Another form of overloading is based on parameter data type. The following
methods overload the name, displayValue, by distinguishing between the types of
the methods’ parameter:
public void displayValue(int i)
{ System.out.println(i); }
public void displayValue(String s)
{ System.out.println(s); }
public void displayValue(BankAccount b)
{ System.out.println(b.balanceOf()); }
It is easy to select the proper method based on the actual parameter supplied with the
method invocation, e.g., displayValue("Hello") or displayValue(new BankAccount(300)).
But some invocations do not match any of the methods, e.g., displayValue(2.5)—the
Java compiler will report an error in this situation.
Perhaps you have deduced that System.out’s println is itself overloaded. Indeed,
there are ten different methods! These include
public
public
public
public
public
public
void
void
void
void
void
void
println() {...}
println(boolean x) {...}
println(char x) {...}
println(int x) {...}
println(long x) {...}
println(double x) {...}
558
and so on. (Consult the Java API for details.)
In the case of println, we note that some of the data types used in the overloaded
method are related by subtyping. For example, int is a subtype of long and of double.
This suggests that any of the three println methods that can print integers, longs,
or doubles, can be used to execute System.out.println(3). But the method, public
void println(int x) {...} is selected because it is the best matching method for
the invocation.
But this approach gets quickly confusing. Say that the name, f, is overloaded by
these two methods:
public boolean f(int x)
{ System.out.println("nonfractional");
return true;
}
public boolean f(double x)
{ System.out.println("fractional");
return false;
}
The behavior of f(3) and f(3.5) are not too difficult to guess correctly. What about
long x = 3000000000;
... f(x) ...
That is, when the actual parameter is a long integer? There is no variant of f for
data type long, but since long is a subtype of double (and not of int), the parameter
is cast into a double, and the second variant is chosen; "fractional" is printed and
false is returned.
Figure 24 presents a more perplexing example. In the example, if we declare
OneInt s = new OneInt(5);
TwoInt t = new TwoInt(5, 2);
we get these two objects in storage:
a2 : TwoInt
OneInt s ==
a1
TwoInt t ==
a2
private int j ==
public int secondVal() { return j; }
// from class OneInt:
a1 : OneInt
private int i ==
2
5
public int firstVal() { return i; }
private int i ==
5
public int firstVal() { return i; }
Say that, in a third class, we write these two methods:
559
9.12. BEYOND THE BASICS
Figure 9.24: one class that extends another
/** OneInt is a ‘‘wrapper’’ for a single integer */
public class OneInt
{ private int i;
public OneInt(int x) { i = x; }
public int firstVal() { return i; }
}
/** TwoInt is a ‘‘wrapper’’ for a pair of integers. */
public class TwoInt extends OneInt
{ private int j;
public TwoInt(int x, int y)
{ super(x); // execute the constructor for
j = y;
}
OneInt,
the superclass
public int secondVal() { return j; }
}
public boolean equals(OneInt x, OneInt y)
{ return (x.firstVal() == y.firstVal()); }
public boolean equals(TwoInt x, TwoInt y)
{ return (x.firstVal() == y.firstVal()) &&
(x.secondVal() == y.secondVal()) }
These seem like reasonable definitions of equality checks for the two forms of objects.
But consider this statement, which uses objects s and t:
System.out.println(equals(s, t));
This statement prints true! There is no equals method when one actual parameter
has type OneInt and the other has type TwoInt, but since TwoInt is a subtype of
OneInt, actual parameter t is cast to OneInt, and the first method is selected.
If you are uncomfortable with the above answer, you can write these two additional
methods:
public boolean equals(OneInt x, TwoInt y)
{ return false; }
public boolean equals(TwoInt x, OneInt y)
{ return false; }
560
Now, all combinations of data types are listed, and equals(s, t) returns false. But
this example,
OneInt u = t;
System.out.println(equals(u, t));
returns false as well, even though variables u and t both hold a2 as their value!
The precise reasons why these behaviors unfold as they do are explained in the
next section. But the moral is, if at all possible, avoid overloading a method name by
methods that are distinguished by a formal parameter whose respective data types are
related by subtyping. That is,
• it is acceptable to overload a method name based on quantity of parameters
(cf., the showTransaction example earlier)
• it is acceptable to overload a method name based on the data type of a parameter, if the data types are unrelated by subtyping (cf. the displayValue
example)
but it is questionable to overload based on parameter data types related by subtyping
(cf. the equals example).
In many cases, what appears to be an “essential” use of overloading based on
parameter data type can be eliminated by augmenting the classes involved with an
abstract class. For example, we can retain all four of the above variants of equals if
we change, in a trivial way, the relationship between classes OneInt and TwoInt with
an abstract class:
public abstract class AbsInt
{ private int i;
public AbsInt(int x) { i = x; }
public int firstVal() { return i; }
}
public class OneInt extends AbsInt
{ public OneInt(int x)
{ super(x); }
}
public class TwoInt extends AbsInt
{ private int j;
public TwoInt(int x, int y)
{ super(x);
j = y;
9.12. BEYOND THE BASICS
561
}
public int secondVal() { return j; }
}
Since it is illegal to create AbsInt objects, class OneInt is essential. And, no longer
is there a subtyping relationship between OneInt and TwoInt, which was at the root
of our earlier difficulties.
9.12.5
Semantics of Overloading
The Java compiler must type check overloaded method names, like the ones just seen.
First, you should review the section, “Formal Description of Methods,” in Chapter 5,
and study closely the definition of “best matching method.” This definition must be
revised to handle the more difficult examples in the previous section.
You can avoid reading this section if if you promise never to overload a method
name based on parameter data types related by subtyping.
Revised Definition of Best Matching Method
Say that the Java compiler must locate the best matching method for this invocation:
[[
RECEIVER NAME0 .
]]? (EXPRESSION1, EXPRESSION2, ..., EXPRESSIONn)
Say that the compiler has calculated that C0 is the data type of the RECEIVER and say
that each EXPRESSIONi has data type Ti, for i in 1..n.
What method will be invoked by this invocation? If the invocation appears
within class C0 also, then the best matching method is the method the compiler
finds by searching(all public and private methods of class C0); Otherwise, the
best matching method is the method found by searching(all public methods of
class C0).
The algorithm for searching(some of the methods of class C0) is defined as follows:
Within some of the methods of class C0, find the method(s) whose header line
has the form,
... ... NAME0(TYPE1 NAME1, TYPE2 NAME2, ..., TYPEn NAMEn)
such that each Ti is a subtype of TYPEi, for all i in the range of 1..n.
• If exactly one such method definition of NAME0 in C0 is found, then this
method is selected.
562
• If there are two or more variants of NAME0 in C0 that fit the criterion stated
above (that is, NAME0 is overloaded), then the variant whose formal parameter types most closely match the types of the actual parameters is selected.
If none of the variants most closely match, then the invocation is said to
be ambiguous and is not well typed; the search fails. (The definition of
“most closely match” is given below.)
• If there is no method definition for NAME0 that is found, and if class C0
extends C1, then the best matching method comes from searching(all
public methods of class C1). But if class C0 extends no other class,
there is no best matching method, and the search fails.
Here is the definition of “most closely match”: Consider a single actual parameter,
E, with data type, T and the data types, T1, T2..., Tn, such that T is a subtype of each
of T1, T1, ... Tn. We say that one of the data types, Tk, most closely matches T if Tk
is itself a subtype of all of T1, T2, ..., Tn. (Tk is considered to be a subtype of itself by
default.)
Next, take this definition and apply it to all n of the actual parameters of a method
invocation: For a variant of NAME0 to mostly closely match a method invocation, the
data type of each of its formal parameters must most closely match the type of the
corresponding actual parameter of the method invocation.
If we return to the example in Figure 18 and reconsider this situation:
public boolean equals(OneInt x, OneInt y)
{ return (x.firstVal() == y.firstVal()); }
public boolean equals(TwoInt x, TwoInt y)
{ return (x.firstVal() == y.firstVal()) &&
(x.secondVal() == y.secondVal()) }
public boolean equals(OneInt x, TwoInt y)
{ return false; }
OneInt s = new OneInt(5);
TwoInt t = new TwoInt(5, 2);
System.out.println(equals(s, t));
we see that first and third methods named equal match the invocation, equals(s, t),
because the data types of the actual parameters are OneInt and TwoInt, respectively.
When we compare the data types of the formal parameters of the two methods, we
find that the third variant most closely matches.
Finally, we must note that the Java compiler in fact implements a more restrictive
version of best matching method, prohibiting some examples of overloading that
extend across super- and subclasses. For example, the Java compiler will refuse to
calculate a best matching method for this example:
9.12. BEYOND THE BASICS
563
public class A
{ public A() { }
public void f(int i) { System.out.println("A"); }
}
public class B extends A
{ public B() { }
public void f(double i) { System.out.println("B"); }
}
...
B ob = new B();
ob.f(3);
because the above definition of “best matching method” would select the version of
f in B as the best match to ob.f(3), even though there is a more appropriate version
in the superclass, A.
Chapter 10
Graphical User Interfaces and EventDriven Programming
10.1 Model-View-Controller Revisited
10.2 Events
10.3 The AWT/Swing Class Hierarchy
10.4 Simple Windows: Labels and Buttons
10.5 Handling an Event
10.5.1 A View as Action Listener
10.5.2 A Separate Controller
10.5.3 A Button-Controller
10.6 Richer Layout: Panels and Borders
10.6.1 An Animation in a Panel
10.7 Grid Layout
10.8 Scrolling Lists
10.9 Text Fields
10.10 Error Reporting with Dialogs
10.11 TextAreas and Menus
10.11.1 Case Study: Text Editor
10.12 Event-Driven Programming with Observers
10.12.1 Observers and the MVC-Architecture
10.13 Summary
10.14 Programming Projects
10.15 Beyond the Basics
10.1. MODEL-VIEW-CONTROLLER REVISITED
565
Just as the connection points between program components are called interfaces,
the “connection point” between a program and its human user is called its “user
interface.” A program that uses visual aids—buttons, scroll bars, menus, etc.—to
help a user enter input and read output is called a graphical user interface (“GUI”
for short, pronounced “goo-ee”).
In this chapter, we learn the following:
• how to employ Java’s AWT/Swing framework to design graphical user interfaces
• how to write programs whose controllers are distributed and event driven. That
is, a program has multiple controllers, and a controller executes when it is started
by a user action (event), e.g., a button press.
• how to use the observer design pattern to streamline the collaborations between
the components of a program that uses a GUI.
10.1
Model-View-Controller Revisited
The model-view-controller (MVC) architecture we have used for our programs was
first developed to manage programs with GUIs. The philosophy and terminology
behind MVC might be explained as follows:
• A computer program is a kind of “appliance,” like a television set, radio, or
hand-held calculator, so it should look like one: The program should have a
view or appearance like an appliance, so that its human user feels familiar with
it.
• Appliances have controls—switches, buttons, knobs, sliders—that their users
adjust to operate it. A program should also have controllers that its user adjusts
to execute the program. And for familiarity’s sake, the controllers should have
the appearance of switches, buttons, knobs, etc.
• An apppliance’s controls are connected to the appliance’s internal circuitry,
which does the work that its user intended. Similarly, a program’s controllers are
connected to the program’s model, which calculates results that are portrayed by
the program’s view. By “portrayed,” we mean that the view presents a picture
of the internal state of the model. Indeed, a program’s model might even have
multiple views that are presented simultaneously.
The manner in which the model, view, and controller collaborate is equally important: A user interacts with a program’s view, say, by adjusting one of its controllers.
This awakens the controller, which might examine the view for additional data and
then send messages to the program’s model. The model executes the controller’s
messages, computing results and updating its internal state. The view is then sent a
566
Figure 10.1: model-view-controller architecture
message (either indirectly by the model or directly by the controller) to display the
results. The view queries the model, getting information about the model’s state and
presenting this information to the user. Figure 1 depicts the situation.
Another important aspect about the MVC-architecture is that it can be composed,
that is, one component—say, the view—might be built from smaller components—
say, buttons, text fields, and menus—that are themselves constructed with their own
little MVC-architectures. For example, consider a text field, which is a component
within a view where a user can type text. The text field has its own little appearance
or view. (Typically, it is an area that displays the letters the user typed while the
mouse was positioned over the area.) When a user types a letter into the text field,
this activates the text field’s controller, which transfers the letter into the text field’s
internal model. Then, the text field’s view is told to refresh itself by asking the model
for all the letters that have been typed; the view displays the letters.
Of course, it would be a nightmare if we must design from scratch the little MVC
architectures for buttons, menus, text fields, and the other GUI components! For
this reason, we use a framework that contains prebuilt components and provides
interfaces for connecting them. In Java, the framework for building GUIs and connecting them to controllers and models is called AWT/Swing. (The AWT part is the
java.awt package; the Swing part is the javax.swing package. “AWT” stands for
10.2. EVENTS
567
“Abstract Window Toolkit.”) The bulk of this chapter introduces a useful subset of
AWT/Swing.
Exercise
Consider a television set. What are its controllers? model? view? Answer the same
questions for a calculator. How can a television have multiple views? How can a
calculator have multiple views?
10.2
Events
In the programs we built in previous chapters, the program’s controller was “in
control”—it controlled the sequence of steps the user took to enter input, it controlled
the computation that followed, and it controlled the production of the program’s output. When one employs a GUI, this changes—the program’s user decides when to
enter input, and the controllers must react accordingly. The moving of the mouse,
the pushing of a button, the typing of text, and the selection of a menu item are all
forms of input data, and the controllers must be prepared to calculate output from
these forms of input. The new forms of input are called events, and the style of
programming used to process events is called event-driven programming.
Event-driven programming is more complex than the programming style we employed in earlier chapters: When a program receives events as input, a coherent “unit”
of input might consist of multiple events (e.g., a mouse movement to a menu, a menu
selection, text entry, mouse movement to a button, and a button push); the program
that receives this sequence of events must be written so that it can
• process each individual event correctly—this is called handling the event. The
controller that handles the event is sometimes called the event handler or event
listener.
• accumulate information from handling the sequence of events and generate output. Typically, the controllers that handle the events save information about
them in model objects.
Further, the user of the program might generate events in unexpected or incorrect
orders, and event handlers must be prepared to handle unwelcome events.
The previous examples should also make clear that event-driven programs use
multiple controllers (event handlers), so there is no longer one controller that oversees
the execution of the entire program. Instead, execution is distributed across multiple
controllers that are activated at the whim of a user. For this reason, an event-driven
program is a bit like a crew of night-duty telephone operators who are repeatedly
awakened by telephone calls (events) and must process each call in a way that keeps
the telephone station operating smoothly through the night. The telephone operators
568
cannot predict when the telephone will ring and what each call’s request might be;
the operators must react to the evening’s events rather than dictate what they might
be.
To assist an event driven program, a programmer must design the program’s GUI
so that
• sequences of events are organized into natural units for processing
• it is difficult or impossible for the user to generate a truly nonsensical sequence
of events
To help with the first objective, we will usually design our GUIs so that computation occurs only after a sequence of events terminates with a button push or a menu
selection. (In Java, button pushes and menu selections are called action events, and
their event handlers are called action listeners.) We will let mouse-movement and
text-entry events be handled by the default event handlers that are already built into
the components of the Java AWT/Swing framework.
To assist with the second objective, we will design our GUIs so that the action
events that can be generated from a window can occur in any order at all. If a program
must enforce that one action event must occur before another, we will program the
GUI so that the first action event causes a secondary window to appear from which
the second action event can be generated.
Because we let the AWT/Swing components handle mouse movement events and
text entry events, we need not write extra controllers to monitor the position of the
mouse on the display or to monitor every key press into a text field—we use the
code that is already in place in the default codings of windows and text fields. This
should make clear why a framework is so useful for building graphical user interfaces:
Many intelligent classes are available for immediate use, and we need worry only
about programming controllers for those events (here, action events) that we choose
to handle in a customized way.
10.3
The AWT/Swing Class Hierarchy
Before we write GUIs with Java’s AWT/Swing framework, we must survey the components provided by the framework. We begin with some terminology.
An entity that can have a position and size (on the display screen) and can have
events occur within it is called a component. A component that can hold other
components is a container; a panel is the standard container into which one inserts
components (including painted text and shapes). Panels are themselves inserted into
a container called a window, which is a “top-level” container, that is, a container that
can be displayed by itself.
A frame is a window with a title and menus; frames are “permanent” in that they
are created when an application starts and are meant to exist as long as the application
10.3. THE AWT/SWING CLASS HIERARCHY
569
Figure 10.2: frame with basic components
executes. A dialog is a “temporary” window that can appear and disappear while the
program is executing.
Examples of components that one finds within panels and frames are
• a label, which is text that the user can read but cannot alter
• a text component, into which a user can type text
• a button, which can be pushed, triggering an action event
• a list of items, whose items can be chosen (“selected”)
Figure 2 shows a frame that contains a label, a text component, a list, and three
buttons. Although it is not readily apparent, the text component and list are embedded in a panel, which was inserted in the middle of the frame, and the label and
button live in their own panels in the top and bottom regions of the frame.
Figure 3 displays an example dialog, which might have appeared because the user
entered text and pushed a button in the frame behind. The dialog contains a label
and a button; when the button is pushed, the dialog disappears from the screen.
A frame can hold a menu bar, which holds one or more menus. Each menu contains
menu items, which can be selected, triggering an action event. Figure 4 shows a frame
with a menu bar that holds two menus, where the second menu is open and displays
four menu items, one of which is itself a menu:
When a container holds multiple components, the components can be formatted
with the assistance of a layout manager. Three basic forms of layout are
• flow layout: the components are arranged in a linear order, like the words in a
line of text
570
Figure 10.3: sample dialog
Figure 10.4: frame with menus
10.4. SIMPLE WINDOWS: LABELS AND BUTTONS
571
• border layout: components are explicitly assigned to the “north,” “south,”
“east,” “west,” or “center” regions of the container
• grid layout: components are arranged as equally-sized items in rows and columns,
like the entries of a matrix or grid
In Figure 2, the frame is organized with a 3-by-1 grid layout, where the second element
of the grid is a panel containing a text component and list. The panel uses flow layout
to arrange its two components. We will see an example of border layout momentarily.
The AWT/Swing framework contains dozens of classes, each of which owns dozens
of methods. It is overwhelming to learn the entire framework, so we will master a
manageably sized subset of it. Figure 5 displays the parts of AWT/Swing we will use;
aside from reading the names of the various classes, do not study the Figure at this
point—use it as a reference as you progress through the chapter.
Since the AWT/Framework was developed in several stages by the Java designers,
the elegant hierarchy of component-container-panel-window-frame is obscured in the
final product in Figure 5.
The classes in Figure 5 require other classes that define points, type fonts, images,
layouts, and events. Figure 6 lists these extra classes. Again, study the Figure as you
progress through the chapter.
As all frameworks must do, AWT/Swing uses a variety of interfaces for connecting
view components to controllers. (Review Chapter 9 for uses of Java interfaces; an
introductory explanation appears later in this chapter, also.) The interfaces within
AWT/Swing that we employ appear in Figure 7.
Figures 5 through 7 list the constructs we use in this chapter, and you should use
the Figures as a road map through the examples that follow.
Exercises
1. List all the methods owned by a JFrame. (Remember that the JFrame inherits
the methods of its superclasses.) Compare your answer to Table 21, Chapter 4.
2. Several of the classes in Figure 5 have methods named addSOMETHINGListener.
The classes that possess such methods are capable of generating SOMETHING
events. List the classes that generate events.
10.4
Simple Windows: Labels and Buttons
The standard graphical user interface for a program is a frame, generated from class
JFrame. We made extensive use of JFrame in previous chapters, using it to hold and
display panels. Now, we learn how to insert components like labels and buttons into
a frame.
572
Figure 10.5: partial AWT/Swing hierarchy
Object
|
+-Component [abstract]: setSize(int,int), setVisible(boolean), setFont(Font),
|
isShowing():boolean, getLocationOnScreen():Point, setLocation(Point),
|
paint(Graphics), repaint(), setForeground(Color),
|
setBackground(Color), getGraphics()
|
+-Container: add(Component), add(Component,Object), setLayout(LayoutManager)
|
+-Window: pack(), dispose(), addWindowListener(WindowListener)
| |
| +-JFrame: JFrame(), setTitle(String), setJMenuBar(JMenuBar)
|
getContentPane():Container
|
+-JApplet: JApplet(), init(), getParameter(String):String,
|
getContentPane():Container, setJMenuBar(JMenuBar)
|
+-JComponent [abstract]: paintComponent(Graphics)
|
+-AbstractButton [abstract]: addActionListener(ActionListener),
| |
setEnabled(Boolean), getText():String, setText(String),
| |
setSelected(Boolean), isEnabled():boolean,
| |
isSelected():boolean, doClick()
| |
| +-JButton: JButton(String), JButton(Icon), JButton(String, Icon),
| |
setIcon(Icon)
| |
| +-JMenuItem: JMenuItem(String)
|
|
|
+-JMenu: JMenu(String), add(Component), addSeparator()
|
+-JLabel: JLabel(String), getText():String, setText(String)
|
+-JList: JList(Object[]),getSelectedIndex():int,setSelectedIndex(int),
| getSelectedIndices():int[], setSelectedIndices(int[]),
| setSelectionMode(int), clearSelection(),
| addListSelectionListener(ListSelectionListener)
|
+-JMenuBar: JMenuBar(), add(JMenu)
|
+-JOptionPane: showMessageDialog(Component,Object),
|
showConfirmDialog(Component,Object):int,
|
showInputDialog(Component,Object):String
...
10.4. SIMPLE WINDOWS: LABELS AND BUTTONS
573
Figure 10.5: partial AWT/Swing hierarchy (concl.)
|
+-JPanel: Panel(), Panel(LayoutManager)
|
+-JScrollPane: JScrollPane(Component)
|
+-JTextComponent [abstract]: cut(), paste(), copy(), getText():String,
|
setText(String), getSelectionStart():int,
|
getSelectionEnd():int, getSelectedText():String,
|
replaceSelection(String), getCaretPosition():int,
|
setCaretPosition(int), moveCaretPosition(int),
|
isEditable():boolean, setEditable(boolean)
|
+-JTextField: JTextField(String,int),
|
addActionListener(ActionListener)
+-JTextArea: JTextArea(String,int,int), insert(String,int)
replaceRange(String,int,int), setLineWrap(boolean)
The first example is a frame that holds the label, Press This, and a button named
OK. These two components must be inserted into the frame, and we must indicate the
form of layout. We use flow layout, which places the two components next to each
other. Here is the result,
which is produced by the program in Figure 8.
Let’s examine the statements in the constructor method one by one:
• JLabel label = new JLabel("Press This:") constructs a label object, label
that displays the string, Press This:.
• Similarly, JButton button = new JButton("OK") constructs a button.
• Container c = getContentPane() asks the frame to extract (the address of) its
content pane and assign it to c. Many examples in earlier chapters invoked
getContentPane, and now it is time to understand the activity.
For the moment, pretend that a JFrame object is in fact a real, physical, glass
window that is assembled from several layers of glass. The frame’s topmost
574
Figure 10.6: points, fonts, images, layouts, and events
Object
|
+-BorderLayout: BorderLayout(), BorderLayout.NORTH, BorderLayout.SOUTH,
|
BorderLayout.EAST, BorderLayout.WEST, BorderLayout.CENTER
|
+-FlowLayout: FlowLayout(), FlowLayout(int), FlowLayout.LEFT,
|
FlowLayout.RIGHT, FlowLayout.CENTER
|
+-GridLayout: GridLayout(int,int)
|
+-Font: Font(String,int,int), Font.PLAIN, Font.BOLD, Font.ITALIC
|
+-Point: Point(int,int), translate(int,int)
|
+-EventObject
| |
| +-AWTEvent [abstract]
| | |
| | +-ActionEvent: getActionCommand():String, getSource():Object
| | |
| | +-WindowEvent
| |
| +-ListSelectionEvent
|
+-WindowAdapter [implements WindowListener]
|
+-Image
|
+-ImageIcon [implements Icon]: ImageIcon(String), getImage():Image
|
+-Observable: addObserver(Observer), setChanged(), notifyObservers(),
notifyObservers(Object)
10.4. SIMPLE WINDOWS: LABELS AND BUTTONS
Figure 10.7: interfaces
public interface ActionListener
{ public void actionPerformed(ActionEvent e); }
public interface WindowListener
{ public void windowActivated(WindowEvent e);
public void windowClosed(WindowEvent e);
public void windowClosing(WindowEvent e);
public void windowDeactivated(WindowEvent e);
public void windowDeiconified(WindowEvent e);
public void windowIconified(WindowEvent e);
public void windowOpened(WindowEvent e);
}
public interface ListSelectionListener
{ public void valueChanged(ListSelectionEvent e); }
public interface Observer
{ public void update(Observable ob, Object arg); }
public interface Icon
{ public int getIconHeight();
public int getIconWidth();
public void paintIcon(Component c, Graphics g, int x, int y);
}
575
576
Figure 10.8: frame with label and button
import java.awt.*;
import javax.swing.*;
/** Frame1 is a frame with a label and a button */
public class Frame1 extends JFrame
{ /** Constructor Frame1 creates a frame with a label and button */
public Frame1()
{ JLabel label = new JLabel("Press This:");
JButton button = new JButton("OK");
Container c = getContentPane();
c.setLayout(new FlowLayout());
c.add(label);
c.add(button);
setTitle("Example1");
setSize(200, 60);
setVisible(true);
}
public static void main(String[] args)
{ new Frame1(); }
}
layer is called the glass pane, and it is possible (but not recommended) to paint
on it. When we insert components like panels, labels, and buttons into the
frame, the components are inserted into an inner layer, called the content pane.
(There are additional layers of “glass,” but we will not deal with them in this
chapter.)
The statement, Container c = getContentPane(), fetches the content pane. We
could also write the statement as
Container c = this.getContentPane();
but from this point onwards, we take advantage of Java’s convention: a message
that an object sends to itself need not be prefixed by this.
• The message, c.setLayout(new FlowLayout()) tells the content pane to arrange
the components in a flow layout, that is, where the components are arranged in
a line.
• c.add(label) uses the content pane’s add method to add label; c.add(button)
adds button also.
10.4. SIMPLE WINDOWS: LABELS AND BUTTONS
577
• Finally, the setTitle, setSize, and setVisible messages are the standard ones.
Again, we omit the this pronoun as the receiver.
The names, label and button, are not crucial to the example, and we might revise
the above statements to read,
Container c = getContentPane();
c.setLayout(new FlowLayout());
c.add(new JLabel("Press This:"));
c.add(new JButton("OK"));
Indeed, even the name, c, can be discarded, e.g., getContentPane().setLayout(new
FlowLayout()), getContentPane().add(new JLabel("Press This:")), etc.
The statement, setSize(200, 60), which sets the frame’s size, can be replaced
by pack(), which resizes the frame at a minimal size to display the components it
contains. (But take care when using pack(), because it occasionally creates a frame
with too small of a size for its components.)
As is the custom, we place a tiny main method at the end of the class so that we
can easily test the frame.
As noted earlier, it is possible to paint on the surface of a frame by using a method
named paint. if we add this method
public void paint(Graphics g)
{ g.setColor(Color.red);
g.fillRect(0, 0, 100, 100);
}
to Figure 8, we get this result,
because we have painted on the frame’s topmost, “glass,” pane, covering the components we inserted into the content pane.
Although we should not paint directly onto a frame, we can set the background
and foreground colors of the content pane and the components we insert into it. For
example, to make the content pane’s background yellow, we state,
Container c = getContentPane();
c.setBackground(Color.yellow);
and we can make button a matching yellow by saying,
button.setBackground(Color.yellow);
578
We can color red the foreground text on both label and button by saying
label.setForeground(Color.red);
button.setForeground(Color.red);
This works because every component has a “background” and “foreground” that can
be colored.
Buttons usually have text displayed on their faces, but it is possible to display
images, called icons, as well. If you have an image formatted as a gif or jpg file, you
can place the image on the face of the button as follows:
ImageIcon i = new ImageIcon("mypicture.gif");
JButton b = new JButton(i);
which displays
That is, the image file, mypicture.gif, is used to construct an ImageIcon object,
which itself is used to construct the button. A button can display both an image and
text:
JButton b = new JButton("My Picture:", new ImageIcon("mypicture.gif"));
Once the frame and its button appear on the display, you can move the mouse
over the frame’s button and push it to your heart’s content. But the program takes
no action when the button is pushed, because the program has only a view object
but no model and no controller. We write these next.
Exercises
1. Create a frame with three buttons whose labels state, Zero, One, and Two. Create
the frame with different sizes, e.g., setSize(300, 150), setSize(50, 300), and
pack().
2. Color the frame’s background white, color each button’s background a different
color, and color each button’s text (foreground) black.
3. Replace the three buttons with one label that states, Zero One Two. Color the
background and foreground of the label.
10.5. HANDLING AN EVENT
10.5
579
Handling an Event
Every component—button, label, panel, etc.—of a window is an object in its own
right. When an event like a button push or a mouse movement occurs, it occurs
within an object within the window. The object in which the event occurs is the
event source. In AWT/Swing, when an event occurs, the event source automatically
sends a message to an object, called its event listener, to handle the event. The event
listener is a controller—when it receives a message, it sends messages to model object
and view object to compute and display results.
As stated earlier, we focus upon action events—button pushes and menu selections. An action event, like a button push, is handled by an action-listener object; in
Java, an action-listener object must have a method named actionPerformed, and it
is this method that is invoked when an action event occurs.
When we write a class, C, that creates action-listener objects, we use this format:
public class C implements ActionListener
{ ...
public void actionPerformed(ActionEvent e)
{ ... instructions that handle a button-push event ... }
}
That is, the class’s header line asserts implements ActionListener, and the class
contains an actionPerformed method. As we learned in Chapter 9, we use a Java
interface to name the methods required of a class. In AWT/Swing, there is a
prewritten interface, named ActionListener, which is found in the java.awt.event
package. The interface was listed in Figure 7; once again, it looks like this:
/** ActionListener names the method needed by an action listener. */
public interface ActionListener
{ /** actionPerformed handles an action event, say, a button push
* @param e - information about the event */
public void actionPerformed(ActionEvent e);
}
The interface states that an action listener must have a method named actionPerformed.
AWT/Swing requires that a button’s action listener must be an object that is constructed from a class that implements ActionListener. We now study three standard
ways of writing action listeners.
10.5.1
A View as Action Listener
For our first, simplest example of event handling, we alter Frame1 so that each time
the the frame’s button is pushed, its label displays the total number of button pushes.
580
Figure 10.9: model class, Counter
/** Counter holds a counter */
class Counter
{ private int count;
// the count
/** Constructor Counter initializes the counter
* @param start - the starting value for the count
public Counter(int start)
{ count = start; }
*/
/** increment updates count. */
public void increment()
{ count = count + 1; }
/** countOf accesses count.
* @return the value of count */
public int countOf()
{ return count; }
}
When created, the GUI appears
and after 3 button pushes, the view is
To create this behavior, we use a model-view-controller architecture, where class
Counter, from Figure 9, remembers the quantity of button pushes; it acts as the
model.
Next, we require a controller (action listener) for the OK button, and we must
connect the controller to the button. In this first example, the view and controller
are combined into the same class; this is a naive and inelegant but simple solution—see
Figure 10.
10.5. HANDLING AN EVENT
581
Figure 10.10: combined view/controller for counter example
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/** Frame2a shows a frame with whose label displays the number of times
its button is pushed */
class Frame2a extends JFrame implements ActionListener
{ private Counter count; // address of model object
private JLabel label = new JLabel("count = 0"); // label for the frame
/** Constructor Frame2a creates a frame with a label and button
* @param c - the model object, a counter */
public Frame2a(Counter c)
{ count = c;
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(label);
JButton button = new JButton("OK");
cp.add(button);
button.addActionListener(this); // this object---the view---is connected
// to button as its action listener
setTitle("Frame2a");
setSize(200, 60);
setVisible(true);
}
/** actionPerformed handles an action event---a button push */
public void actionPerformed(ActionEvent e)
{ count.increment();
label.setText("count = " + count.countOf());
}
}
/** Example2a starts the application */
public class Example2a
{ public static void main(String[] args)
{ Counter model = new Counter(0);
// create the model
Frame2a view = new Frame2a(model); // create the controller and view
}
}
582
Since the view is also the controller, class Frame2a states in its header line that
it implements ActionListener. And indeed, method actionPerformed appears at the
end of the class. (We study the contents of actionPerformed momentarily.) Also, the
statement, import java.awt.event.*, appears at the beginning of the class because
the java.awt.event package contains the ActionListener interface.
Now, how do we connect the button to its action listener, the view object? The
answer appears within the view’s constructor method:
JButton button = new JButton("OK");
cp.add(button);
button.addActionListener(this);
The first two statements create the button and add it to the view, like before. The
third statement connects button to its action listener—every button has a method,
named addActionListener, which is invoked to connect a button to its action listener.
Here, the action-listener object is this very object—the view object, which displays
the button! From this point onwards, every push of button causes the view object’s
actionPerformed method to execute.
Frame2a’s actionPerformed method handles a button push by making the counter
increment and by resetting the text of the label to display the counter’s new value.
The method receives a parameter that contains technical information about the button push; we will not use the parameter at this time.
Here is a slightly detailed explanation of what happens when the program in
Figure 10 is started and its button is pushed. When Example2a is started:
1. The main method creates objects for the model and the view/controller. Frame2a’s
constructor method creates a label and a button. The button is sent an addActionListener
message that tells the button that its action events will be handled by the
Frame2a object.
2. The view appears on the display, and the program awaits events.
When the user clicks on the frame’s OK button:
1. The computer’s operating system detects the event, and tells AWT/Swing
about it. AWT/Swing determines the event source (the button), and creates
an ActionEvent object that holds precise information about the event. The
ActionEvent object is sent as the actual parameter of an actionPerformed message to the event source’s action listener, which is the Frame2a object.
2. The message arrives at actionPerformed in Frame2a. The actionPerformed
method sends an increment message to the counter object and a setText message to the label.
3. When actionPerformed’s execution concludes, the computer automatically refreshes the view on the display; this displays the label’s new value.
10.5. HANDLING AN EVENT
583
4. The program awaits new events.
The explanation should make clear that computation occurs when events trigger execution of event listeners. This is why computation is “event driven.”
10.5.2
A Separate Controller
Our second approach to the the previous example uses a separate class to be the
button’s event listener—its controller. This style is preferred because it makes it
easier to build GUIs with multiple buttons and to reuse views and controllers. The
controller, class CountController, will be studied momentarily. This leaves the view,
class Frame2b, with the sole job of presenting the frame, label, and button on the
display. Figure 11 shows the view after we have extracted the controller from it.
The key change in the constructor method lies at
JButton button = new JButton("OK");
button.addActionListener(new CountController(count, this));
which constructs a new CountController object (see Figure 12), gives it the addresses
of the model object and view object (this object), and attaches the controller to the
button as the button’s action listener.
Because the controller is separate from the view, the former will need a way to
tell the latter when it is time to display a new value for the count. For doing this,
the view class has a method, update, which resets the text of the label with the latest
value of the count.
We have simple coding of the controller, class CountController, in Figure 12.
The controller implements ActionListener and it holds the actionPerformed method
that is invoked when the OK button is pushed. This controller resembles the ones we
saw in previous chapters, because its job is to tell the model object to compute results
and tell the view to display the results.
10.5.3
A Button-Controller
The third solution to the example merges the JButton object with its controller. This
follows the philosophy that, to the user, the button is the controller, and pushing the
button activates its methods. The button-controller we write appears in Figure 13.
CountButton is a “customized” JButton, hence it extends JButton. Its constructor
method starts work by invoking the constructor in JButton—this is what super(my label)
does—and creates the underlying JButton and attaches my label to the button’s face.
But the constructor for CountButton does more: The crucial statement is, addActionListener(this),
which tells the button that its action listener is this very same object. Thus, the
class implements ActionListener.
584
Figure 10.11: view class for counter example
import java.awt.*; import javax.swing.*;
/** Frame2b shows a frame with whose label displays the number of times
its button is pushed */
public class Frame2b extends JFrame
{ private Counter count; // address of model object
private JLabel label = new JLabel("count = 0"); // label for the frame
/** Constructor Frame2b creates a frame with a label and button
* @param c - the model object, a counter */
public Frame2b(Counter c)
{ count = c;
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(label);
JButton button = new JButton("OK");
button.addActionListener(new CountController(count, this)); // see Fig. 12
cp.add(button);
setTitle("Frame2");
setSize(200, 60);
setVisible(true);
}
/** update revises the view */
public void update()
{ label.setText("count = " + count.countOf()); }
}
/** Example2b starts the application */
public class Example2b
{ public static void main(String[] args)
{ Counter model = new Counter(0);
Frame2b view = new Frame2b(model);
}
}
10.5. HANDLING AN EVENT
585
Figure 10.12: controller for counter example
import java.awt.event.*;
/** CountController handles button push events that increment a counter */
public class CountController implements ActionListener
{ private Frame2b view; // the view that must be refreshed
private Counter model; // the counter model
/** CountController constructs the controller
* @param my model - the model object
* @param my view - the view object */
public CountController(Counter my model, Frame2b my view)
{ view = my view;
model = my model;
}
/** actionPerformed handles a button-push event */
public void actionPerformed(ActionEvent evt)
{ model.increment();
view.update();
}
}
The view that uses the button-controller is in Figure 14, and it is the simplest of
the three versions we have studied.
Figure 15 summarizes the architecture that we have assembled for the third variant
of the counter application.
The interface, written in italics, serves as the connection point between the buttoncontroller and the AWT/Swing framework.
We can revise the application in Figure 15 so that it it presents two buttons, an OK
button that changes the count, and an Exit button that terminates the application:
The result holds interest because there are now two controllers, one for each action
event. The controller for terminating the program appears in Figure 16. (Recall that
System.exit(0) terminates an application.)
Next, Figure 17 defines the view, class Frame3, as a quick extension of class
Frame2c from Figure 14. Although an abstract class, like those in Chapter 9, might
be a better solution for organizing the previous and revised views, we use the existing
586
Figure 10.13: button-controller for counter example
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
/** CountButton defines a button-controller */
public class CountButton extends JButton implements ActionListener
{ private Frame2c view;
// the view that holds this controller
private Counter model; // the model that this controller collaborates with
/** Constructor CountButton builds the controller
* @param my label - the label on the button that represents the controller
* @param my model - the model that the controller collaborates with
* @param my view - the view that the controller updates */
public CountButton(String my label, Counter my model, Frame2c my view)
{ super(my label); // attach label to the button in the superclass
view = my view;
model = my model;
addActionListener(this); // attach this very object as the ‘‘listener’’
}
/** actionPerformed handles a push of this button
* @param evt - the event that occurred, namely, the button push */
public void actionPerformed(ActionEvent evt)
{ model.increment();
view.update();
}
}
10.5. HANDLING AN EVENT
Figure 10.14: view for Example2
import java.awt.*;
import javax.swing.*;
/** Frame2c shows a frame with whose label displays the number of times
its button is pushed */
public class Frame2c extends JFrame
{ private Counter count; // address of model object
private JLabel label = new JLabel("count = 0"); // label for the frame
/** Constructor Frame2c creates a frame with a label and button
* @param c - the model object, a counter */
public Frame2c(Counter c)
{ count = c;
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(label);
cp.add(new CountButton("OK", count, this)); // the button-controller
setTitle("Example2");
setSize(200, 60);
setVisible(true);
}
/** update revises the view */
public void update()
{ label.setText("count = " + count.countOf()); }
}
/** Example2c starts the application */
public class Example2c
{ public static void main(String[] args)
{ Counter model = new Counter(0);
Frame2c view = new Frame2c(model);
}
}
587
588
Figure 10.15: architecture of Example2c
JButton
ActionListener
actionPerformed(ActionEvent e)
CountButton
actionPerformed(ActionEvent e)
Frame2c
update()
AWT/Swing classes
that detect events
JFrame
Counter
increment()
countOf()
Figure 10.16: exit controller
import javax.swing.*;
import java.awt.event.*;
/** ExitButton defines a controller that terminates an application */
public class ExitButton extends JButton implements ActionListener
{
/** Constructor ExitButton builds the controller
* @param my label - the label for the controller’s button */
public ExitButton(String my label)
{ super(my label);
addActionListener(this);
}
/** actionPerformed handles a button-push event
* @param evt - the event */
public void actionPerformed(ActionEvent evt)
{ System.exit(0); }
}
10.5. HANDLING AN EVENT
589
Figure 10.17: view for two-button view
import java.awt.*;
import javax.swing.*;
/** Frame3 shows a frame with whose label displays the number of times
its button is pushed */
class Frame3 extends Frame2c
{
/** Constructor Frame3 creates a frame with a label and button
* @param c - the model object, a counter */
public Frame3(Counter c)
{ super(c); // tell superclass to construct most of the frame
Container cp = getContentPane();
cp.add(new ExitButton("Exit")); // add another button-controller
setTitle("Example 3"); // reset the correct title and size:
setSize(250, 60);
setVisible(true);
}
}
/** Example3 starts the application */
public class Example3
{ public static void main(String[] args)
{ Counter model = new Counter(0);
Frame3 view = new Frame3(model);
}
}
approach if only to show that components can be added to a frame’s content pane
incrementally. (See Frame3’s constructor method.)
Finally, users of Windows-style operating systems have the habit of terminating
applications by clicking the “X” button that appears at a window’s upper right corner. The buttons at the upper right corner are monitored through the AWT/Swing
WindowListener interface. (See Figure 7 for the interface.) Since it does not deserve
a long explanation, we merely note that the changes presented in Figure 18 will make
a mouse click on the “X” button terminate a program.
Exercises
1. Create an interface for an application whose model possesses two Counter objects. Create two buttons, each of which increments one of the counters when
pushed. Use labels to display the values of the two counters.
590
Figure 10.18: terminating a program with the X-button
// Define this class:
import java.awt.event.*;
public class ExitController extends WindowAdapter
{ public void windowClosing(WindowEvent e)
{ System.exit(0); }
}
// Within the frame’s constructor method, add this statement:
addWindowListener(new ExitController());
2. As noted in Figure 5, buttons have a setText(String s) method, which changes
the text that appears on a button’s face to s. Revise your solution to the
previous exercise so that each button displays the number of times that it has
been pushed.
3. Create an application whose GUI has an Increment button, a Decrement button,
and label that displays an integer. Each time Increment is pushed, the integer
increases, and each time Decrement is pushed, the integer decreases. (Hint:
Write a new model class.)
4. Create a GUI with a red button, a yellow button, and a blue button. The
frame’s background turns into the color of the button last pushed.
10.6
Richer Layout: Panels and Borders
We can rebuild the above GUI so that its label appears at the top of the frame,
the OK and Exit buttons are positioned along the frame’s bottom, and a drawing of
the current count appears in the center. Figure 19 shows this. To do this, we use
panels. We construct a panel, insert the label in it, and place the panel in the “north”
region of the frame. Next, we construct another panel, paint it, and place it in the
frame’s “center” region. Finally, we insert the two buttons into a third panel and
place the panel in the “south” region. Border layout lets us specify the regions where
components can be inserted; in addition to the region used in the example in Figure
19, there are also “east” and “west” regions. Here is a picture of how the regions are
10.6. RICHER LAYOUT: PANELS AND BORDERS
591
Figure 10.19: frame with two depictions of the count
positioned in a container by the border layout manager:
The north and south regions take precedence in layout, followed by the east and
west regions. Any space left in the middle becomes the center region. The layout is
delicate in the sense that an undersized window may cause components in a region
to be partially hidden.
Figure 20 presents the view for the GUI in Figure 19.
In Frame4’s constructor, the content pane is told to use border layout:
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
Next, components are added to the regions of the content pane, e.g.,
cp.add(drawing, BorderLayout.CENTER);
In addition to the regions used in the Figure, one can also use BorderLayout.EAST
and BorderLayout.WEST.
Panels are simply created and inserted into the frame, e.g.,
592
Figure 10.20: view class with border layout and panels
import java.awt.*;
import javax.swing.*;
/** Frame4 is a frame with a label and a button */
public class Frame4 extends JFrame
{ private Counter count; // address of model object
private JLabel lab = new JLabel("count = 0"); // label for the frame
private JPanel drawing; // a drawing for the center of the frame
/** Constructor Frame4 creates a frame with label, drawing, and 2 buttons
* @param c - the model object, a counter
* @param panel - a panel that displays a drawing */
public Frame4(Counter c, JPanel panel)
{ count = c;
drawing = panel;
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
JPanel p1 = new JPanel(new FlowLayout());
p1.add(lab);
cp.add(p1, BorderLayout.NORTH);
cp.add(drawing, BorderLayout.CENTER);
JPanel p2 = new JPanel(new FlowLayout());
p2.add(new CountButton("Count", count, this));
p2.add(new ExitButton("Quit"));
cp.add(p2, BorderLayout.SOUTH);
setTitle("Example4");
setSize(200,150);
setVisible(true);
}
/** update revises the view */
public void update()
{ lab.setText("count = " + count.countOf());
drawing.repaint();
}
}
10.6. RICHER LAYOUT: PANELS AND BORDERS
593
Figure 10.20: view class with border layout and panels (concl.)
import java.awt.*;
import javax.swing.*;
/** Drawing creates a panel that displays a small drawing */
public class Drawing extends JPanel
{ private Counter count; // the model object
public Drawing(Counter model)
{ count = model;
setSize(200, 80);
}
public void paintComponent(Graphics g)
{ g.setColor(Color.white);
g.fillRect(0, 0, 150, 80);
g.setColor(Color.red);
for ( int i = 0; i != count.countOf(); i = i+1 )
{ g.fillOval(i * 25, 0, 20, 20); }
}
}
/** Example4 starts the application */
public class Example4
{ public static void main(String[] args)
{ Counter model = new Counter(0);
Drawing drawing = new Drawing(model);
Frame4 view = new Frame4(model, drawing);
}
}
JPanel p2 = new JPanel(new FlowLayout());
p2.add(new CountButton("Count", count, this));
p2.add(new ExitButton("Quit"));
cp.add(p2, BorderLayout.SOUTH);
The first statement simultaneously creates a panel and sets its layout. Components
are added to panels just like they are to frames, and panels are added to frames just
like other components are added.
Class Drawing extends JPanel. It repaints the drawing when told.
An image file can be displayed within a panel by using the panel’s graphics pen.
Insert these statements into the panel’s paintComponent method:
ImageIcon i = new ImageIcon("mypicture.gif");
Image j = i.getImage();
594
g.drawImage(j, 20, 20, this);
The first statement converts the image file into an ImageIcon, as we did for creating
labels for buttons. The second statement extracts an Image object from the icon,
and the third employs the panel’s graphics pen to draw the image with its upper left
corner at position 20, 20.
Exercises
1. Test Example4 by pushing its Count button 10 times. What happens to the
drawing? Resize the frame to a larger size; what do you observe? Propose a
solution to this problem.
2. Experiment with border layout: Rewrite Frame4 in Figure 20 so that the three
panels are inserted into the east, center, and west regions, respectively; into the
center, north, and east regions, respectively. Next, delete panel p1 from the
constructor method and insert the label directly into the north region.
3. Write a application that lets you grow and shrink an egg: The GUI has two
buttons, Grow and Shrink. An egg is displayed underneath the buttons; when
you push Grow, the egg gets 10% larger; when you push Shrink, the egg becomes
10% smaller. Use method paintAnEgg from Figure 2, Chapter 5, to paint the
egg.
10.6.1
An Animation in a Panel
Animations, like the moving ball example in Chapter 7, can be easily reformatted
to appear within a panel of a GUI. The GUI might also display buttons that, when
pressed, alter the animation’s progress—video games are built this way.
Here is a simple example. Perhaps we have an animation that displays a ball that
10.6. RICHER LAYOUT: PANELS AND BORDERS
595
Figure 10.21: architecture of throbbing ball animation
ColorButton
ThrobController
ThrobPanel
ThrobFrame
ThrobbingBall
“throbs” between large and small:
When the button is pressed, the ball in the animation changes color; in this simple
way, the user “plays” the animation.
The animation is displayed in a panel that is embedded into a frame. Like the animation in Chapter 7, this animation has its own controller, and there is an additional
button-controller for changing the color of the ball. Figure 21 displays the architecture. The ball is modelled by class ThrobbingBall; the view consists of ThrobPanel,
which paints the ball on a panel, and ThrobFrame, which displays the panel and the
color-change button on a frame. Figure 22 displays these classes.
The application’s controllers hold the most interest: ThrobController contains
a run method whose loop resizes the throbbing ball and redraws it on the panel;
ColorButton changes the ball’s color each time its button is pressed. The controllers
are connected to the model and view by means of the start-up class, StartThrob. All
three classes appear in Figure 23.
Because the ThrobController’s run method is a nonterminating loop, it is crucial
that this method is invoked as the last statement in the animation. (The section,
596
Figure 10.22: model and view classes for animation
/** ThrobbingBall models a ball that changes size from large to small */
public class ThrobbingBall
{ private boolean is it currently large; // the ball’s state---large or small
public ThrobbingBall() { is it currently large = true; }
/** isLarge returns the current state of the ball */
public boolean isLarge() { return is it currently large; }
/** throb makes the ball change state between large and small */
public void throb() { is it currently large = !is it currently large; }
}
import java.awt.*;
import javax.swing.*;
/** ThrobPanel draws a throbbing ball */
public class ThrobPanel extends JPanel
{ private int panel size;
// size of this panel
private int location;
// where ball will be painted on the panel
private int ball size;
// the size of a ‘‘large’’ ball
private Color c = Color.red; // the ball’s color
private ThrobbingBall ball;
// the ball object
public ThrobPanel(int size, ThrobbingBall b)
{ panel size = size;
location = panel size / 2;
ball size = panel size / 3;
ball = b;
setSize(panel size, panel size);
}
/** getColor returns the current color of the ball */
public Color getColor() { return c; }
/** setColor resets the color of the ball to new color */
public void setColor(Color new color) { c = new color; }
...
10.6. RICHER LAYOUT: PANELS AND BORDERS
Figure 10.22: model and view classes for animation (concl.)
/** paintComponent paints the ball */
public void paintComponent(Graphics g)
{ g.setColor(Color.white);
g.fillRect(0, 0, panel size, panel size);
g.setColor(c);
if ( ball.isLarge() )
{ g.fillOval(location, location, ball size, ball size); }
else { g.fillOval(location, location, ball size / 2, ball size / 2); }
}
}
import java.awt.*;
import javax.swing.*;
/** ThrobFrame displays the throbbing-ball panel and color-change button */
public class ThrobFrame extends JFrame
{ /** Constructor ThrobFrame builds the frame
* @param size - the frame’s width
* @param p - the panel that displays the ball
* @param b - the color-change button */
public ThrobFrame(int size, ThrobPanel p, ColorButton b)
{ Container cp = getContentPane();
cp.setLayout(new BorderLayout());
cp.add(p, BorderLayout.CENTER);
cp.add(b, BorderLayout.SOUTH);
setTitle("Throb");
setSize(size, size + 40);
setVisible(true);
}
}
597
598
Figure 10.23: Controllers for animation
/** ThrobController runs the throbbing-ball animation */
public class ThrobController
{ private ThrobPanel writer; // the output-view panel
private ThrobbingBall ball; // the ball model object
private int time; // how long animation is delayed before redrawn
/** ThrobController initializes the controller
* @param w - the panel that is controlled
* @param b - the ball that is controlled
* @param delay time - the amount of time between redrawing the animation */
public ThrobController(ThrobPanel w, ThrobbingBall b, int delay time)
{ writer = w;
ball = b;
time = delay time;
}
/** run runs the animation
public void run()
{ while ( true )
{ ball.throb();
writer.repaint();
delay();
}
}
forever */
// redisplay ball
/** delay pauses execution for time
private void delay()
{ try { Thread.sleep(time); }
catch (InterruptedException e) { }
}
}
milliseconds */
10.6. RICHER LAYOUT: PANELS AND BORDERS
599
Figure 10.23: Controllers for animation (concl.)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/** ColorButton controls the color of the ball */
public class ColorButton extends JButton implements ActionListener
{ private ThrobPanel view; // the view object where shapes are drawn
public ColorButton(ThrobPanel f)
{ super("OK");
view = f;
addActionListener(this);
}
/** actionPerformed handles a click */
public void actionPerformed(ActionEvent e)
{ Color c = view.getColor();
if ( c == Color.red )
{ view.setColor(Color.blue); }
else { view.setColor(Color.red); }
}
}
/** StartThrob assembles the objects of the animation */
public class StartThrob
{ public static void main(String[] a)
// size of displayed frame
{ int frame size = 180;
// speed of animation (smaller is faster)
int pause time = 200;
ThrobbingBall b = new ThrobbingBall();
ThrobPanel p = new ThrobPanel(frame size, b);
ThrobFrame f = new ThrobFrame(frame size, p, new ColorButton(p));
new ThrobController(p, b, pause time).run();
// important: do this last!
}
}
600
“Threads of Execution,” at the end of this chapter explains why.) It is also crucial
that the loop within run has a delay, because it is during the period of delay that
the computer detects action events and executes action listeners.
Exercises
1. Add a “Pause” button to the throbbing-ball animation that, when pushed,
causes the ball to stop throbbing until the button is pushed again. (Hint: add
another boolean field variable to ThrobbingBall.)
2. Swap the last two statements of main in StartThrob. Explain what the animation no longer operates.
3. Embed the moving-ball animation of Figure 7, Chapter 7 into a panel; insert
the panel into a frame. Next, add two buttons, “Faster” and “Slower,” which
increase and decrease, respectively, the velocity at which the ball travels in the
animation. (You will have to write additional methods for class MovingBall in
Figure 8, Chapter 7; the methods will adjust the ball’s x- and y-velocities.)
10.7
Grid Layout
When you have a collection of equally-sized components to be arranged as a table or
grid, use grid layout. As an example, consider the slide puzzle program from Figure
11, Chapter 8, where its output view, class PuzzleWriter, is replaced by a GUI
where buttons portray the pieces of the puzzle—a click on a button/piece moves the
10.7. GRID LAYOUT
601
puzzle piece. The view might look like this:
The frame is laid out as a 4-by-4 grid, where each cell of the grid holds a buttoncontroller object created from class PuzzleButton (which will be studied momentarily). Figure 24 shows class PuzzleFrame, the puzzle’s GUI.
A grid layout of m rows by n columns is created by new GridLayout(m, n). Adding
components to a grid layout is done with the add method, just like with flow layout.
In the constructor method in the Figure, the nested for-loop creates multiple distinct
PuzzleButton objects and inserts them into a grid layout; the grid is filled row by
row. As explained in the next paragraph, it is helpful to retain the addresses of the
button objects in an array, named button, but such an array is not itself required
when using grid layout.
Each button displays a numerical label on its face, stating the numbered piece it
represents. A user “moves” a slide piece by clicking on it. Unfortunately, the buttons
themselves do not move—the labels on the buttons move, instead. When a button
is clicked, its event handler sends a message to method update, which consults array
button and uses the setBackground and setText methods to repaint the faces of the
buttons.
Figure 25 displays the controller class, PuzzleButton. When pushed, the button’s
actionPerformed method uses getText() to ask itself the number attached to its face;
it then tries to move that number in the model. If the move is successful, the view is
told to update.
The controller in Figure 25 should be contrasted with the one in Figure 12, Chapter
8. The Chapter 8 controller used one central loop to read integers and make moves
602
Figure 10.24: grid layout for slide puzzle game
import java.awt.*; import javax.swing.*;
/** PuzzleFrame shows a slide puzzle */
public class PuzzleFrame extends JFrame
{ private SlidePuzzleBoard board;
// the model; see Fig. 10, Ch. 8
private int size;
// the board’s size
private int button size = 60;
// width/height of each button
private PuzzleButton[][] button; // the buttons on the face of the view
/** Constructor PuzzleFrame builds the view
* @param board size - the width and depth of the puzzle
* @param b - the model, a slide puzzle board */
public PuzzleFrame(int board size, SlidePuzzleBoard b)
{ size = board size;
board = b;
button = new PuzzleButton[size][size];
Container cp = getContentPane();
cp.setLayout(new GridLayout(size, size));
// create the button-controllers and insert them into the layout:
for ( int i = 0; i != size; i = i+1 )
{ for ( int j = 0; j != size; j = j+1 )
{ button[i][j] = new PuzzleButton(board, this);
cp.add(button[i][j]);
}
}
update(); // initialize the pieces with their numbers
addWindowListener(new ExitController()); // activates X-button; see Fig. 15
setTitle("PuzzleFrame");
setSize(size * button size + 10, size * button size + 20);
setVisible(true);
}
/** update consults the model and repaints each button */
public void update()
{ PuzzlePiece[][] r = board.contents(); // get contents of the puzzle
for ( int i = 0; i != size; i = i+1 ) // redraw the faces of the buttons
{ for ( int j = 0; j != size; j = j+1 )
{ if ( r[i][j] != null )
{ button[i][j].setBackground(Color.white);
button[i][j].setText("" + r[i][j].valueOf()); }
else { button[i][j].setBackground(Color.black);
button[i][j].setText( "" );
}
}
}
}
}
10.7. GRID LAYOUT
603
Figure 10.24: grid layout for slide puzzle game (concl.)
/** Puzzle creates and displays the slide puzzle */
public class Puzzle
{ public static void main(String[] args)
{ int size = 4; // a 4 x 4 slide puzzle
SlidePuzzleBoard board = new SlidePuzzleBoard(size); // see Fig. 10, Ch. 8
PuzzleFrame frame = new PuzzleFrame(size, board);
}
}
Figure 10.25: button-controller for slide puzzle
import javax.swing.*;
import java.awt.event.*;
/** PuzzleButton implements a button controller for a puzzle game */
public class PuzzleButton extends JButton implements ActionListener
{ private SlidePuzzleBoard puzzle; // address of the SlidePuzzle model
private PuzzleFrame view; // address of Frame that displays this button
/** Constructor PuzzleButton builds the button
* @param my puzzle - the address of the puzzle model object
* @param my view - the address of the puzzle’s view */
public PuzzleButton(SlidePuzzleBoard my puzzle, PuzzleFrame my view)
{ super(""); // set label to nothing, but this will be repainted by the view
puzzle = my puzzle;
view = my view;
addActionListener(this);
}
/** actionPerformed processes a move of the slide puzzle */
public void actionPerformed(ActionEvent evt)
{ String s = getText(); // get the number on the face of this button
if ( !s.equals("") )
// it’s not the blank space, is it?
{ boolean ok = puzzle.move(new Integer(s).intValue()); // try to move
if ( ok ) { view.update(); }
}
}
}
604
forever. But the just class just seen is used to construct multiple controllers, each of
which is programmed to make just one move. There is no loop to control the moves.
Instead, the application is controlled by the user, who can push buttons forever.
Whenever the user tires, the application patiently waits for more events.
Exercises
1. Experiment with grid layout: Rewrite Frame4 in Figure 20 so that the three components are added into a frame with new GridLayout(3,1); with new GridLayout(1,3);
with new GridLayout(2,2).
2. Add to the GUI in Figure 24 a label that displays the count of the number of
pieces that have been moved since the slide-puzzle was started. (Hint: Use the
counter from Figure 9.)
3. Create a GUI that looks like a calculator. The GUI displays 9 buttons, arranged
in a 3-by-3 grid, and a numerical display (label) attached to the top.
10.8
Scrolling Lists
A scrolling list (or “list”, for short) can be used when a user interface must display
many items of information. The scrolling list has the added advantage that a user can
click on one or more of its items, thus highlighting or selecting them. (Selecting a list
item generates an event—not an action event, but a list selection event. The default
event listener for a list selection event highlights the selected item.) At a subsequent
button push, the list’s items can be examined and altered.
Here is a small example that uses a scrolling list. The list displays the values of
eight counters. When a user clicks on a list item, it is highlighted. Here, the third
item is selected:
10.8. SCROLLING LISTS
605
When the user pushes the Go button, an action event is triggered, and the associated
action listener increments the counter associated with the selected item. For example,
if the Go button was clicked on the above interface, we obtain this new view:
In this manner, a scrolling list can present choices, like buttons do, and give useful
output, like labels do.
A scrolling list is built in several steps: You must create a “model” of the list,
you must insert the model into a JList object, and you must insert the JList into a
JScrollPane object to get a scroll bar. These steps might look like this:
String[] list_labels = new String[how_many];
JList items = new JList(list_labels);
JScrollPane sp = new JScrollPane(items);
// the list’s ‘‘model’’
// embed model into the list
// embed list into a scroll pane
Now, sp can be added into a content pane, e.g.,
Container cp = getContentPane();
...
cp.add(sp);
Whatever strings that are assigned to the elements of list labels will be displayed
as the items of the list, e.g.,
for ( int i = 0; i != list_labels.length; i = i+1 )
{ list_labels[i] = "Counter " + i + " has 0"; }
At the beginning of the chapter, we noted that each AWT/Swing component is
built as a little MVC-architecture of its own. When we create a JList object, we must
supply its model part, which must be an array of objects, e.g., strings like list labels
just seen. If the objects are not strings, then we must ensure that the objects have
606
Figure 10.26: model and view for scrolling list of counters
/** ListExample displays an array of counters as a scrolling list */
public class ListExample
{ public static void main(String[] a)
{ int how many counters = 8;
Counter2[] counters = new Counter2[how many counters]; // the model
for ( int i = 0; i != how many counters; i = i+1 )
{ counters[i] = new Counter2(0, i); }
// see below
new ListFrame(counters);
// the view
}
}
/** Counter2 is a Counter that states its identity with a
public class Counter2 extends Counter
{ private int my index;
toString
method */
public Counter2(int start, int index)
{ super(start);
my index = index;
}
public String toString()
{ return "Counter " + my index + " has " + countOf(); }
}
a toString method, which JList’s internal view uses to display the objects as list
items.
Here is how we build the example program displayed above:
• We extend class Counter in Figure 9 to class Counter2 by writing a toString
method for it.
• We use as the application’s model, a Counter2[] object, that is, an array of
counters.
• We create a view that contains a JList whose internal model is exactly the
array of counters.
• We add a Go button that, when pushed, asks the JList which Counter2 item
was selected and then tells that item to increment itself.
Figure 26 shows the model and view classes that generates the example, and Figure
27 shows the controller-button that updates the model.
10.8. SCROLLING LISTS
Figure 10.26: model and view for scrolling list of counters (concl.)
import java.awt.*;
import javax.swing.*;
/** ListFrame shows a scrolling list */
public class ListFrame extends JFrame
{ private Counter2[] counters; // the address of the model object
private JList items;
// the list that displays the model’s elements
/** Constructor ListFrame generates the frame with the list
* @param model - the model object that will be displayed as a list */
public ListFrame(Counter2[] model)
{ counters = model;
items = new JList(counters); // embed the model into a JList
JScrollPane sp = new JScrollPane(items); // attach a scroll bar
Container cp = getContentPane();
cp.setLayout(new GridLayout(2,1));
cp.add(sp);
// add the scrolling list to the pane
JPanel p = new JPanel(new GridLayout(2,1));
p.add(new ListButton("Go", counters, this)); // see Figure 27
p.add(new ExitButton("Quit"));
// see Figure 16
cp.add(p);
update(); // initialize the view of the list
setTitle("ListExample");
setSize(200,200);
setVisible(true);
}
/** getSelection returns which list item is selected by the user
* @return the element’s index, or -1 is no item is selected */
public int getSelection()
{ return items.getSelectedIndex(); }
/** update refreshes the appearance of the list */
public void update()
{ items.clearSelection(); } // deselect the selected item in the list
}
607
608
Figure 10.27: button-controller for scrolling list example
import java.awt.event.*;
import javax.swing.*;
/** ListButton implements a button that alters a scrolling list */
public class ListButton extends JButton implements ActionListener
{ private Counter2[] counters; // address of model object
private ListFrame view;
// address of view object
/** Constructor ListButton constructs the controller */
public ListButton(String label, Counter2[] c, ListFrame v)
{ super(label);
counters = c;
view = v;
addActionListener(this);
}
/** actionPerformed handles a button-push event */
public void actionPerformed(ActionEvent evt)
{ int choice = view.getSelection(); // get selected index number
if ( choice != -1 ) // Note: -1 means no item was selected.
{ counters[choice].increment();
view.update();
}
}
}
Class ListFrame builds its scrolling list, items, from the array of Counter2[] objects that its constructor method receives. When the frame is made visible, the view
part of items automatically sends toString messages to each of its array elements
and it displays the strings that are returned in the list on the display.
ListFrame is equipped with two small but important public methods, getSelection
and update. When invoked, the first asks the list for which item, if any, is selected at
this time by the user. The second method tells the list to deselect the item so that
no list item is selected.
Perhaps the user pushes the Go button; the actionPerformed method for ListButton
sends a getSelection message to the view and uses the reply to increment the appropriate counter in the model. Then an update message deselects the selected item.
Once actionPerformed finishes, the scrolling list is redrawn on the display, meaning
that the toString methods of the counters report their new values.
It is possible to attach event listeners directly to the scrolling list, so that each
time the user selects an item, an event is generated and an event handler is invoked. A controller that handles such list selection events must implement the
10.8. SCROLLING LISTS
609
ListSelectionListener interface in Figure 7. For example, we can remove the Go
button from the frame in Figure 26, delete Figure 27 altogether, and replace the
latter with this controller:
import javax.swing.*;
import javax.swing.event.*;
/** ListController builds controllers for lists of counters */
public class ListController implements ListSelectionListener
{ private Counter2[] counters; // address of model object
private ListFrame view;
// address of view object
/** Constructor ListController constructs the controller */
public ListController(Counter2[] c, ListFrame v)
{ counters = c;
view = v;
}
/** valueChanged responds to a list item selection */
public void valueChanged(ListSelectionEvent e)
{ int choice = view.getSelection(); // get selected index number
if ( choice != -1 )
{ counters[choice].increment();
view.update();
}
}
}
Then, within the constructor method of class ListFrame, we attach the controller as
a listener to the JList items:
items.addListSelectionListener(new ListController(counters, this));
This makes the list’s items behave as if they are buttons, all connected to the same
controller.
Finally, it is possible to tell a scrolling list to allow simultaneous selection of
multiple items;
items.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
tells list items to allow multiple selections. Of course, when an event listener examines
the list, it should ask for all the selected items; this is done by
items.getSelectedIndices()
which returns as its answer an array of integers.
610
Exercises
1. Create a GUI that displays a scrolling list whose items are the first 10 letters
of the alphabet. (Hint: use as the list’s model this array: String [] letters =
{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}.)
2. Augment the GUI from the previous Exercise with a label and a button. When
the user selects a list item and pushes the button, the letter on the selected list
item is displayed on the label.
3. Augment the GUI from the previous Exercise so that when the user pushes
the button, the text in the selected item is “doubled,” e.g., if a is selected, it
becomes aa.
4. Create a list that contains the strings, Red, Yellow, and Blue. Insert the list
and a button into a GUI, and program the button so that when the user pushes
it, the GUI’s background is colored with the color selected in the list.
10.9
Text Fields
AWT/Swing provides a JTextField component, which lets a user type one line of
text into a text field. An example text field appears in Figure 2 at the start of this
Chapter. Typing text into a text field generates events (but not action events) that
are processed by the default event listener for a text field; the event listener displays
the typed text in the text field, and it will accommodate backspacing and cursor
movement. One problem that arises with text fields is that a program’s user might
type something inappropriate into the text field, so the program must be prepared to
issue error messages in response.
Creating and adding a text field to a frame is easy, e.g.,
JTextField input_text = new JTextField("0", 8);
Container cp = getContentPane();
...
cp.add(input_text);
The first statement creates a JTextField object that displays 8 characters of text and
initially shows the string, "0".
The standard operations one does with a text field is extract the text the user has
typed, e.g.,
String data = input_text.getText();
and reset the text in the text field with a new string:
input_text.setText("0");
10.9. TEXT FIELDS
611
These operations might be used when, say, the user pushes a button that starts an
action listener that extracts and resets the text field’s contents.
As Figure 5 indicates, a JTextField is a subclass of a JTextComponent and therefore
inherits a variety of methods for cutting, copying, pasting, and selecting. We will not
study these methods for the moment; they are more useful for so-called text areas
(multi-line text components), which we study in a later section.
To show use of a text field, we develop a simple temperature convertor, which
accepts a numerical temperature, either Fahrenheit or Celsius, and converts it to the
equivalent temperature of the other scale. When the user types a temperature into
a text field, selects either the Celsius or Fahrenheit scale, and pushes the Go button,
the result is displayed in the view:
Figure 28 displays the view class for the temperature converter. It is written in
two parts: An abstract class provides all the methods but one, and a concrete class
extends the abstract one with a coding for displayError, a method that displays
error messages.
The view’s public methods will be used by the controllers, ResetButton and
ComputeTempButton, to fetch the text the user typed, to display the answer that the
model computes, and to reset the text field for another conversion. Figure 29 displays
the two controllers as well as a simplistic model class, TempCalculator.
ComputeTempButton uses the model to convert temperatures. For the moment, ignore the try...catch exception handler within the button’s actionPerformed method
and examine its interior: The controller sends the view a getInputs message to receive
an array of two strings, one containing the input temperature and one containing the
temperature’s scale. The first string is converted into a double. Assuming that the
conversion is successful, the scale is examined and used to choose the correct conversion method from the model object. Finally, the view’s displayAnswer method shows
the converted temperature.
What happens if the user types a bad temperature, e.g., "abc0"? In this case, the
statement,
612
Figure 10.28: view class for temperature converter
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/** AbsTempFrame creates a view that displays temperature conversions */
public abstract class AbsTempFrame extends JFrame
{ private String START TEXT = "0";
private String BLANKS = "
";
private JTextField input text = new JTextField(START TEXT, 8);
private JLabel answer = new JLabel(BLANKS);
// components for the temperature scales list:
private String[] choices = {"Celsius", "Fahrenheit"};
private JList scales = new JList(choices);
/** AbsTempFrame constructs the frame */
public AbsTempFrame()
{ // the controller that triggers temperature conversion; see Figure 29:
ComputeTempButton compute controller = new ComputeTempButton("Go", this);
Container cp = getContentPane();
cp.setLayout(new GridLayout(4, 1));
JPanel p1 = new JPanel(new FlowLayout());
p1.add(new JLabel("Convert degrees:"));
cp.add(p1);
JPanel p2 = new JPanel(new FlowLayout());
p2.add(input text);
p2.add(scales);
cp.add(p2);
JPanel p3 = new JPanel(new FlowLayout());
p3.add(answer);
cp.add(p3);
JPanel p4 = new JPanel(new FlowLayout());
p4.add(compute controller);
p4.add(new ResetButton("Reset", this)); // see Figure 29
p4.add(new ExitButton("Bye"));
// see Figure 16
cp.add(p4);
resetFields(); // initialize the view
setSize(240, 180);
setTitl
Download