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