Academia.eduAcademia.edu

Who's Afraid of Java

2003

AI-generated Abstract

The book challenges the notion that programming is an exclusive skill reserved for specialists, advocating for the idea that anyone can learn to understand and write programs. The author emphasizes the importance of this knowledge in an increasingly technology-dependent society, while also aiming to present programming in an approachable yet serious manner. It incorporates a conversational tone and interactive elements, with input from a primary test reader, Susan, who engages with the material to clarify and enhance understanding.

Who’s Afraid of Java? by Steve Heller Copyright (c) 1997, 2003 by Chrysalis Software Corporation All rights reserved Contents Figures xi Dedication xvii Acknowledgements xix Preface xxi Letter from a Novice xxv Foreword xxvii About the Author xxix Chapter 1: Prologue Introduction to Programming . . . . . . . . . . . . . . . . How to Write a Program . . . . . . . . . . . . . . . . . Baby Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . On with the Show . . . . . . . . . . . . . . . . . . . . . . 1 1 5 6 8 9 Chapter 2: Hardware Fundamentals Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Objectives for This Chapter . . . . . . . . . . . . . . . . . 10 Behind the Curtain . . . . . . . . . . . . . . . . . . . . . . . . 11 Disk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1985, a Space Odyssey . . . . . . . . . . . . . . . . . 13 RAM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 Return to Sender, Address Unknown . . . . . . 15 Odometer Trouble . . . . . . . . . . . . . . . . . . . . . . . . 17 Back to the Future . . . . . . . . . . . . . . . . . . . . . 21 v Over-Hexed . . . . . . . . . . . . . . . . . . . . . . . . . . Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . The CPU . . . . . . . . . . . . . . . . . . . . . . . . . . . . Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Answers to Exercises . . . . . . . . . . . . . . . . . . . . . . 22 27 28 29 30 30 Chapter 3: Basics of Programming Creative Programming? . . . . . . . . . . . . . . . . . . . . Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objectives for This Chapter . . . . . . . . . . . . . . . . . Speed Demon . . . . . . . . . . . . . . . . . . . . . . . . . . . . Blaming It on the Computer . . . . . . . . . . . . . That Does Not Compute . . . . . . . . . . . . . . . . The Man behind the Curtain . . . . . . . . . . . . . . . . Lost in Translation . . . . . . . . . . . . . . . . . . . . . . . . What’s Going on Underneath? . . . . . . . . . . . . . . Underware? . . . . . . . . . . . . . . . . . . . . . . . . . . Stacking the Deck . . . . . . . . . . . . . . . . . . . . . Compiler’s-Eye View . . . . . . . . . . . . . . . . . . How It All Stacks Up . . . . . . . . . . . . . . . . . . A Cast of Characters . . . . . . . . . . . . . . . . . . . . . . The Right Type . . . . . . . . . . . . . . . . . . . . . . . Reality Check . . . . . . . . . . . . . . . . . . . . . . . . Some Strings Attached . . . . . . . . . . . . . . . . . In and Out . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . If Only You Knew . . . . . . . . . . . . . . . . . . . . . . . . While We’re on the Subject . . . . . . . . . . . . . . . . Separate but Possibly Equal . . . . . . . . . . . . . How to Do the Exercises . . . . . . . . . . . . . . . . . . . Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Just up the Block . . . . . . . . . . . . . . . . . . . . . . . . . At the Fair . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Novice Alert . . . . . . . . . . . . . . . . . . . . . . . . . Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Answers to Exercises . . . . . . . . . . . . . . . . . . . . . . 33 33 33 34 35 35 36 37 38 43 43 43 47 52 60 64 65 65 66 70 71 73 74 74 75 75 80 81 84 84 Chapter 4: More Basics A Modest Proposal . . . . . . . . . . . . . . . . . . . . . . . Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objectives for This Chapter . . . . . . . . . . . . . . . . . Algorithmic Thinking . . . . . . . . . . . . . . . . . . . . . A Prize Catch . . . . . . . . . . . . . . . . . . . . . . . . . 93 93 93 94 94 97 vi What a Tangled Web We Weave . . . . . . . . You May Already Have Won . . . . . . . . . . . . . . Variables, by the Numbers . . . . . . . . . . . . . Details, Details . . . . . . . . . . . . . . . . . . . . . . To Really Foul Things Up Requires a Computer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Terror in the Year Zero . . . . . . . . . . . . . . . . . . . I’ll Think about It Tomorrow . . . . . . . . . . . In the Long Run, We Are All Dead . . . . . . . Garbage in, Garbage Out . . . . . . . . . . . . . . . Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Answers to Exercises . . . . . . . . . . . . . . . . . . . . . 126 128 130 131 132 135 137 138 138 Chapter 5: "You Know My Methods" Large Problems, Small Files? . . . . . . . . . . . . . . Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objectives for This Chapter . . . . . . . . . . . . . . . . Good Things Come in Small Packages . . . . Functioning Normally . . . . . . . . . . . . . . . . . . . . Above Average . . . . . . . . . . . . . . . . . . . . . . Return to Sender . . . . . . . . . . . . . . . . . . . . . For the Sake of Argument . . . . . . . . . . . . . . General Delivery . . . . . . . . . . . . . . . . . . . . . Using a Method . . . . . . . . . . . . . . . . . . . . . . . . . A Convincing Argument . . . . . . . . . . . . . . . A First-class Solution . . . . . . . . . . . . . . . . . . Operating Systematically . . . . . . . . . . . . . . . Don’t Call Me, I’ll Call You . . . . . . . . . . . . Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 143 143 144 145 146 151 153 155 159 159 164 164 165 167 168 169 Chapter 6: Taking Inventory A class Act . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objectives for This Chapter . . . . . . . . . . . . . . . . Pay Some Attention to the Man behind the Curtain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Second-class Citizens . . . . . . . . . . . . . . . . . . . . . Taking Stock . . . . . . . . . . . . . . . . . . . . . . . . . . . More Definitions . . . . . . . . . . . . . . . . . . . . . . . . Stock in Trade . . . . . . . . . . . . . . . . . . . . . . . Scoped Out . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 171 171 172 vii 101 102 103 124 173 174 177 180 180 186 Shop till You Drop . . . . . . . . . . . . . . . . . . . . . . References Required . . . . . . . . . . . . . . . . . . . . . Referred Pain . . . . . . . . . . . . . . . . . . . . . . . . Objection Sustained . . . . . . . . . . . . . . . . . . . Array of Hope . . . . . . . . . . . . . . . . . . . . . . . An Exceptional Opportunity . . . . . . . . . . . . . . . Better Read than Dead . . . . . . . . . . . . . . . . . Nothing Ventured, Nothing Gained . . . . . . I Take Exception to That Statement . . . . . . Reading between the Lines . . . . . . . . . . . . . Don’t Fence Me In . . . . . . . . . . . . . . . . . . . . Can I Help You? . . . . . . . . . . . . . . . . . . . . . The Customer Is Always Right . . . . . . . . . . Next Customer, Please? . . . . . . . . . . . . . . . . Nobody’s Home . . . . . . . . . . . . . . . . . . . . . . Testing, 1, 2, 3. . . . . . . . . . . . . . . . . . . . . . . Paging Rosie Scenario . . . . . . . . . . . . . . . . . Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Answers to Exercises . . . . . . . . . . . . . . . . . . . . . 191 197 201 202 203 206 207 210 215 219 221 223 231 234 240 245 249 250 255 256 256 Chapter 7: Stocking Up Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objectives for This Chapter . . . . . . . . . . . . . . . . Under Control . . . . . . . . . . . . . . . . . . . . . . . . . . Claiming an Inheritance . . . . . . . . . . . . . . . . . . A Dated Approach . . . . . . . . . . . . . . . . . . . . . . . Ancestor Worship . . . . . . . . . . . . . . . . . . . . Protection Racket . . . . . . . . . . . . . . . . . . . . . Stock Footage . . . . . . . . . . . . . . . . . . . . . . . Getting static . . . . . . . . . . . . . . . . . . . . . . . . . Reordering Priorities . . . . . . . . . . . . . . . . . . Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259 259 260 261 266 268 271 277 279 282 288 291 293 293 Chapter 8: Pretty Poly Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objectives for This Chapter . . . . . . . . . . . . . . . . Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . Poly Anna . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A Pointed Reminder . . . . . . . . . . . . . . . . . . Maintenance Required . . . . . . . . . . . . . . . . . . . . 295 295 296 296 300 308 312 viii Begin at the Beginning . . . . . . . . . . . . . . . . Blind Date . . . . . . . . . . . . . . . . . . . . . . . . . . Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313 316 318 318 Appendix A: Tying up Loose Ends Where Am I, Anyway? . . . . . . . . . . . . . . . . . . . Tying up Loose Ends . . . . . . . . . . . . . . . . . . . . . Reference Substitution . . . . . . . . . . . . . . . . . Command Line Arguments . . . . . . . . . . . . . Operator Precedence . . . . . . . . . . . . . . . . . . Other Native Data Types . . . . . . . . . . . . . . . The Vector . . . . . . . . . . . . . . . . . . . . . . . . . . . The interface . . . . . . . . . . . . . . . . . . . . . . . . . Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . Standard packages . . . . . . . . . . . . . . . . . . . . . Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321 321 322 322 322 323 323 324 325 325 325 326 Appendix B: Java Q&A Questions and Answers . . . . . . . . . . . . . . . . . . . Equality Now? . . . . . . . . . . . . . . . . . . . . . . . Input and Output . . . . . . . . . . . . . . . . . . . . . Exceptional Opportunities . . . . . . . . . . . . . . In Resplendent Array . . . . . . . . . . . . . . . . . . Getting into an Argument . . . . . . . . . . . . . . Semifinalist . . . . . . . . . . . . . . . . . . . . . . . . . 327 327 328 330 333 338 340 342 Glossary 345 Index 371 ix x Figures Figure 2.1: The first few numbers . . . . . . . . . . . . . . . Figure 2.2: The next few numbers . . . . . . . . . . . . . . . Figure 2.3: How many combinations? . . . . . . . . . . . . Figure 2.4: Binary to hex conversion table . . . . . . . . Figure 2.5: Different representations of the same numbers . . . . . . . . . . . . . . . . . . . . . . . . . . Figure 3.1: A little numeric calculation . . . . . . . . . . . Figure 3.2: A stack with one entry . . . . . . . . . . . . . . Figure 3.3: A stack with two entries . . . . . . . . . . . . . Figure 3.4: A stack with three entries . . . . . . . . . . . . Figure 3.5: A really little numeric calculation . . . . . . Figure 3.6: Compiling, part 1 . . . . . . . . . . . . . . . . . . Figure 3.7: Compiling, part 2 . . . . . . . . . . . . . . . . . . Figure 3.8: Compiling, part 3 . . . . . . . . . . . . . . . . . . Figure 3.9: Compiling, part 4 . . . . . . . . . . . . . . . . . . Figure 3.10: Execution, part 1 . . . . . . . . . . . . . . . . . . Figure 3.11: Execution, part 2 . . . . . . . . . . . . . . . . . . Figure 3.12: Execution, part 3 . . . . . . . . . . . . . . . . . . Figure 3.13: Execution, part 4 . . . . . . . . . . . . . . . . . . Figure 3.14: Execution, part 5 . . . . . . . . . . . . . . . . . . Figure 3.15: Execution, part 6 . . . . . . . . . . . . . . . . . . Figure 3.16: Execution, part 7 . . . . . . . . . . . . . . . . . . Figure 3.17: Some real characters and Strings (code\Basic00\Basic00.java) . . . . . . . . . Figure 3.18: Special characters for program text . . . Figure 3.19: Simple input (code\Basic00\Basic02.java) . . . . . . . . . Figure 3.20: Using an if statement (code\Basic03\Basic03.java) . . . . . . . . . Figure 3.21: Using a while statement (code\Basic04\Basic04.java) . . . . . . . . . Figure 3.22: A Java Program (code\Pump1\Pump1.java) . . . . . . . . . . xi 18 19 20 25 26 40 44 44 45 47 48 49 49 51 52 54 55 56 57 58 59 61 66 68 70 72 77 Figure 3.23: First dinner party program (code\Basic05\Basic05.java) . . . . . . . . . 85 Figure 3.24: else if example . . . . . . . . . . . . . . . . . . . 86 Figure 3.25: Second dinner party program (code\Basic06\Basic06.java) . . . . . . . . . 86 Figure 3.26: Name and age program (code\Basic07\Basic07.java) . . . . . . . . . 87 Figure 3.27: Novice program (code\Basic08\Basic08.java) . . . . . . . . . 88 Figure 3.28: Allowance program (code\Basic09\Basic09.java) . . . . . . . . . 90 Figure 3.29: Grading program (code\Basic10\Basic10.java) . . . . . . . . . 91 Figure 4.1: Finding the top two weights, first try (code\Pump1a\Pump1a.java) . . . . . . . . . 95 Figure 4.2: Susan’s version of an if statement with an else clause . . . . . . . . . . . . . . . . . . . . . . . . 98 Figure 4.3: Using an if statement with an else clause . 99 Figure 4.4: Finding the top two weights (code\Pump2\Pump2.java) . . . . . . . . . . 100 Figure 4.5: What if? . . . . . . . . . . . . . . . . . . . . . . . . . 101 Figure 4.6: Using an array (code\Vect1\Vect1.java) . . . . . . . . . . . . 106 Figure 4.7: Using a for statement (from code\Vect1\Vect1.java) . . . . . . . . . . . . . 109 Figure 4.8: Sorting the weights (from code\Vect1\Vect1.java) . . . . . . . . . . . . . 118 Figure 4.9: Initial situation . . . . . . . . . . . . . . . . . . . 125 Figure 4.10: After the first pass . . . . . . . . . . . . . . . . 125 Figure 4.11: After the second pass . . . . . . . . . . . . . 125 Figure 4.12: Final situation . . . . . . . . . . . . . . . . . . . 126 Figure 4.13: Garbage prevention, first attempt (from code\Vect2a\Vect2a.java) . . . . . . . . . . 133 Figure 4.14: Finding the top three weights using arrays (code\Vect3\Vect3.java) . . . . . . . . . . . 134 Figure 4.15: Exercise 1 (code\Morbas01\Morbas01.java) . . . . 137 Figure 4.16: A weight program (code\Morbas03\Morbas03.java) . . . . 139 Figure 4.17: The weight-totalling program (code\Morbas04\Morbas04.java) . . . . 140 Figure 5.1: A sample program with duplicated code (code\Nofunc\Nofunc.java) . . . . . . . . . . 148 Figure 5.2: A method call . . . . . . . . . . . . . . . . . . . . 149 xii Figure 5.3: A method to average two values . . . . . . 152 Figure 5.4: Argument passing with one argument (code\BirthdayTest\BirthdayTest.java) . 156 Figure 5.5: Using the Average method (code\Func1\Func1.java) . . . . . . . . . . . 160 Figure 6.1: The initial sample program for the StockItem class (code\Itemtst1\Itemtst1.java) . . . . . 179 Figure 6.2: The initial class definition for the StockItem class (code\Itemtst1\StockItem.java) . . . 182 Figure 6.3: The default constructor for the StockItem class (from code\Itemtst1\Item1.java) . . . . . . 185 Figure 6.4: Another constructor for the StockItem class (from code\Itemtst1\Item1.java) . . . . . . 191 Figure 6.5: Display method for the StockItem class (from code\Itemtst1\StockItem1.java) . . . . . . 193 Figure 6.6: The SetName method of the StockItem class (from code\Itemtst1\Item1.java) . . . . . . 196 Figure 6.7: item1 and its object . . . . . . . . . . . . . . . . . 197 Figure 6.8: item1, item2, and their object . . . . . . . . . . 198 Figure 6.9: item1, item2, and their object . . . . . . . . . . 198 Figure 6.10: A sample program for references (code\Reference1\ReferenceTest1.java) .199 Figure 6.11: Output of sample program for references (code\Reference1\Reftest1.out) . . . . . . 200 Figure 6.12: Reading and displaying an array of StockItems (code\Itemtst2\Itemtst2.java) . . . . . . . 205 Figure 6.13: An uninitialized array of StockItem references . . . . . . . . . . . . . . . . . . . . . . 209 Figure 6.14: An array of uninitialized StockItem references . . . . . . . . . . . . . . . . . . . . . . 209 Figure 6.15: An array of StockItem references, with the first reference initialized . . . . . . . . . . . 211 Figure 6.16: An array of StockItem references, with the first StockItem set up for use . . . . . . . . . 212 Figure 6.17: The Read method for the StockItem class (from code\Itemtst2\StockItem.java) . 213 Figure 6.18: Trying to update inventory (code\Itemtst3\Itemtst3.java) . . . . . . . 224 Figure 6.19: An enhanced StockItem class (code\Itemtst4\Stockitem.java) . . . . . . 229 Figure 6.20: Updating StockItem inventory (code\Itemtst4\Itemtst4.java) . . . . . . . 232 xiii Figure 6.21: Inventory class (code\Itemtst5\Inventory.java) . . . . . . 236 Figure 6.22: Default constructor for Inventory class (from code\Itemtst5\Inventory.java) . . . . . . . 238 Figure 6.23: The LoadInventory method for the Inventory class (from code\Itemtst5\Inventory.java) . . . . . . . 239 Figure 6.24: FindItem method for Inventory class (from code\Itemtst5\invent1.java) . . . . . . . . . 241 Figure 6.25: The implementation of GetPrice (from code\Itemtst5\StockItem.java) . . . . . . 242 Figure 6.26: Current implementation for StockItem class (code\Itemtst5\StockItem.java) . . . . . . 242 Figure 6.27: Latest Inventory class (code\Itemtst5\Inventory.java) . . . . . . 245 Figure 6.28: Updated inventory application (code\Itemtst5\Itemtst5.java) . . . . . . . 246 Figure 6.29: The Write method for the StockItem class (from code\Itemtst6\StockItem.java) . 257 Figure 6.30: The StoreInventory method for the Inventory class (from code\Itemtst6\Inventory.java) . . . . . . . 257 Figure 6.31: The changes to the application program (from code\Itemtst6\Itemtst6.java) . . . 258 Figure 7.1: The latest StockItem class (code\Itemtst20\StockItem.java) . . . . . . 263 Figure 7.2: The ReorderItems method for the Inventory class (from code\itemtst20\Inventory.java) . . 266 Figure 7.3: The Reorder method for the StockItem class (from code\Itemtst20\StockItem.java) . 267 Figure 7.4: The Today method for the DatedStockItem class (from code\Itemtst21\DatedStockItem.java) . . 269 Figure 7.5: A StockItem object . . . . . . . . . . . . . . . . . . 271 Figure 7.6: A DatedStockItem object . . . . . . . . . . . . . . 272 Figure 7.7: protected fields in StockItem (from code\itemtst21\StockItem.java) . . . . . . . 279 Figure 7.8: Implementation for DatedStockItem (code\Itemtst21\DatedStockItem.java) . 279 Figure 7.9: Default constructor for DatedStockItem (from code\Itemtst21\DatedStockItem.java) . . 283 Figure 7.10: How the compiler interprets the default constructor for DatedStockItem (from code\Itemtst21\DatedStockItem.java) . 283 xiv Figure 7.11: Constructing a default DatedStockItem object . . . . . . . . . . . . . . . . . . . . . . . . . . 285 Figure 7.12: Normal constructor for DatedStockItem (from code\Itemtst21\DatedStockItem.java) . 286 Figure 7.13: Constructing a DatedStockItem object . . 287 Figure 7.14: Reorder method for DatedStockItem (from code\Itemtst21\DatedStockItem.java) . 288 Figure 7.15: FormattedDisplay method for DatedStockItem (from code\Itemtst21\DatedStockItem.java) . 289 Figure 7.16: Test program for DatedStockItem (code\Itemtst21\itemtst21.java) . . . . . . 290 Figure 8.1: Polymorphism test program (code\Polytest\Polytest.java) . . . . . . . . . 299 Figure 8.2: Polymorphism test program output (code\Polytest\Polytest.out) . . . . . . . . . 299 Figure 8.3: A simplified StockItem object . . . . . . . . . 302 Figure 8.4: A simplified DatedStockItem object . . . . . 302 Figure 8.5: Calling the Reorder method of StockItem through a StockItem reference to a StockItem object . . . . . . . . . . . . . . . . . . . . . . . . . . . 303 Figure 8.6: Calling the Reorder method of DatedStockItem through a DatedStockItem reference . . . . . 304 Figure 8.7: Calling the Reorder method of DatedStockItem through a StockItem reference . . . . . . . . . 305 Figure 8.8: A simplified StockItem object with two methods . . . . . . . . . . . . . . . . . . . . . . . . . 307 Figure 8.9: A simplified DatedStockItem object with two methods . . . . . . . . . . . . . . . . . . . . . . . . . 307 Figure 8.10: Mixing StockItem and DatedStockItems in a single array (code\Polytest2\Polytest2.java) . . . . . . 308 Figure 8.11: Calling the Write method of StockItem through a StockItem reference . . . . . . . . 310 Figure 8.12: Calling the Write method of DatedStockItem through a StockItem reference . . . . . . . . 311 Figure 8.13: StockItem.Write (from code\Itemtst22\StockItem.java) . . . . . 313 Figure 8.14: DatedStockItem.Write (from code\Itemtst22\DatedStockItem.java) . 314 Figure 8.15: The Inventory.Read method (from code\Itemtst22\Inventory.java) . . . . . . 315 xv Figure 8.16: Test program for DatedStockItem and StockItem (code\Itemtst22\Itemtst22.java) . . . . . 317 Figure B.1: Equality testing program (code\Equality\Equality.java) . . . . . . . . 328 Figure B.2: Corrected equality testing program (code\Equal2\Equal2.java) . . . . . . . . . . 329 Figure B.3: Reading an int from the keyboard (from code\WAJ\WAJ\RWvar.java) . . . . . . . 331 Figure B.4: Test input program (code\Testinput\Testinput.java) . . . . . . 332 Figure B.5: Test input/output program (code\IOTest1\IOTest1.java) . . . . . . . . 333 Figure B.6: Test input/output program (code\IOTest2\IOTest2.java) . . . . . . . . 334 Figure B.7: Output of first test input/output program with exception handling . . . . . . . . . . . . 335 Figure B.8: Test input/output program with exception handling (code\Except1\Except1.java) . 336 Figure B.9: Second test input/output program with exception handling (code\Except2\Except2.java) . . . . . . . . 337 Figure B.10: Output of second test input/output program with exception handling . . . . 338 Figure B.11: A sample program for arrays (code\ArrayTest1\ArrayTest1.java) . . 339 Figure B.12: A sample program for arrays (code\ArrayTest2\ArrayTest2.java) . . 339 Figure B.13: How arguments work (code\Arg1\Arg1.java) . . . . . . . . . . . . 341 xvi Dedication This book is dedicated to Susan Patricia Caffee Heller, the light of my life. Without her, this book would not be what it is; even more important, I would not be what I am: a happy man. xvii xviii Acknowledgements I’d like to thank all those readers who have posted reviews of my books on Amazon.com for helping me make the transition to fulltime writing. I’d also like to thank Sam Porter and Kurt Matti, our hosts on our honeymoon in Europe, for all the hospitality they showed us, most especially for letting us use their telephone lines for our email. Of course, I’m deeply indebted to Eric Raymond for his wonderful foreword; I can only hope that you and my other readers like this book as well as he does! xix xx Preface Is this book for you? If you ’re a programmer in a language other than Java, and want to upgrade your skills, then the answer is yes.1 But what if you have no previous programming experience? In that case, here’s a little quiz that may help you decide: 1. 2. 3. Do you want to know how the programs in your computer work inside, and how to write some of your own? Are you willing to exert yourself mentally to learn a complex technical subject? Do you have a sense of humor? If you’ve answered yes to these questions and follow through with the effort required, then you will get a lot out of this book. The common wisdom states that programming is a difficult subject that should be reserved for a small number of specialists. One of the main reasons that I have written this book is that I believe this attitude is wrong; it is possible, and even desirable, for you to learn how programs work and how to write them. Those who don’t understand how computers perform their seemingly magical feats are at an increasing disadvantage in a society ever more dependent on these extraordinary machines. Regardless of the topic, I can see no valid reason for a book to be stuffy and dry, and I’ve done everything possible to make this one approachable. However, don’t let the casual tone fool you into thinking that the subject is easy; there is no "royal road" to programming, any more than there is to geometry. Especially if you have no prior experience in programming, this book will stretch your mind more than virtually any other subject you could study. One important reason why this book is different from other books is the participation of Susan, my primary "test reader", whose ______________________________________________________________________________ 1. If you are a C++ programmer, you might want to read Appendix B, which describes the most important differences between that language and Java before reading the rest of the book. xxi account of her involvement in this project immediately follows this preface. I recommend that you read that account before continuing with the technical material following it, as it explains how and why she contributed to making your task easier and more enjoyable. Speaking of Susan, here is a bit of correspondence between us on the topic of how one should read this book, which occurred after her first reading of what is now Chapters 2 and 3: Susan: Let me say this: to feel like I would truly understand it, I would really need to study this about two more times. Now, I could do this, but I am not sure you would want me to do so. I think reading a chapter once is enough for most people. Steve: As a matter of fact, I would expect the reader of my book to read and study this chapter several times if necessary; for someone completely new to programming, I imagine that it would be necessary. Programming is one of the most complex human disciplines, although it doesn’t take the mathematical skills of a subject such as nuclear physics, for example. I’ve tried to make my explanations as simple as possible, but there’s no way to learn programming (or any other complex subject) without investing a significant amount of work and thought. After she had gone through the text a number of times and had learned a lot from the process, we continued this discussion as follows: Susan: Well then, maybe this should be pointed out in a preface or something. Of course, it would eventually be obvious to the reader as it was to me, but it took me a while to come to that conclusion. The advantage of knowing this in advance is that maybe I would not be so discouraged that I was not brilliant after one read of a chapter. Steve: I will indeed mention in the preface that the reader shouldn’t be fooled by the casual tone into thinking that this is going to be a walk in the park. In any event, please don’t be discouraged. It seems to me that you have absorbed a fair amount of very technical material with no previous background; that’s something to be proud of! We’ll be hearing from Susan many more times in the course of the book. She will be checking in frequently in the form of extracts from the e-mail discussion we engaged in during the testing and revising process. I hope you will find her comments and my replies add a personal touch to your study of this technical material. While we’re on the topic of your studying, this would be a good time to tell you how to get updates and help with any errors you xxii might find in the book or any other questions you might have. The best way is to visit my WWW page, which can be found at http://www.steveheller.com. If you don’t have WWW access, you can write to me via email, at [email protected]. In the event that you enjoy this book and would like to tell others about it, you might want to write an on-line review on Amazon.com, which you can do by visiting my home page and following the links to the "readers reviews" on Amazon. I should also tell you how the various typefaces are used in the book. H e l v e t i c a N a r r o w is used for program listings, for terms used in programs, and for words defined by the Java language. I t a l i c s a re used primarily for technical terms that are found in the glossary, although they are also used for emphasis in some places. The first time that a particular technical term is used, it is in bold face; if it is a term defined in the Java language, it will be in HelveticalNarrowBold. The next voice you will hear is that of Susan, my test reader. I hope you get as much out of her participation in this book as I have. xxiii xxiv Letter from a Novice As a famous frog once said, "It’s not easy being green". Yet being new at programming languages seems to be my forte these days, at least according to Steve Heller, who, after one tumultuous effort in the production of his book Who’s Afraid of C++? found me indispensable enough to employ my lack of knowledge once again in the making of Who’s Afraid of Java?. After being a "professional novice" for 2 years now I still can’t get used to the idea of my ignorance as being an important commodity. Sometimes I wonder why I so foolishly spent nearly 5 years in college. Perhaps if I had not done that I could be in huge demand as one of the world’s greatest "know-nothings". It really is a difficult position to rationalize, and creates a very odd slant to my reality system. However once again I have been prevailed upon to apply my expertise of knowing nothing to learning the Java programming language. And once again Steve and I went full circle, asking and answering questions concerning his writing until I felt comfortable enough with each concept that I could move on to the next. To do this has been and still is an exercise in extremes, emotionally and intellectually. But once again I sincerely hope that, for those of you totally new to programming, our efforts will make the road to learning Java a little less rocky for you because we tried to think like you do, and give you explanations that you will be able to understand. This book, based on the same general outline as Who’s Afraid of C++? , g i v e s a n o v i c e r e a d e r t h e b a s i c s o f p r o g r a m m i n g a n d t h e n leads up to some rather advanced concepts that even experienced programmers in other languages might find a bit of a stretch. This is because Java quickly incorporates what is considered a higher level of complexity in other languages. I must admit that there was one unexpected oversight on our part concerning my commission as the resident know-nothing. I suddenly realized not too far into the Java book that my career as a xxv professional novice might be in jeopardy, as we overlooked one minor detail, that is <ahem>, I know C++.1 That is, I know enough about C++ to make comparisons of the two languages, which came up many times in the reading of the book. That fact made it in some cases easier for me to read this book, but in some cases harder. While I must consider that in all fairness I might be suffering from "first language chauvinism", the differences in the two languages forced me to make side by side comparisons and it didn’t take me long to start to complain about what I disliked in Java.2 Even though my days of absolute novicehood may be over, I am sure that Steve will be imploring me to apply my relative lack of knowledge to yet another of his many book ideas. Since each new language will be just that (new) to me, I think I will be able to keep my status as a novice for quite some time to come. However, I have already put him on alert that there is at least one subject I won’t suffer through: there will be no book called Who’s Afraid of Assembly Language? with my name in it. I have seen the stuff, and I am certain about this: I am afraid of that! ______________________________________________________________________________ 1. When you get further along in the book, you may wonder on occasion how I could ask such basic questions if I already have knowledge of another language such as C++. The answer is simple: those questions are transplanted from our previous book, Who’s Afraid of C++?. Of course, they have been edited to fit into a discussion of Java, but they are essentially the same questions I asked when I was first learning about programming. 2. You can find some of these complaints in Appendix B, which discusses the differences between C++ and Java. xxvi Foreword Steve and Susan Heller have given a new meaning to the cliche "labor of love". By the time you read this book, I expect to have danced at their wedding. That ceremony will make a perfect symbol of what this book is and why I care about it — and why I think you will, too. The best technical books marry the expertise of a technical expert with the curiosity and eagerness of a novice. Books like this do not merely recite facts and approaches; they challenge. They motivate. They are even (what a concept!) fun to read. Because so much technical tutorial writing is boring, pretentious, and constipated in style, it is easy to forget these books exist. But every once in a while a book like Who’s Afraid of Java? comes along and reminds us that the very best technical books are discoveries in both knowledge and joy. Tutorial books this good are very hard to write. The people who write them are, after all, experts. It takes a very difficult mental effort to abandon that expertise, to see their fields through a novice’s eyes. It takes an even more taxing effort to hold the expert and n o v i c e v i e w p o i n t s i n o n e m i n d s i m u l t a n e o u s l y . T h e w o n d e r i s n’ t that really good technical tutorials by one author are so rare, it’s that they exist at all. Perhaps the ideal team for a good technical book is one expert and one highly motivated novice. But collaboration has its own perils. The partners have to be compatible. They have to understand each other. They have to work to resolve differences. They have to be committed to making the relationship work. It’s not unlike being...married. Susan was Steve’s novice. They collaborated on this book as they did on Who’s Afraid of C++?. Her questions in the text are lucid, penetrating, unafraid. They’re the right questions to support a novice reader struggling through the unavoidable complexities of the exposition. xxvii The dialogue between them — both intelligent, both able writers, gives this book an almost unique strength. Susan stimulates Steve to address the novice viewpoint without ever having to abandon his natural voice as an expert. The sympathy between them shows; and it invites the reader almost irresistibly into the conversation and the sense of intellectual intimacy it creates. (Oh, yes. If it’s not obvious from the foregoing, this book works technically, too. It’s a thorough and concise treatment of its topics. I differ with Steve’s opinions about Java sometimes, but his facts are reliable.) Steve and I have been friends since long before either of us were successful authors; we know each other well. After Who’s Afraid of Java? I feel like I know Susan’s personality nearly as well. What they’ve done together is a delight and an astonishment. I look forward to more books from these two, perhaps Who’s Afraid of the World Wide Web? I look forward to meeting Susan at their wedding in June 1997. And I’ll have just two things to say to Steve as I shake his hand afterwards: 1. Keep this one, she’s good for you! 2. Are you going to put her name on the cover next time? Eric Raymond Malvern, Pa. May 1997 xxviii About the Author Steve Heller had always been fascinated by writing. In his childhood days in the 1950s and 1960s, he often stayed up far past his bedtime reading science fiction. Even in adulthood, if you came across him in his off-hours, he was more likely to be found reading a book than doing virtually anything else. After college, Steve got into programming more or less by accident; he was working at an actuarial consulting firm and was selected to take charge of programming on their time-sharing terminal, because he was making much less than most of the other employees. Finding the programming itself to be more interesting than the actuarial calculations, he decided to become a professional programmer. Until 1984, Steve remained on the consuming side of the writing craft. Then one day he was reading a magazine article on some programming-related topic and said to himself, "I could do better than that". With encouragement from his wife of the time, he decided to try his hand at technical writing. Steve’s first article submission — to the late lamented Computer Language Magazine — was published, as were a dozen more over the next ten years. But although writing magazine articles is an interesting pastime, writing a book is something entirely different. Steve got his chance at this new level of commitment when Harry Helms, then an editor for Academic Press, read one of his articles in Dr. Dobb’s Journal and wrote him a letter asking whether he would be interested in writing a book for AP. He answered, "Sure, why not?", not having the faintest idea of how much work he was letting himself in for. T h e r e s u l t i n g b o o k , L a r g e P r o b l e m s , S m a l l M a c h i n e s, received favorable reviews for its careful explanation of a number of facets of program optimization, and sold a total of about twenty thousand copies within a year after publication of the second edition, entitled Efficient C/C++ Programming. By that time, Steve was hard at work on his next book, Who’s Afraid of C++, which is designed to make object-oriented xxix programming intelligible to anyone from the sheerest novice to the programmer with years of experience in languages other than C++. To make sure that his exposition was clear enough for the novice, he posted a message on CompuServe requesting the help of someone new to programming. The responses included one from a woman named Susan, who ended up contributing a great deal to the book. Her contribution was wonderful, but not completely unexpected. What was unexpected was that Steve and Susan would fall in love during the course of this project, but that’s what happened. Since she lived in Texas and he lived in New York, this posed some logistic difficulties. The success of his previous book now became extremely important, as it was the key to Steve’s becoming a full-time writer. Writers have been "telecommuting" since before the invention of the telephone, so his conversion from "programmer who writes" to "writer" made it possible for him to relocate to her area, which he promptly did. Since his move to Texas, Steve has been hard at work on his writing projects, including Introduction to C++, a classroom text that covers more material in the same space as Who’s Afraid of C++? at the expense of the email exchanges in the latter book, followed by the book you are now reading. Steve and Susan were married in 1997. xxx Chapter 1 Prologue Introduction to Programming "Begin at the beginning, and go on till you come to the end: then stop." This method of telling a story is as good today as it was when the King of Hearts prescribed it to the White Rabbit in Alice in Wonderland. In this book, we must begin with you, the reader, since my job is to explain a technical subject to you. It might appear that I’m at a severe disadvantage; after all, I’ve never met you. Nevertheless, I can make some pretty good guesses about you. You almost certainly own a computer and know how to use its most common application, word processing. If you use the computer in business, you probably also have an acquaintance with spreadsheets and perhaps some database experience as well. Now you have decided to learn how to program the computer yourself rather than relying completely on programs written by others. On the other hand, you might be a student using this book as a text in an introductory course on programming. In that case, you’ll be happy to know that this book isn’t written in the dry, overly academic style employed by many textbook writers. I hope that you will enjoy reading it, as my "test readers" have. Whether you are using this book on your own or in school, there are many good reasons to learn how to program. You may have a problem that hasn’t been solved by commercial software; you may want a better understanding of how commercial programs function so you can figure out how to get around their shortcomings and peculiarities; or perhaps you ’re just curious about how computers perform their seemingly magical feats. Whatever the initial reason, I 1 2 Chapter 1: PROLOGUE hope you come to appreciate the great creative possibilities opened up by this most ubiquitous of modern inventions.1 Before we begin, however, we should agree on definitions for some fundamental words in the computing field. Susan had some incisive observations about the power of words. Here is our exchange on that issue: Susan: I will read something usually at face value, but often there is much more to it; that is why I don’t get it. Then, when I go back and really think about what those words mean, it will make more sense. This book almost needs to be written in ALL CAPS to get the novice to pay closer attention to each and every word. Steve: IMAGINE WRITING A BOOK IN ALL CAPS! THAT WOULD BE VERY DIFFICULT TO READ, DON’T YOU THINK? Many of the technical words used in this book are in the Glossary at the end of the book; it is also very helpful to have a good technical dictionary of computer terms, as well as a good general dictionary of English. Of course, you may not be able to remember all of these technical definitions the first time through. If you can’t recall the exact meaning of one of these terms, just look up the word or phrase in the index, and it will direct you to the page where the definition is stated. You could also look in the Glossary, at the end of the book. Definitions of key technical terms are listed there in alphabetical order. Before we continue, let’s check in again with Susan. The following is from her first letter to me about the contents of this book: Susan: I like the one-on-one feel of your text, like you are just talking to me. Now you did make a few references to how simple some things were that I didn’t catch on to, so it kinda made me feel I was not too bright for not seeing how apparently simple those things were. . . . I think maybe it would have been helpful if you could have stated from the onset of this book just what direction you were taking, at least chapter by chapter. I would have liked to have seen a goal stated or at least a summary of objectives from the beginning. I often would have the feeling I was just suddenly thrown into something as I was reading ______________________________________________________________________________ 1. Of course, it’s also possible that you already know how to program in another language and are using this book to learn how to do so in Java. If so, you’ll have a head start; I hope that you’ll learn enough to make it worth your while to wade through some material you already know. INTRODUCTION TO PROGRAMMING 3 along. Also (maybe you should call this Java for Dummies, or is that taken already?)2, you might even define what programming is! What a concept! Because it did occur to me that since I have never seen it done, or a language or anything, I really don’t know what programming is! I just knew it was something that nerds do. Susan’s wish is my command, so I have provided a list of objectives at the beginning of each chapter after this one. I’ve also fulfilled her request for a definition of some programming terms, starting as follows: An algorithm is a set of precisely defined steps to calculate an answer to a problem or set of problems, which is guaranteed to arrive at such an answer eventually. As this implies, a set of steps that might never end is not an algorithm. Programming is the art and science of solving problems by the following procedure:3 1. Find or invent a general solution to a class of problems. 2. Express this solution as an algorithm or set of algorithms. 3. Translate the algorithm(s) into terms so simple that a stupid machine like a computer can follow them to calculate the specific answer for any specific problem in the class. At this point, let’s see what Susan had to say about the above definition and my response: Susan: Very descriptive. How about this definition: "Programming is the process of being creative using the tools of science, such as incremental problem solving, to make a stupid computer do what you want it to"? That I understand! Your definition is just fine. A definition has to be concise and descriptive and that you have done and covered all the bases. But you know what is lacking? An example of what it looks like. Maybe just a little statement that really looks bizarre to me and then say that by the end of the chapter you will actually know what this stuff really means! Sort of like a coming attraction type of thing. ______________________________________________________________________________ 2. As it happens, that title is indeed taken, as is Java Programming for Dummies! 3. This definition is possibly somewhat misleading since it implies that the development of a p r o g r a m i s s t r a i g h t f o r w a r d a n d l i n e a r , w i t h n o r e v i s i o n s r e q u i r e d . T h i s i s k n o w n as t h e "waterfall model" of programming, since water going over a waterfall follows a preordained course in one direction. In real-life, however, programming doesn’t usually work this way; rather, most programs are written in an incremental process as assumptions are changed and errors are found and corrected. 4 Chapter 1: PROLOGUE Steve: I understand the idea of trying to draw the reader into the "game"; however, I think that presenting a bunch of apparent gibberish with no warning could frighten readers as easily as it might intrigue them. I think it’s better to delay showing examples until they have some background. Now let’s return to our list of definitions. Hardware refers to the physical components of a computer, the ones you can touch. Examples include the keyboard, the monitor, the printer. Software refers to the other, nonphysical components of a computer, the ones you cannot touch. If you can install it on your hard disk, it’s software. Examples include a spreadsheet, a word processor, a database program. A source-code program is a program in a form suitable for reading and writing by a human being. A byte-code program is a Java program in a form suitable for running (by an interpreter) on a user’s computer. A byte-code instruction is one of the fundamental operations that a Java interpreter can perform. Some examples of these operations are addition, subtraction, or other arithmetic operations; other possibilities include operations that control what instruction will be executed next. All Java programs must be converted into byte-code instructions by a compiler before they can be executed by the Java interpreter. Compilation, in Java, is the process of translating source code into a byte-code program, which is composed of byte-code instructions along with the data needed by those instructions. We have to compile Java programs before we can use them, because the Java interpreter (see following definition) doesn’t know how to deal with source-code instructions, only with byte-code instructions. A Java compiler is a program that performs compilation as defined above. An interpreter is a program that controls the execution of another program. HOW TO WRITE A PROGRAM 5 The Java interpreter is the program that controls the execution of your Java programs. How to Write a Program Now you have a definition of programming. Unfortunately, however, this doesn’t tell you how to write a program. The process of solving a problem by programming in Java follows these steps: Problem: Algorithms: Java: Compiler: Interpreter: After discussions between the user and the programmer, the programmer defines the problem precisely. The programmer finds or creates algorithms that will solve the problem. The programmer implements these algorithms as source code in Java. The programmer runs the Java compiler, which must already be present on the programmer’s machine, to translate the source code into a bytecode program. The user uses a Java interpreter to run the resulting byte-code program on his or her computer. These steps advance from the most abstract to the most concrete, which is perfectly appropriate for an experienced Java programmer. However, if you’re using this book to learn how to program in Java, you’re obviously not an experienced Java programmer, so before you c a n f o l l o w t h i s p a t h t o s o l v i n g a p r o b l e m , y o u’ r e g o i n g t o n e e d a fairly thorough grounding in all of these steps. It’s not really feasible to discuss each step exhaustively before going to the next one, so I’ve created a little "step indicator" that you ’ l l s e e o n e a c h p a g e o f t h e text, with the currently active step shown in bold. For example, when we’re discussing algorithms, the indicator will display the word Algorithms in bold. The five steps of this indicator correspond to the five steps in problem solving just defined. I hope this device will make it easier for you to follow the sometimes tortuous path to programming knowledge. Let’s see what Susan thinks of it. Susan: With all the new concepts and all the new language and terms, it is so hard to know what one thing has to do with the other and where things are supposed to fit into the big picture. With the key, you can see Problem Algorithms Java Interpreter Hardware 6 Chapter 1: PROLOGUE how these things all fit as logical steps to an end. Now I know it isn’t going to be easy, but at least I know what my destination is before I board the plane. Anyway, you have to understand; for someone like me, this is an enormous amount of new material to be introduced to all at once. When you are bombarded with so many new terms and so many abstract concepts, it is a little hard to sort out what is what. Will you have guidelines for each of the steps? Since I know a little about this already, the more I look at the steps, I just know that what is coming is going to be a big deal. For example, take step 1: you have to give the ingredients for properly defining a problem. If something is left out, then everything that follows won’t work. Steve: I hope you won’t find it that frustrating, because I explain all of the steps carefully as I do them. Of course, it’s possible that I haven’t been careful enough, but in that case you can let me know and I’ll explain it further. Unfortunately, it’s not possible for me to provide a thorough guide to all of those steps, as that would be a series of books in itself. However, there’s a wonderful small book called How to Solve It by G. Polya that you should be able to get at your local library. It was written to help students solve geometry problems, but the techniques are applicable in areas other than geometry. I’m going to recommend that readers of my book read it if they have any trouble with general problem solving. Problem Algorithms The steps for solving a problem via programming might sound Java Interpreter reasonable in the abstract, but that doesn’t mean that you can follow them easily without practice. Assuming that you already have a Hardware pretty good idea of what the problem is that you’re trying to solve, the Algorithms step is likely to be the biggest stumbling block. Therefore, it might be very helpful to go into that step in a bit more detail. Baby Steps If we already understand the problem we’re going to solve, the next step is to figure out a plan of attack, which we will then break down into small enough steps to be expressed in Java. This is called stepwise refinement, since we start out with a "coarse" solution and refine it until the steps are within the capability of the Java language. For a complex problem, this may take several intermediate steps, but let’s start out with a simple example. Say that we want to know how much older one person is than another. We might start with the following general outline: BABY STEPS 1. 2. 3. 7 Get ages from user. Calculate difference of ages. Print the result. This can in turn be broken down further as follows: 1. 2. 3. Get ages from user. a. Ask user for first age. b. Ask user for second age. Subtract second age from first age. Print the result. This looks okay, except that if the first person is younger than the second one, then the result will be negative. That may be acceptable. If so, we’re just about done, since these steps are simple enough for us to translate them into Java fairly directly. Otherwise, we’ll have to modify our program to do something different depending on which age is higher. For example, 1. 2. 3. Get ages from user. a. Ask user for first age. b. Ask user for second age. Compute difference of ages. a. If first age is greater than second, subtract second age from first age. b. Otherwise, subtract first age from second age. Print result. You’ve probably noticed that this is a much more detailed description than would be needed to tell a human being what you want to do. That’s because the computer is extremely stupid and literal: it does only what you tell it to do, not what you meant to tell it to do. Unfortunately, it’s very easy to get one of the steps wrong, especially in a complex program. In that case, the computer will do something ridiculous, and you’ll have to figure out what you did wrong. This "debugging", as it’s called, is one of the hardest parts of p r o g r a m m i n g . A c t u a l l y , i t s h o u l d n’ t b e t o o d i f f i c u l t t o u n d e r s t a n d why that is the case. After all, you’re looking for a mistake you’ve made yourself. If you knew exactly what you were doing, you wouldn’t have made the mistake in the first place. Problem Algorithms Java Interpreter Hardware 8 Chapter 1: PROLOGUE I hope that this brief discussion has made the process of programming a little less mysterious. In the final analysis, it’s basically just logical thinking.4 On with the Show Now that you have some idea how programming works, it’s time to see exactly how the computer actually performs the steps in a program, which is the topic of Chapter 2. Problem Algorithms Java Interpreter Hardware ______________________________________________________________________________ 4. Of course, the word just in this sentence is a bit misleading; taking logical thinking for granted is a sure recipe for trouble. Chapter 2 Hardware Fundamentals Getting Started Like any complex tool, the computer can be understood on several levels. For example, it’s entirely possible to learn to drive an automobile without having the slightest idea of how it works. The analogy with computers is that it’s relatively easy to learn how to use a word processor without having any notion of how such programs work. On the other hand, programming is much more closely analogous to designing an automobile than it is to driving one; therefore, we’re going to have to go into some detail about the internal workings of a computer, not at the level of electronic components, but at the lowest level important to a Java programmer. This is a book on learning to program in Java, not on how a computer works. Therefore, it might seem better to start there and eliminate this detour, and indeed many (perhaps most) books on Java do exactly that. However, in working out in detail how I’m going to explain Java to you, I’ v e c o m e t o t h e c o n c l u s i o n t h a t i t w o u l d b e virtually impossible to explain why certain features of the language exist and how they actually work, without your understanding how they relate to the underlying computer hardware. I haven’t come to this position by pure logical deduction, either. In fact, I’ve worked backward from the concepts that you will need to know to program in Java to the specific underlying information that you will have to understand first. I’m thinking in particular of one specific concept, the r e f e r e n c e, w h i c h i s s u p p o s e d t o b e e x t r e m e l y difficult for a beginning programmer in Java to grasp. With the approach we’re taking, you shouldn’t have much trouble understanding this concept by the time you get to it in Chapter 6; it’s noted as such in the discussion there. I’d be interested to know how 9 10 Chapter 2: HARDWARE FUNDAMENTALS you find my explanation there, given the background that you’ll have by that point; don’t hesitate to e-mail me about this topic (or any other, for that matter). On the other hand, if you’re an experienced programmer, a lot of this will be just review for you. Nonetheless, it can’t hurt to go over the basics one more time before diving into the ideas and techniques that make Java different from other languages. Now let’s begin with some definitions and objectives for this chapter. Definitions A digit is one of the characters used in any positional number system to represent all numbers starting at 0 and ending at one less than the base of the number system. In the decimal system, there are ten digits, 0–9, and in the hexadecimal system there are sixteen digits, 0–9 and a–f. A binary number system is one that uses only two digits, 0 and 1. Problem Algorithms A hexadecimal number system is one that uses 16 digits, 0–9 and Java a–f. Interpreter Hardware A variable is a programming construct that represents a specific item of data that we wish to keep track of in a program. Some examples are the weight of a pumpkin or the number of cartons of milk in the inventory of a store. Objectives for This Chapter By the end of this chapter, you should: 1. 2. 3. 4. Understand the programmer’s view of the most important pieces of hardware in your computer. Understand the programmer’s view of the most important pieces of software in your computer. Be able to solve simple problems using both the binary and hexadecimal number systems. Understand how whole numbers are stored in the computer. BEHIND THE CURTAIN 11 Behind the Curtain First we’ll need to expand on the definition of hardware. As noted earlier, hardware means the physical components of a computer, the o n e s y o u c a n t o u c h .1 E x a m p l e s a r e t h e m o n i t o r , t h e k e y b o a r d , t h e printer, and all of the interesting electronic and electromechanical components inside the case of your computer.2 Right now we’re concerned with the Java programmer’s view of the hardware. The hardware components of a computer with which you’ll be primarily concerned are the disk, RAM (short for Random Access Memory), and the CPU (short for Central Processing Unit).3 We’ll take up each of these topics in turn. Disk When you sit down at your computer in the morning, before you turn it on, where are the programs you’re going to run? To make this more specific, suppose you’re going to use a word processor to revise a letter you wrote yesterday before you turned the computer off. Where is the letter, and where is the word processing program? You probably know the answer to this question: They are stored on a disk inside the case of your computer.4 Disks use magnetic recording media, much like the material used to record speech and music on cassette tapes, to store information in a way that will not be lost when the power is turned off. How exactly is this information ______________________________________________________________________________ 1. Whenever I refer to a computer, I mean a modern microcomputer capable of running MSDOS; these are commonly referred to as PCs. Most of the fundamental concepts are the same in other kinds of computers, but the details differ. 2. Although it’s entirely possible to program without ever seeing the inside of a computer, you might want to look in there anyway, just to see what the CPU, RAM chips, disk drives, etc., look like. Some familiarization with the components would give you a head start if you ever want to expand the capacity of your machine. 3. Other hardware components can be important to programmers of specialized applications; for example, game programmers need extremely fine control on how information is displayed on the monitor. However, we have enough to keep us busy learning how to write general datahandling programs; you can always learn how to write games later, if you’re interested in doing so. 4. Technically, this is a hard disk, to differentiate it from a floppy disk, the removable storage medium often used to distribute software or transfer files from one computer to another. Although at one time many small computers used floppy disks to store data, the tremendous decrease in hard disk prices means that today even the most inexpensive computer stores programs and data on a hard disk. Problem Algorithms Java Interpreter Hardware 12 Chapter 2: HARDWARE FUNDAMENTALS (which may be either executable programs or data such as word processing documents) stored? We don’t have to go into excruciating detail on the storage mechanism, but it is important to understand some of its characteristics. A disk consists of one or more circular platters, which are extremely flat and smooth pieces of metal or glass covered with a material that can be very rapidly and accurately magnetized in either of two directions, "north" and "south". To store large amounts of data, each platter is divided into many millions of small regions, each of which can be magnetized in either direction independent of the other regions. The magnetization is detected and modified by r e c o r d i n g h e a d s, s i m i l a r i n p r i n c i p l e t o t h o s e u s e d i n t a p e c a s s e t t e decks. However, in contrast to the cassette heads, which make contact with the tape while they are recording or playing back music or speech, the disk heads "fly" a few millionths of an inch away from the platters, which rotate at very high velocity.5 The separately magnetizable regions used to store information are arranged in groups called sectors, which are in turn arranged in concentric circles called tracks. All tracks on one side of a given platter (a recording surface) can be accessed by a recording head dedicated to that recording surface; each sector is used to store some Problem Algorithms n u m b e r o f b y t e s o f t h e d a t a , g e n e r a l l y a f e w h u n d r e d t o a f e w thousand. "Byte" is a coined word meaning a group of 8 binary Java 6 Interpreter digits, or bits for short. You may wonder why the data aren’t stored in the more familiar decimal system, which of course uses the digits Hardware from 0 through 9. This is not an arbitrary decision; on the contrary, there are a couple of very good reasons that data on a disk are stored using the binary system, in which each digit has only two possible states, 0 and 1. One of these reasons is that it’s a lot easier to determine reliably whether a particular area on a disk is magnetized "north" or "south" than it is to determine 1 of 10 possible levels of magnetization. Another reason is that the binary system is also the natural system for data storage using electronic circuitry, which is used to store data in the rest of the computer. ______________________________________________________________________________ 5. The heads have to be as close as possible to the platters because the influence of a magnet (called the magnetic field) drops off very rapidly with distance. Thus, the closer the heads are, the more powerful the magnetic field is and the smaller the region that can be used to read and write data reliably. Of course, this leaves open the question of why the heads aren’t in c o n t a c t w i t h t h e s u r f a c e ; t h a t w o u l d c e r t a i n l y s o l v e t h e p r o b l e m o f b e i n g t o o f a r a w ay . Unfortunately, this seemingly simple solution would not work at all. There is a name for the contact of heads and disk surface while the disk is spinning: head crash. The friction caused by such an event destroys both the heads and disk surface almost instantly. 6. In some old machines, bytes sometimes contained more or less than 8 bits, but the 8-bit byte is virtually universal today. DISK 13 Although magnetic storage devices have been around in one form or another since the very early days of computing, the advances in technology just in the last dozen years have been staggering. To comprehend just how large these advances have been, we need to define the term used to describe storage capacities: the Megabyte. The standard engineering meaning of Mega is "multiply by one million", which would make a Megabyte equal to one million (1,000,000) bytes. As we have just seen, however, the natural number system in the computer field is binary. Therefore, "one Megabyte" is often used instead to specify the nearest "round" number in the binary system, which is 2^20 (2 to the 20th power), or 1 , 0 4 8 , 5 7 6 b y t e s .7 T h i s w a s n ’ t o b v i o u s t o S u s a n , s o I e x p l a i n e d i t some more, as you can see here: Susan: Just how important is it to really understand that the Megabyte is 2^20 (1,048,576) bytes? I know that a meg is not really a meg; that is, it’s more than a million. But I don’t understand 2^20, so is it enough to just take your word on this and not get bogged down as to why I didn’t go any further than plane geometry in high school? You see, it makes me worry and upsets me that I don’t understand how you "round" a binary number. Steve: The ^ symbol is a common way of saying "to the power of", so 2^20 would be 2 to the power of 20; that is, twenty 2s multiplied together. This is a "round" number in binary just as 10 * 10 * 10 (1000) is a "round" number in decimal. 1985, a Space Odyssey With that detail out of the way, we can see just how far we’ve come in a short period of time. In 1985, I purchased a 20 Megabyte disk for $900 ($45 per Megabyte); its access time, which measures how long it takes to retrieve data, was approximately 100 milliseconds (milli = 1/1000, so a millisecond is one thousandth of a second). In April 1997, a 6510 Megabyte disk cost as little as $449, or approximately 7 cents per Megabyte; in addition to delivering 650 times as much storage per dollar, this disk had an access time of 14 milliseconds, which is approximately 7 times as fast as the old disk. Of course, this ______________________________________________________________________________ 7. In case you’re not familiar with the ^ notation, the number on its right indicates how many copies of the number to the left have to be multiplied together to produce the final result. For example, 2^5 = 2 * 2 * 2 * 2 * 2, whereas 4^3 = 4 * 4 * 4. Of course, I’ve just introduced another symbol you might not be familiar with: the * is used to indicate multiplication in programming. Problem Algorithms Java Interpreter Hardware 14 Chapter 2: HARDWARE FUNDAMENTALS significantly understates the amount of progress in technology in both economic and technical terms. For one thing, a 1997 dollar is worth considerably less than a 1985 dollar. In addition, the new drive is superior in every other measure as well: It is much smaller than the old one, consumes much less power, and has many times the projected reliability of the old drive. This tremendous increase in performance and price has prevented the long-predicted demise of disk drives in favor of new technology. However, the inherent speed limitations of disks still require us to restrict their role to the storage and retrieval of data for which we can afford to wait a relatively long time. You see, while 14 milliseconds isn’t very long by human standards, it is a long time indeed to a modern computer. This will become more evident as we examine the next essential component of the computer, the RAM. RAM Problem Algorithms Java Interpreter Hardware The working storage of the computer, where data and programs are stored while we’re using them, is called RAM, which is an acronym for Random Access Memory.8 For example, your word processor is stored in RAM while you’re using it. The document you’re working on is likely to be there as well unless it’s too large to fit all at once, in which case parts of it will be retrieved from the disk as needed. Since we have already seen that both the word processor and the document are stored on the disk in the first place, why not leave them there and use them in place, rather than copying them into RAM? The answer, in a word, is speed. RAM is physically composed of millions of microscopic switches on a small piece of silicon known as a chip: a 4 Megabit RAM chip has approximately four million of them.9 Each of these switches can be either on or off; we consider a switch that is "on" to be storing a 1, and a switch that is "off" to be storing a 0. Just as in storing information on a disk, where it was easier to magnetize a region in either of two directions, it’s a lot easier to make a switch that can be turned on or off reliably and quickly than one that can be set to any value from 0 to 9 reliably and ______________________________________________________________________________ 8. RAM is sometimes called "internal storage", as opposed to "external storage", that is, the disk. 9. Each switch is made of several transistors. Unfortunately, an explanation of how a transistor works would take us too far afield. Consult any good encyclopedia, such as the Encyclopedia Britannica, for this explanation. RAM 15 q u i c k l y . T h i s i s p a r t i c u l a r l y i m p o r t a n t w h e n y o u’re manufacturing millions of them on a silicon chip the size of your fingernail. A main difference between disk and RAM is what steps are needed to access different areas of storage. In the case of the disk, the head has to be moved to the right track (an operation known as a seek), and then we have to wait for the platter to spin so that the region we want to access is under the head (called rotational delay). On the other hand, with RAM, the entire process is electronic; we can read or write any byte immediately, as long as we know which byte we want. To specify a given byte, we have to supply a unique number called its memory address or just address for short. Return to Sender, Address Unknown What is an address good for? Let’s see how my discussion with Susan on this topic started. Susan: About memory addresses: Are you saying that each little itty bitty tiny byte of RAM is a separate address? Well, this is a little hard to imagine. Steve: Actually, each byte of RAM has a separate address, which doesn’t change, and a value, which does. In case the notion of an address of a byte of memory on a piece of silicon is too abstract, it might help to think of an address as a set of directions as to how to find the byte being addressed, much like directions to someone’s house. For example, "Go three streets down, then turn left. It’s the second house on the right". With such directions, the house number wouldn’t need to be written on the house. Similarly, the memory storage areas in RAM are addressed by position; you can think of the address as telling the hardware which street and house you want, by giving directions similar in concept to t h e p r e c e d i n g e x a m p l e . T h e r e f o r e , i t’ s n o t n e c e s s a r y t o e n c o d e t h e addresses into the RAM explicitly. Susan wanted a better picture of this somewhat abstract idea. Susan: Where are the bytes on the RAM, and what do they look like? Steve: Each byte corresponds to a microscopic region of the RAM chip. As to what they look like, have you ever seen a printed circuit board such as the ones inside your computer? Imagine the lines on that circuit Problem Algorithms Java Interpreter Hardware 16 Chapter 2: HARDWARE FUNDAMENTALS board reduced thousands of times in size to microscopic dimensions, and you’ll have an idea of what a RAM chip looks like inside. Since it has no moving parts, storing and retrieving data in RAM is much faster than waiting for the mechanical motion of a disk platter turning.10 As we’ve just seen, disk access times are measured in milliseconds, or thousandths of a second. RAM access times, however, are measured in nanoseconds (abbreviated ns); nano means one billionth. In early 1997, a typical speed for RAM was 70 ns, which means that it is possible to read a given data item from RAM about 200,000 times as quickly as from a disk with an access time of 14 milliseconds. In that case, why not use disks only for permanent storage, and read everything into RAM in the morning when we turn on the machine? The reason is cost. In early 1997, the cost of 16 Megabytes of RAM was approximately $80. For that same amount of money, you could have bought over 1100 Megabytes of disk space!11! Therefore, we must reserve RAM for tasks where speed is all-important, such as running your word processing program and holding a letter while you’re working on it. Also, since RAM is an electronic storage medium (rather than a magnetic one), it does not maintain its Problem Algorithms contents when the power is turned off. This means that if you had a power failure while working with data only in RAM, you would lose Java 12 Interpreter everything you had been doing. This is not merely a theoretical problem, by the way; if you don’ t r e m e m b e r t o s a v e w h a t y o u’re Hardware doing in your word processor once in a while, you might lose a whole day’s work from a power outage of a few seconds.13 ______________________________________________________________________________ 10. There’s also another kind of electronic storage, called ROM, for Read-Only Memory; as its name indicates, you can read from it, but you can’t write to it. This is used for storing permanent information, such as the program that allows your computer to read a small program from your boot disk; that program, in turn, reads in the rest of the data and programs needed to start up the computer. This process, as you probably know, is called booting the computer. In case you’re wondering where that term came from, it’s an abbreviation for bootstrapping, which is intended to suggest the notion of pulling yourself up by your bootstraps. You may have noticed that the terms RAM and ROM aren’t symmetrical; why isn’t RAM called RWM, Read-Write Memory? Because that’s too hard to pronounce. 11. To be sure, an 1100 Megabyte disk cost more than $80 in April 1997, but 1100 Megabytes of space on the 6510 Megabyte drive I mentioned earlier would represent a little less than $80 of its total price of $449. 12. The same disaster would happen if your system were to crash, which is not that unlikely if you’re using certain popular PC graphically oriented operating environments whose names start with "W". 13. Most modern word processors can automatically save your work once in a while, for this very reason. I heartily recommend using this facility; it’s saved my bacon more than once. RETURN TO SENDER, ADDRESS UNKNOWN 17 Before we get to how a program actually works, we need to develop a better picture of how RAM is used. As I’ve mentioned before, you can think of RAM as consisting of a large number of bytes, each of which has a unique identifier called an address. This address can be used to specify which byte we mean, so the program might specify that it wants to read the value in byte 148257, or change the value in byte 66666. Susan wanted to make sure she had the correct understanding of this topic. Susan: Are the values changed in RAM depending on what program is loaded in it? Steve: Yes, and they also change while the program is executing. RAM is used to store both the program itself and the values it manipulates. This is all very well, but it doesn’t answer the question of how we actually use the data in RAM. The answer is by means of variables, which represent specific items of data that we wish to keep track of in our programs, such as weights and numbers of items. You can put something in a variable, and it will stay there until you store something else there; you can also look at it to find out what’s in it. As you might expect, several types of variables are used to hold different kinds of data; the first ones we will look at are variables representing whole numbers (the so-called integer variables), which are a subset of the category called numeric variables . A s t h i s s u g g e s t s , t h e r e a r e a l s o v a r i a b l e s t h a t r e p r e s e n t numbers that can have fractional parts. We’ll look at these so-called floating-point variables briefly in a later chapter. Different types of variables require different amounts of RAM to store them, depending on the amount of data they contain; a very common type of numeric variable, known as a short, requires 16 bits (that is, 2 bytes) of RAM to hold any of 65536 different values, from –32768 to 32767, including 0. These odd-looking numbers are the result of using the binary system. To make this number system more intelligible, I have written the following little fable. Odometer Trouble Once upon a time, the Acme company had a factory that made golf carts. One day, Bob, the president of Acme, decided to add an odometer to the carts, so that the purchaser of the cart could estimate Problem Algorithms Java Interpreter Hardware 18 Chapter 2: HARDWARE FUNDAMENTALS when to recharge the battery. To save money, Bob decided to buy the little numbered wheels for the odometers and have his employees put the odometers together. The minimum order was a thousand odometer wheels, which was more than he needed for his initial run of 50 odometers. When he got the wheels, however, he noticed that they were defective: Instead of the numbers 0–9, each wheel had only two numbers, 0 and 1. Of course, he was quite irritated by this error, and attempted to contact the company from which he had purchased the wheels, but it had closed down for a month for summer vacation. What was he to do until it reopened? While he was fretting about this problem, the employee who had been assigned to the task of putting the odometers together from the wheels came up with a possible solution. This employee, Jim, came into Bob’s office and said, "Bob, I have an idea. Since we have lots of orders for these odometer-equipped carts, maybe we can make an odometer with these funny wheels and tell the customers how to read the numbers on the odometer." Bob was taken aback by this idea. "What do you mean, Jim? How can anyone read those screwy odometers?" Jim had given this some thought. "Let’s take a look at what one of these odometers, say with five wheels, can display. Obviously, it Problem Algorithms would start out reading 00000, just like a normal odometer. Then when one mile has elapsed, the right-most wheel turns to 1, so the Java Interpreter whole display is 00001; again, this is no different from a normal odometer." Hardware "Now we come to the tricky part. The right-most wheel goes back to 0, not having any more numbers to display, and pushes the ‘tens’ wheel to 1; the whole number now reads 00010. Obviously, one more mile makes it 00011, which gives us the situation shown in the following diagram: Normal odometer 00000 00001 00002 00003 Funny odometer 00000 00001 00010 00011 Figure 2.1: The first few numbers Jim continued, "What’s next? This time, the right-most wheel turns over again to 0, triggering the second wheel to its next position. At this point, however, the second wheel is already at its highest value, 1; therefore, it also turns over to 0 and increments the third ODOMETER TROUBLE 19 wheel. It’s not hard to follow this for a few more miles, as illustrated in Figure 2.2. Normal odometer 00004 00005 00006 00007 Funny odometer 00100 00101 00110 00111 Figure 2.2: The next few numbers Bob said, "I get it. It’s almost as though we were counting normally, except that you skip all the numbers that have anything but 0s or 1s in them." "That’s right, Bob. So I suppose we could make up a list of the ‘real’ numbers and give it to the customers to use until we can replace these odometers with normal ones. Perhaps they’ll be willing to work with us on this problem." "Okay, Jim, if you think they’ll buy it. Let’s get a few of the customers we know the best and ask them if they’ll try it; we won’t charge them for the odometers until we have the real ones, but maybe they’ll stick with us until then. Perhaps any odometer would be better than no odometer at all." Jim went to work, making some odometers out of the defective wheels; however, he soon figured out that he had to use more than five wheels, because that allowed only numbers from 0 to 31. How did he know this? Each wheel has two numbers, 0 and 1. So with one wheel, we have a total of two combinations. Two wheels can have either a 0 or a 1 for the first number, and the same for the second number, for a total of four combinations. With three wheels, the same analysis holds: 2 numbers for the first wheel * 2 for the second wheel * 2 for the third wheel = 8 possibilities in all; actually, they are the same 8 possibilities we saw in Figures 2.1 and 2.2. A pattern is beginning to develop: For each added wheel, we get twice as many possible combinations. To see how this continues, take a look at Figure 2.3, which shows the count of combinations versus the number of wheels for all wheel counts up to 16 (i.e., 16-bit quantities). Problem Algorithms Java Interpreter Hardware 20 Chapter 2: HARDWARE FUNDAMENTALS Number of wheels 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Number of combinations14 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 Figure 2.3: How many combinations? Jim decided that 14 wheels would do the job, since the life span of the golf cart probably wouldn’t exceed 16,383 miles, and so he made up the odometers. The selected customers turned out to be agreeable Problem Algorithms and soon found that having even a weird odometer was better than none, especially since they didn’t have to pay for it. However, one Java c Interpreter ustomer did have a complaint: The numbers on the wheels didn’t seem to make sense when translated with the chart supplied by Hardware Acme. The customer estimated that he had driven the cart about 9 miles, but the odometer displayed the following number: 11111111110111 which, according to his translation chart, was 16,375 miles. What could have gone wrong? Jim decided to have the cart brought in for a checkup, and what he discovered was that the odometer cable had been hooked up backwards. That is, instead of turning the wheels forward, they were going backwards. That was part of the solution, but why was the value 16375? Just like a car odometer, in which 99999 (or 999999, if you have a six-wheel odometer) is followed by 0, going backwards from 0 reverses that progression. Similarly, the number 11111111111111 on ______________________________________________________________________________ 14. If you think that last number looks familiar, you’re right: It’s the number of different values that I said could be stored in a type of numeric variable called a short. This is no coincidence; read on for the detailed explanation. ODOMETER TROUBLE 21 the funny odometers would be followed by 00000000000000, since the "carry" off the left-most digit is lost. Therefore, if you start out at 0 and go backward one mile, you’ll get 11111111111111 The next mile will turn the last digit back to 0, producing 11111111111110 What happens next? The last wheel turns back to 1, and triggers the second wheel to switch as well: 11111111111101 The next few "backward" numbers look like this: 11111111111100 11111111111011 11111111111010 11111111111001 11111111111000 11111111110111 and so on. If you look at the right-hand end of these numbers, you’ll see that the progression is just the opposite of the "forward" numbers. As for the customer’s actual mileage, the last one of these is the number the customer saw on his backward odometer. Apparently, he was right about the distance driven, since this is the ninth "backward" number. So Jim fixed the backward odometer cable and reset the value to the correct number, 00000000001001, or nine miles. Eventually, Acme got the right odometer wheels with 0–9 on them, replaced the peculiar ones, and everyone lived happily ever after. THE END Back to the Future Of course, the wheels that made up the funny odometers contain only two digits, 0 and 1, so the odometers use the binary system for counting. Now it should be obvious why we will see numbers like Problem Algorithms Java Interpreter Hardware 22 Chapter 2: HARDWARE FUNDAMENTALS 65536 and 32768 in our discussions of the number of possible different values that a variable can hold: Variables are stored in RAM as collections of bytes, each of which contains 8 bits. As the list of combinations indicates, 8 bits (1 byte) provide 256 different combinations, while 16 bits (2 bytes) can represent 65,536 different possible values. But what about the "backward" numbers with a lot of 1s on the left? As the fable suggests, they correspond to "negative" numbers. That is, if moving two miles forward from 0 registers as 00000000000010, and moving two miles backward from 0 registers as 11111111111110, then the latter number is in some sense equivalent to –2 miles. In Java, all integer variables can store either positive or negative values, and this "backward" representation is the way that negative integers are represented in a Java variable. Over-Hexed You may have noticed that it’s tedious and error prone to represent numbers in binary; a long string of 0s and 1s is hard to remember or to copy. For this reason, the pure binary system is hardly ever used to Problem Algorithms specify numbers in computing. However, we have already seen that binary is much more "natural" for computers than the more familiar Java Interpreter decimal system. Is there a number system that we humans can use a little more easily than binary, while retaining the advantages of Hardware binary for describing internal events in the computer? As it happens, there is. It’s called hexadecimal, which means "base 16". As a rule, the term hexadecimal i s a b b r e v i a t e d t o h e x . Since there are 16 possible combinations of 4 bits (2*2*2*2), hexadecimal notation allows 4 bits of a binary number to be represented by one hex digit. Unfortunately, however, there are only 10 "normal" digits, 0 – 9 . 1 5 T o r e p r e s e n t a n u m b e r i n a n y b a s e , y o u need as many different digit values as the base, so that any number less than the base can be represented by one digit. For example, in base 2, you need only two digits, 0 and 1. In base 8 (octal), you need eight digits, 0–7.16 So far, so good. But what about base 16? To use this base, we need 16 digits. Since only 10 numeric digits are available, hex notation needs a source for the other six digits. ______________________________________________________________________________ 15. Paging Dr. Seuss. . . 16. In the early days of computing, base 8 was sometimes used instead of base 16, especially on machines that used 12-bit and 36-bit registers; however, it has fallen into disuse because almost all modern machines have 32-bit registers. OVER-HEXED 23 Because letters of the alphabet are available and familiar, the first six letters, a–f, were adopted for this service.17 Although the notion of a base 16 number system doesn’t seem strange to people who are familiar with it, it can really throw someone who learned normal decimal arithmetic solely by rote, without understanding the concepts on which it is based. This topic of hexadecimal notation occupied Susan and me for quite awhile; here’s some of the discussion we had about it: Susan: I don’t get this at all! What is the deal with the letters in the hex system? I guess it would be okay if 16 wasn’t represented by 10! S t e v e : W e l l , t h e r e a r e o n l y 1 0 " n o r m a l " d i g i t s , 0–9. To represent a number in any base, you need as many "digits" as the base, so that any number less than the base can be represented by one "digit". This is no problem with a base less than ten, such as octal, but what about base 16? To use this base we need 16 digits, 0–9 and a–f. One way to remember this is to imagine that the "hex" in "hexadecimal" stands for the six letters a–f and the "decimal" stands for the 10 digits 0–9. Susan: OK, so a hex digit represents 16 bits? So then is hex equal to 2 bytes? According to the preceding, a hex digit is 4 bits. Steve: Yes, a hex digit represents 4 bits. Let’s try a new approach. First, let me define a new term, a hexit. That’s short for "hex digit", just like "bit" is short for "binary digit". Now let’s look at the answers to the following questions. 1. 2. 3. 4. 5. 6. 7. How many numbers can be represented with no more than one decimal digit? How many numbers can be represented with no more than two decimal digits? How many numbers can be represented with no more than three decimal digits? How many numbers can be represented with no more than four decimal digits? How many numbers can be represented with no more than one bit? How many numbers can be represented with no more than two bits? How many numbers can be represented with no more than three bits? ______________________________________________________________________________ 17. Either upper or lower case letters are acceptable to most programs (and programmers). I’ll use lower case because such letters are easier to distinguish than upper case ones; besides, I find them less irritating to look at. Problem Algorithms Java Interpreter Hardware 24 Chapter 2: HARDWARE FUNDAMENTALS 8. 9. 10. 11. 12. How many bits? How many hexit? How many hexits? How many hexits? How many hexits? numbers can be represented with no more than four numbers can be represented with no more than one numbers can be represented with no more than two numbers can be represented with no more than three numbers can be represented with no more than four The answers are: Problem Algorithms Java Interpreter Hardware 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 10 100 1000 10000 2 4 8 16 16 256 4096 65536 What do all these answers have in common? Let’s look at the answers a little differently, in powers of 10, 2, and 16, respectively: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 10 = 10^1 100 = 10^2 1000 = 10^3 10000 = 10^4 2 = 2^1 4 = 2^2 8 = 2^3 16 = 2^4 16 = 16^1 256 = 16^2 4096 = 16^3 65536 = 16^4 That is, a number that has one digit can represent "base" different values, where "base" is two, ten, or sixteen (in our examples). Every time we increase the size of the number by one more digit, we can represent "base" times as many possible different values, or in other words, we multiply the range of values that the number can represent by OVER-HEXED 25 the base. Thus, a two-digit number can represent any of "base*base" values, a three-digit number can represent any of "base*base*base" values, and so on. That’s the way positional number systems such as decimal, binary, and hex work. If you need a bigger number, you just add more digits. Okay, so what does this have to do with hex? If you look at the above table, you’ll see that 2^4 (16) is equal to 16^1. That means that 4 bits are exactly equivalent to one hexit in their ability to represent different numbers: Exactly 16 possible numbers can be represented by four bits, and exactly 16 possible numbers can be represented by one hexit. This means that you can write one hexit wherever you would otherwise have to use four bits, as illustrated in Figure 2.4. 4-bit value 1-hexit value 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 0 1 2 3 4 5 6 7 8 9 a b c d e f Figure 2.4: Binary to hex conversion table So an 8-bit number, such as: 0101 1011 can be translated directly into a hex value, like this: 5 b For this reason, binary is almost never used. Instead, we use hex as a shortcut to eliminate the necessity of reading, writing, and remembering long strings of bits. Susan: A hex digit or hexit is like a four-wheel odometer in binary. Since each wheel is capable of only one of two values, being either (1) or (0), then the total number of possible values is 16. Thus your 2*2*2*2 = 16. I think I’ve got this down. Steve: You certainly do! Problem Algorithms Java Interpreter Hardware 26 Chapter 2: HARDWARE FUNDAMENTALS Susan: If it has 4 bits and you have 2 of them, then won’t there be eight "wheels" and so forth? So 2 hex would hold XXXXXXXX places and 3 hex would hold XXXXXXXXXXXX places. Steve: Correct. A one-hexit number is analogous to a one-digit decimal number. A one-hexit number contains 4 bits and therefore can represent any of 16 values. A two-hexit number contains 8 bits and therefore can represent any of 256 values. Problem Algorithms Java Interpreter Hardware Now that we’ve seen how each hex digit corresponds exactly to a group of four binary digits, here’s an exercise you can use to improve your understanding of this topic: Invent a random string of four binary digits and see where it is in Figure 2.4. I guarantee it’ll be there somewhere! Then look at the "hex" column and see what "digit" it corresponds to. There’s nothing really mysterious about hex; since we have run out of digits after 9, we have to use letters to represent the numbers ‘ten’, ‘eleven’, ‘twelve’, ‘thirteen’, ‘fourteen’, and ‘fifteen’. Here’s a table showing the correspondence between some decimal, hex, and binary numbers, with the values of each digit position in each number base indicated, and the calculation of the total of all of the bit values in the binary representation, as shown in Figure 2.5. Decimal Place Values 10 1 1 1 1 1 1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 Hexadecimal Place Values 16 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 Binary Place Values 16 8 4 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 Sum of Binary Digit Values = = = = = = = = = = = = = = = = = = = = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 16 16 16 16 + + + + + + + + + + + + + + + + + + + + 0 0 0 0 0 0 0 0 8 8 8 8 8 8 8 8 0 0 0 0 + + + + + + + + + + + + + + + + + + + + 0 0 0 0 4 4 4 4 0 0 0 0 4 4 4 4 0 0 0 0 + + + + + + + + + + + + + + + + + + + + 0 0 2 2 0 0 2 2 0 0 2 2 0 0 2 2 0 0 2 2 + + + + + + + + + + + + + + + + + + + + Figure 2.5: Different representations of the same numbers 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 OVER-HEXED 27 Another reason to use hex rather than decimal is that byte values expressed as hex digits can be combined directly to produce larger values, which is not true with decimal digits. In case this isn’t o b v i o u s , l e t ’ s g o o v e r i t i n m o r e d e t a i l . S i n c e e a c h h e x d i g i t ( 0– f ) represents exactly 4 bits, two of them (00–ff) represent 8 bits, or one byte. Similarly, 4 hex digits (0000–ffff) represent 16 bits, or a short value; the first two digits represent the first byte of the 2-byte value, and the last two digits, the second byte. This can be extended to any number of bytes. On the other hand, representing 4 bits requires two decimal digits, as the values range from 00–15, whereas it takes three digits (000–255) to represent one byte. A 2-byte value requires five decimal digits, since the value can be from 00000 to 65535. As you can see, there’s no simple relationship between the decimal digits representing each byte and the decimal representation of a 2-byte value. Susan had some more thoughts on the hexadecimal number system. Let’s listen in. Susan: I think you need to spend a little more time reviewing the hex system, like an entire chapter.<G> Well, I am getting the impression that we are going to be working with hex, so I am trying to concentrate my understanding on that instead of binary. I think this all moves a little too fast for me. I don’t know what your other reviewers are saying but I just feel like I get a definition of a abstract concept, and the next thing I know I am supposed to be doing something with it, like make it work. Ha! I personally need to digest new concepts, I really need to think them over a bit, to take them in and absorb them. I just can’t start working with it right away. As usual, I’ve complied with her request; the results are immediately ahead. Exercises Here are some exercises that you can use to check your understanding of the binary and hexadecimal number systems.18 I’ve limited the examples to addition and subtraction, as that is all that you’re ever likely to have to do in these number systems. These operations are exactly like their equivalents in the decimal system, except that as we have already seen, the hexadecimal system has six ______________________________________________________________________________ 18. Please note that the ability to do binary or hexadecimal arithmetic, although valuable in a number of applications, is not essential to further reading in this book. Problem Algorithms Java Interpreter Hardware 28 Chapter 2: HARDWARE FUNDAMENTALS extra digits after 9: a, b, c, d, e, and f. We have to take these into account in our calculations: for example, adding 9 and 5, rather than producing 14, produces e. 1. 2. Using the hexadecimal system, answer these problems: a. 1a + 2e = ? b. 12 + 18 = ? c. 50 – 12 = ? In the binary system, answer these problems: a. 101 + 110 = ? b. 111 + 1001 = ? c. 1010 – 11 = ? Let’s suppose that x is a short, currently holding the value 32767, or 7fff in hex. What is the result of adding 1 to x, in both decimal and hex? Answers to exercises can be found at the end of the chapter. Problem Algorithms Java Interpreter Hardware The CPU There’s one point we haven’t covered yet: how these variables are actually referred to and modified. The ultimate answer to that question is that every action in the computer is performed by another piece of hardware: the CPU (Central Processing Unit). Like RAM, it is physically composed of millions of microscopic transistors on a chip; however, the organization of these transistors in a CPU is much more complex than on a RAM chip, as the latter’s functions are limited to the storage and retrieval of data. The CPU, on the other hand, is capable of performing dozens or hundreds of different fundamental operations called machine instructions, or just instructions for short. While each instruction performs a very simple function, the tremendous power of the computer lies in the fact that the CPU can perform (or e x e c u t e) tens or hundreds of millions of these instructions per second.19 ______________________________________________________________________________ 19. Each type of CPU has a different set of instructions, so that programs intended for one CPU cannot in general be run on a different CPU. Some CPUs, such as the very popular 80x86 ones from Intel, fall into a "family" of CPUs in which each new CPU can execute all of the instructions of the previous family members. This allows upgrading to a new CPU without having to throw out all of your old programs, but correspondingly limits the ways in which the new CPU can be improved without affecting this "family compatibility". THE CPU 29 These instructions fall into a number of categories: instructions that perform arithmetic operations such as adding, subtracting, multiplying, and dividing; instructions that move information from one place to another in RAM; instructions that compare two quantities to help make a determination as to which instructions need to be executed next and instructions that implement that decision; and other, more specialized types of instructions. With most languages, the programs you write are translated into machine instructions for a specific CPU. However, one of the design goals of Java was to allow the same translated program to work on any machine. To facilitate this task, the process of making your programs runnable works differently in Java: Instead of your programs being translated into a form designed to be executed on a specific CPU, they are translated into a form that can be executed on any CPU with the intervention of a special program called an interpreter. We’ll go into how this works in great detail in the next chapter; for now, just keep in mind that Java programs aren’t tied to a specific type of computer. You’ve just been subjected to a barrage of information on how a computer works. Let’s go over it again briefly before continuing. Review Two main components of the computer hardware are of most significance to Java programmers: disk and RAM. Both of these store programs and data for use by Java programs. Computers represent pieces of information (or data) as binary digits, universally referred to as bits. Each bit can have the value 0 or 1. The binary system is used instead of the more familiar decimal system because it is much easier to make devices that can store and retrieve 1 of 2 values than 1 of 10. Bits are grouped into sets of eight, called bytes. The disk uses magnetic recording heads to store and retrieve groups of a few hundred bytes on rapidly spinning platters in a few milliseconds. The contents of the disk are not lost when the power is turned off, so it is suitable for more or less permanent storage of programs and data. RAM, which is an acronym for Random Access Memory, is used to hold programs and data while they’re in use. It is made of millions of microscopic transistors on a piece of silicon called a chip. Each bit is stored using a few of these transistors. RAM does not retain its contents when power is removed, so it is not good for permanent Problem Algorithms Java Interpreter Hardware 30 Chapter 2: HARDWARE FUNDAMENTALS storage. However, any byte in a RAM chip can be accessed in about 70 nanoseconds (billionths of a second), which is hundreds of thousands of times as fast as accessing a disk. Each byte in a RAM chip can be independently stored and retrieved without affecting other bytes, by providing the unique memory address belonging to the byte you want to access. The binary system is the most fundamental number system in the computer, but it is not very convenient for people, especially when dealing with large numbers. For this purpose, the hexadecimal number system is considerably more convenient, as the representations of large numbers are much shorter and easier to remember. Unlike most other computer languages, Java was designed to allow the same program to be run on various kinds of computers, with the intervention of a special kind of program called an interpreter. Conclusion Problem Algorithms Java Interpreter Hardware In this chapter, we’ve covered a lot of material on how a computer actually works. As you’ll see, this background is essential if you’re going to understand what really happens inside a program. In the next chapter, we’ll get to the "real thing": how to write a program to make all this hardware do something useful. Answers to Exercises 1. Hexadecimal arithmetic a. 48 You probably won’t be surprised to hear that Susan didn’t care much for this answer originally. Here’s the discussion on that topic: Susan: Problem 1a. My answer is 38. Why? My own personal way of thinking: If a = 10, right? and if e = 14 and if 1 * 10 = 10 and if 2 * 14 = 28, then if you add 10 + 28 you get 38. So please inform me how you arrived at 48? I didn’t bother with the rest of the problems. If I couldn’t get the first one right, then what was the point? Steve: Here’s how you do this problem, with the letter "h" indicating a hex number: 10h(1 * 16) + ah(10 * 1) ANSWERS TO EXERCISES 31 20h(2 * 16) + eh(14 * 1) -----------------30h(3 * 16) + 18h(24 * 1 = 1 * 16 + 8 * 1) Carry the 1 from the low digit to the high digit of the answer, to produce: 40h(4 * 16) + 8h(8 * 1), or 48 hex, which is the answer. 2. 3. b. 2a c. 3e Binary arithmetic a. 1011 b. 10000 c. 111 –32768, or 8000 in hex In case you got this wrong, you should remember that the range of short values is –32768 to +32767, with 0000h to 7fffh being considered positive, and 8000h to ffffh considered negative. Problem Algorithms Java Interpreter Hardware Chapter 3 Basics of Programming Creative Programming? After that necessary detour into the workings of the hardware, we can now resume our regularly scheduled explanation of the creative possibilities of computers. It may sound odd to describe computers as providing grand scope for creative activities: Aren’t they monotonous, dull, unintelligent, and extremely limited? Yes, they are. However, they have two redeeming virtues that make them ideal as a canvas of invention: They are extraordinarily fast and amazingly reliable. These characteristics allow the creator of a program to weave intricate chains of thought and have a fantastic number of steps carried out without fail. We’ll begin to explore how this is possible after we go over some definitions and objectives for this chapter. Definitions A k e y w o r d is a word defined in the Java language, such as i f a n d while. An identifier is a user-defined name; variable names are identifiers. Identifiers must not conflict with keywords such as if and while; for example, you cannot create a variable with the name while. An operator is one of the facilities of Java that is built into the language rather than being added on later, and therefore can have a name that does not conform to the rules for identifiers. Examples are +, -, and =. 33 34 Chapter 3: BASICS OF PROGRAMMING A statement is a complete operation understood by the Java compiler. Each statement is ended with a semicolon (;). A token is a part of a program that the compiler treats as a separate unit. It’s analogous to a word in English; a statement is more like a sentence. For example, String is a token, as is (. On the other hand, x = 5; is a statement. An expression is one of the units of which a statement is made. It is made up of one or more tokens. In the statement i = k + 3;, "k + 3" is an expression composed of the three tokens k, +, and 3. An if statement is a statement that causes its controlled statement to be executed if the condition specified in the if statement is true. A while statement is a statement that causes its controlled statement to be executed while a specified condition is true. Problem Algorithms Java Interpreter Hardware A block is a group of statements that are considered one logical statement. A block is delimited by the "curly braces", { and }; the first of these symbols starts a block, and the second one ends the block. A block can be used anywhere that a statement can be used, and is treated in exactly the same way as if it were one statement. Objectives for This Chapter By the end of this chapter, you should: 1. 2. 3. 4. 5. 6. Understand what a program is and have some idea how one works. Understand how to get information into and out of a Java program. Understand how to use if and while to control the execution of a Java program.1 Understand how to tell the compiler to treat a section of a Java program as one unit. Be able to understand a simple program I’ve written in Java. Be able to write a simple program in Java. ______________________________________________________________________________ 1. Please note that capitalization counts in Java, so IF and WHILE are not the same as if and while. You have to use the latter versions. SPEED DEMON 35 Speed Demon The most impressive attribute of modern computers is, of course, their speed; as we have already seen, this is measured in MIPS (millions of instructions per second). Of course, raw speed is not very valuable if we can’t rely on the results we get. ENIAC, one of the first electronic computers, had a failure every few hours on the average; since the problems it was used to solve took about that much time to run, the likelihood that the results were correct wasn’t very high. Particularly critical calculations were often run several times, and if the users got the same answer twice, they figured it was probably correct. By contrast, modern computers are almost incomprehensibly reliable. With almost any other machine, a failure rate of one in every million operations would be considered phenomenally low, but a computer with such a failure rate would make dozens or hundreds of errors per second.2 Blaming It on the Computer On the other hand, if computers are so reliable, why are they blamed for so much that goes wrong with modern life? Who among us has not been the victim of an erroneous credit report, or a bill sent to the wrong address, or been put on hold for a long time because "the computer is down"? The answer is fairly simple: It’s almost certainly not the computer that is really at fault. It may be the software, other equipment such as telephone lines, tape or disk drives, or any of the myriad "peripheral devices" that the computer uses to store and retrieve information and interact with the outside world. Usually, it’s the software; when customer service representatives tell you that they can’t do something obviously reasonable, you can bet that the problem is the software. For example, I once belonged to a 401K plan whose administrators provided statements only every three months, about three months after the end of the quarter; in other words, in July I found out how much my account had been worth at the end of March. To estimate how much I had in the meantime, I had to look up the share values in the newspaper and multiply by the ______________________________________________________________________________ 2. However, we haven’t yet eliminated the possibility of hardware errors, as the floating-point flaw in early versions of the Pentium processor illustrates. In rare cases, the result of the divide instruction in those processors was accurate to only about 5 decimal places rather than the normal 16 to 17 decimal places. Problem Algorithms Java Interpreter Hardware 36 Chapter 3: BASICS OF PROGRAMMING number of shares. Of course, the mutual fund that issued the shares could tell its shareholders their account balances at any time of the day or night; however, the company that administered the 401K plan didn’t bother to provide such a service, as it would have required doing some work.3 Needless to say, whenever I hear that "the computer can’t do that" as an excuse for such poor service, I reply "Then you need some different programmers." That Does Not Compute All of this emphasis on computation, however, should not blind us to the fact that computers are not solely arithmetic engines. The most common application for which PCs are used is word processing, which is hardly a hotbed of arithmetical calculation. While we have so far considered only numeric data, this is a good illustration of the fact that computers also deal with another kind of information, which is commonly referred to by the imaginative term non-numeric variables. Numeric variables are those suited for use in calculations, such as in totalling a set of weights. On the other hand, non-numeric data are items that are not used in calculations like adding, Problem Algorithms m u l t i p l y i n g , o r s u b t r a c t i n g : E x a m p l e s a r e n a m e s , a d d r e s s e s , telephone numbers, Social Security numbers, bank account numbers, Java Interpreter or drivers license numbers. Note that just because something is called a number, or even is composed entirely of the digits 0–9, does not Hardware make it numeric data by our standards. The question is how the item is used. No one adds, multiplies, or subtracts drivers license numbers, for example; they serve solely as identifiers and could just as easily have letters in them, as indeed some do. For the present, though, let’s stick with numeric variables. We’ve already discussed the short, but in the rest of this book we’ll be using its big brother, the int, which is 4 bytes long and therefore can represent numbers from –2147483648 (-2^31) to 2147483647 (2^31-1). What can we do with variables of this type? To do anything with them, we have to write a Java program, which consists primarily of a list of operations to be performed by the computer, along with directions that influence how these operations are to be translated into a form called "byte-code instructions", or just "byte codes" for short. This raises a couple of interesting points: ______________________________________________________________________________ 3. This was apparently against the plan administrator’s principles. THAT DOES NOT COMPUTE 1. 2. 37 What are byte codes, anyway? Why does our Java program have to be translated into byte codes? Before we can answer these questions, we have to consider a more general notion, that of a "virtual computer". We’ll discuss this immediately after adding some more terms to our vocabulary. The Man behind the Curtain Unlike many words in the vocabulary of computing, virtual has more or less retained its standard English definition: "That is so in essence or effect, although not formally or actually; admitting of being called b y t h e n a m e s o f a r a s t h e e f f e c t o r r e s u l t i s c o n c e r n e d . "4 I n o t h e r words, a virtual computer would be something that acts just like a computer, but really isn’t one. Who would want such a thing? Apparently everyone, since virtual computer is just another name for what we have been calling software. This may seem a rash statement, but it really isn’t. One of the most important mathematical discoveries (inventions?) of the twentieth century was Alan Turing’s demonstration that it was possible to create a fairly simple computing device (called a Turing machine for some reason) that could imitate any other computing device. This machine works in the following way: You provide it with a description of the other computer you want it to imitate, and it follows those directions. Suppose we want a computer that calculates only trigonometric functions. Then we could theoretically write a set of instructions as to how such a computer would behave, feed it into a Turing machine, and have the Turing machine imitate the behavior of this theoretical "trigonometric computer". This is undoubtedly interesting, but you may be wondering what it has to do with programming. Well, what we’re actually doing when we write a Java program is creating instructions to be used by another program called the Java interpreter, describing the actions that would be taken by a hypothetical specialized computer that would solve the problem our program is designed to solve. When we run the program, the Java interpreter simulates these actions. In other words, our program is the definition of a particular virtual computer. Of course, the Java interpreter can’t really simulate any possible program, because there are limits in the amount of memory or disk ______________________________________________________________________________ 4. Oxford English Dictionary, first current definition (4). Problem Algorithms Java Interpreter Hardware 38 Chapter 3: BASICS OF PROGRAMMING space it has available, as well as limits on the speed of its execution. However, for problems that can be solved within those limits, it is a general-purpose computing facility that can be tailored to a particular problem by programming. In the particular case of Java, there are actually two levels of virtuality; whereas your Java program is executed by the Java interpreter, the Java interpreter itself is executed by a real, physical computer component. As we’ve seen in the previous chapter, that component, the CPU (for Central Processing Unit), is the active ingredient inside a computer; the RAM and disk are merely the tools that it uses to store and retrieve data. In other words, the Java interpreter is a "virtual computer" that is simulated by the CPU, whereas a Java byte-code program is a "virtual computer" that can be simulated by the Java interpreter. So now we can answer the question of what a byte-code instruction is: It’s the primitive unit of computation in the Java interpreter, just as a machine instruction is the primitive unit of computation in a physical CPU. Problem Lost in Translation Algorithms Java Now we’re ready to answer those questions that we put on hold for Interpreter our little excursion into virtual reality. First, let’s discuss byte codes Hardware in some more detail. Like a physical CPU, the Java interpreter understands a limited set of instructions, each of which does a very simple task such as adding two numbers together or comparing two numbers to see which is greater. These are much simpler than the source-code statements that we write in our Java programs. Therefore, our source-code programs have to go through an intermediate step, called compilation, which converts them from source code to byte codes so that they can be executed by the Java interpreter. It may not be obvious why we need both an interpreter and a compiler. It wasn’t to Susan. Susan: Can you have just an interpreter and no compiler? Do all interpreted languages then need a compiler? Steve: Yes, you can have an interpreter without a compiler, but that’s not very common because it is so inefficient; you don’t want to do all the work to find out what each statement means every time you look at LOST IN TRANSLATION 39 it, so virtually all interpreted languages translate the source code into a form that is easier to interpret at run time. Susan: The interpreter doesn’t work until run time? Steve: Right. The most basic tasks that the Java compiler performs are:5 1. 2. Converting variable names to numbers that will refer to areas of memory when the program is running. This allows us to use names for variables in our Java programs, while keeping the byte-code instructions simpler and easier to execute. Translating arithmetic and other operations (such as +, –, etc.) into the equivalent byte codes, including references to variables assigned in the previous step. Susan had some questions about what the compiler actually does for us. Susan: A byte-code instruction is in binary? Steve: Yes, or hex. Susan: So it attaches instructions to the variables? Steve: Not exactly. We’ll see shortly how it translates the named variables that we use in writing source code so they can be used when we’re running a program. This is probably a bit too abstract to be easily grasped, so let’ s look at an example. Figure 3.1 shows some sample statements that do arithmetic calculations. ______________________________________________________________________________ 5. The compiler also does a lot of other work for us, which we’ll get into later. Problem Algorithms Java Interpreter Hardware 41 Chapter 3: BASICS OF PROGRAMMING int i; int j; int k; int m; i = 5; j = i * 3; k = j - i; m = (k + j) / 5; i = i + 1; // j is now 156 // k is now 10 // m is now 5 // i is now 6 Figure 3.1: A little numeric calculation To enter such statements in the first place, follow the instructions in the "readme.txt" file on the CD in the back of the book.7 Once we have entered the statements for our program, we use the compiler to translate the programs we write into a form that the Java interpreter can perform; as defined in Chapter 1, the form we create is called source code, since it is the source of the program logic, whereas the form of our program that the Java interpreter can execute is called a byte-code program. As I’ve mentioned previously, there are several types of variables, Problem Algorithms the int being only one of these types. Therefore, the compiler needs some explanatory material so that it can tell what types of variables Java Interpreter you’re using; that’s what the first four lines of our little sample program fragment are for. Each line tells the compiler that the type of Hardware the variable i, j, k, or m is int, which specifies the range of values it can hold.8 After this introductory material, we move into the list of operations to be performed. This is called the executable portion of the program, as it actually causes the Java interpreter to do something when the program is executed; the operations to be performed, as mentioned previously, are called statements. The first one, i = 5;, sets the ______________________________________________________________________________ 6. The // marks the beginning of a comment, which is a note to you or another programmer; it is ignored by the compiler. For those of you with BASIC experience, this is just like REM (the "remark" keyword in that language); anything after it on a line is ignored. 7. By the way, blank lines are ignored by the compiler; in fact, you can even run all the statements together on one line if you want to. That won’t confuse the compiler. but it will make it much harder for someone reading your code later to understand what you’re trying to do. Programs aren’t written just for the compiler but also for other people; therefore, it is important to write them so that they can be understood by those other people. One very good reason for this is that more often than you might think, those "other people" turn out to be you, six months later. 8. Other kinds of variables can hold different ranges of values; we’ll go over them in some detail in future chapters. LOST IN TRANSLATION 41 v a r i a b l e i t o t h e v a l u e 5. A value such as 5, which doesn’t have a name, but represents itself in a literal manner, is called (appropriately enough) a literal value. This is as good a time as any for me to mention something that experienced programmers, especially C programmers, take for granted but has a tendency to confuse novices. This is the choice of the = sign to indicate the operation of setting a variable to a value, which is known technically as assignment. As far as I’m concerned, an assignment operation would be more properly indicated by some symbol suggesting movement of data, such as 5 => i;, meaning "store the value 5 into variable i". Unfortunately, it’s too late to change the notation for the assignment statement, as such a statement is called, so you’ll just have to get used to it. The = in a statement such as i = 5; means "set the variable on the left (in this case, i) to the value on the right (in this case, 5)".9 Now that I’ v e w a r n e d y o u a b o u t t h a t p o s s i b l e c o n f u s i o n , l e t’s continue looking at the operations in the program. The next one, j = i * 3;, specifies that the variable j is to be set to the result of multiplying the current value of i by the literal value 3. The one after that, k = j – i;, tells the Java interpreter to set k to the amount calculated by subtracting i from j; that is, j – i. The most complicated line in our little program fragment, m = (k + j) / 5;, calculates m as the sum of adding k and j and dividing the result by the literal value 5. Finally, the line i = i + 1; sets i to the value of i plus the literal value 1. This last may be somewhat puzzling; how can i be equal to i + 1? The answer is that an assignment statement is not an algebraic equality, no matter how much it may resemble one. It is a command telling the Java interpreter to assign a value to a variable. Therefore, what i = i + 1; actually means is "take the current value of i, add 1 to it, and store the result back into i." In other words, a Java variable is a place to store a value; the variable i can take on any number of values, but only one at a time; any former value is lost when a new one is assigned. This notion of assignment was the topic of quite a few messages with Susan. Let’s go to the first round. ______________________________________________________________________________ 9. At the risk of boring experienced programmers, let me reiterate that = does not mean "is equal to"; it means "set the variable to the left of the = to the value of the expression to the right of the =." In fact, there is no equivalent in Java to the mathematical notion of equality. We have only the assignment operator = and the comparison operator ==, which we will encounter later in this chapter. The latter is used in if statements to determine whether two expressions have the same value. All of the valid comparison operators are listed in Figure 4.5. Problem Algorithms Java Interpreter Hardware 42 Chapter 3: BASICS OF PROGRAMMING Susan: I am confused with the statement i = i + 1;, when you have stated previously that i = 5;. So which one is it? How can there be two values for i? Steve: There can’t; that is, not at one time. However, i, like any other variable, can take on any number of values, one after another. First, we set it to 5; then we set it to 1 more than it was before (i + 1), so it ends up as 6. Susan: Well, the example made it look as if the two values of i were available to be used by the Java interpreter at the same time. They were both lumped together as executable material. S t e v e : A f t e r t h e s t a t e m e n t i = 5 ; , a n d b e f o r e t h e s t a t e m e n t i = i + 1; , t h e value of i is 5. After the statement i = i + 1;, the value of i is 6. The key here is that a variable such as i is just our name for some area of memory that can hold only one value at one time. Does that clear it up? Susan: So it is not like algebra? Then i is equal to an address of memory and does not really equate with a numerical value? Well, I guess it does when you assign a numerical value to it. Is that it? Problem Algorithms Java Interpreter Hardware Steve: Very close. A variable in Java isn’t really like an algebraic variable, which has a value that has to be figured out and doesn’t change in a given problem. A programming language variable is just a name for a storage location that can contain a value. With any luck, that point has been pounded into the ground, so you won’t have the same trouble that Susan did. Now let’s look at exactly what an assignment statement does. If the value of i before the statement i = i + 1; is 5 (for example), then that statement will cause the Java interpreter to perform the following steps:10 1. 2. 3. Take the current value of i (5). Add one to that value (6). Store the result back into i. After the execution of this statement, i will have the value 6. ______________________________________________________________________________ 10. If you have any programming experience whatever, you may think that I’m spending too much effort on this very simple point. I can report from personal experience that it’s not necessarily easy for a complete novice to grasp. Furthermore, without a solid understanding of the difference between an algebraic equality and an assignment statement, that novice will be unable to understand how to write a program. WHAT’S GOING ON UNDERNEATH? 43 What’s Going on Underneath? In a moment we’re going to dive a little deeper into how the Java interpreter accomplishes its task of manipulating data, such as we are doing here with our arithmetic program. First, though, it’s time for a little pep talk for those of you who might be wondering exactly why this apparent digression is necessary. It’s because if you don’t understand what is going on under the surface, you won’t be able to get past the "Sunday driver" stage of programming in Java. A good Java programmer needs to know a fair amount about the internal workings of the language, for reasons which will become very apparent later in the book. For the moment, you’ll just have to take my word that working through these intricacies is essential; the payoff for a thorough grounding in these fundamental concepts of computing will be worth the struggle. Underware? I can almost hear the wailing and tooth gnashing out there. Do I expect you to deal with byte codes by yourself? You’ll undoubtedly be happy to learn that this isn’t necessary, as the compiler and the interpreter take care of these details. On the other hand, if you don’t have some idea of how these programs work, you’ll be at a disadvantage when you’re trying to figure out how to make Java do what you want. Therefore, we’re going to spend some time "playing compiler"; that is, I’ll examine each statement and indicate what action the compiler might take as a result. I’ll simplify the statements a bit to make the explanation simpler; you should still get the idea (I hope). Figure 3.5 illustrates the set of statements that I’ll compile.11 However, before we start to analyze how the compiler does its job, we’ll need to go over one of the fundamental methods that the Java interpreter and compiler use to organize their data: the stack. Stacking the Deck Variables in the program fragment we’ll be considering are stored in a data structure called a stack; the name is intended to suggest the notion of stacking clean plates on a spring-loaded holder such as you ______________________________________________________________________________ 11. As I’ve mentioned previously, blank lines are ignored by the compiler; you can put them in freely to improve readability. Problem Algorithms Java Interpreter Hardware 44 Chapter 3: BASICS OF PROGRAMMING might see in a cafeteria. The last plate deposited on the stack of plates will be the first one to be removed when a customer needs a fresh plate. But what does this have to do with programming? I think this will become clearer when we see some examples. First, though, we’ll need some new terms to describe the behavior of a stack. 1. 2. Push means to add a new item to the stack, causing the previously stored items to move down one location in the stack; that is, the item that was in slot 1 goes to slot 2, the entry in slot 2 goes to slot 3, and so on. Pop means to remove an item from the stack and store it in a specified destination, causing the other items in the stack to move up one location toward the top; that is, the item that was in slot 2 moves to slot 1, the one in slot 3 moves to slot 2, and so on. A stack with one entry might look something like Figure 3.2. Figure 3.2: A stack with one entry Problem ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ Algorithms TOP ³ 1234 ³ Java ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Interpreter Hardware If we add (or push) another value on to the stack, say 999, the result would look like Figure 3.3. Figure 3.3: A stack with two entries TOP 2nd ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ 999 ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 1234 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ If we were to push one more item, this time with the value 1666, the result would look like Figure 3.4. STACKING THE DECK 45 Figure 3.4: A stack with three entries TOP 2nd 3rd ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ 1666 ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 999 ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 1234 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Now, if we retrieve (or pop) a value, we’ll get the one on top; namely, 1666. Then the stack will look like it did in Figure 3.3. The next value to be popped off the stack will be the 999, leaving us with the situation in Figure 3.2 again. If we continue for one more round, we’ll get the value 1234, leaving us with an empty stack. As we’ll see, the actual way that a stack is implemented is a bit different than is suggested by the "stack of plates" analogy, although the effect is exactly the same. Rather than keeping the top of the stack where it is and moving the data (a slow operation), the data are left where they are and the address stored in a stack pointer is changed, which is a much faster operation. In other words, whatever address the stack pointer is pointing to is by definition the "top of the stack".12 The idea of a stack led to some discussions with Susan. Here’s the first installment: Susan: Where is the stack? Steve: The actual memory locations used to hold the items in the stack are just like any other locations in RAM; what makes them part of the stack is how they are used. As always, one memory location can hold only one item at a given time, so the locations used to hold entries on the stack cannot be simultaneously used for something else, like byte codes. Susan: Where you say "what makes them part of the stack is how they are used." How is that? Steve: RAM is RAM. It can be used to store programs, data on the stack, or other types of data that we’ll get to later. What distinguishes these is how the memory is used, not what it’s physically made of. ______________________________________________________________________________ 12. Please note that the address that the stack occupies in the following diagrams is arbitrary. The actual address where the stack is located in your program is determined by the interpreter in combination with the operating system. Problem Algorithms Java Interpreter Hardware 46 Chapter 3: BASICS OF PROGRAMMING Susan: Yes, but what is different about the use of the RAM used in the stack? Steve: It’s used to hold the data in the stack, rather than byte codes or other kinds of data. Susan: Then how does the stack pointer "talk" to the stack to change what it is pointing to? Steve: It doesn’t have to. Whatever the stack pointer is pointing to is by definition the top of the stack; therefore, changing the contents of the stack pointer changes the top of the stack. Susan: So what makes the stack pointer change? Steve: The interpreter, as it is executing the program. Each byte code instruction has a defined effect on the stack pointer (pushing one or more values, popping one or more values, doing both, or doing neither. Susan: What do you mean as the interpreter is executing the program? Do you mean that it doesn’t work until run time? Problem Algorithms Java Interpreter Hardware Steve: Yes. Susan: Okay, then if this is what the interpreter does, what does the compiler do? Does it just compile the program into byte-codes that the interpreter can read? Steve: Yes. Susan : T h e n o n s t a c k s , i s i t l i k e t h i s ? T h e s t a c k s t a y s s t i l l , a n d t h e pointer to the value’s address moves. Steve: The data stays where it is in memory, and the stack pointer points to different places in memory. Whatever it points to is the top of the stack. Susan: Where is the stack pointer relative to the stack? Steve: It isn’t relative to the stack. It’s a register, which doesn’t have a memory address. Susan: I don’t get that. Steve: A register is a storage area that is on the same chip as the CPU. Programs use registers to hold data items that are actively in use; data in STACKING THE DECK 47 registers can be accessed much faster than if it had to be retrieved from RAM every time it was needed. Susan: But I thought that the stack was in the registers. Steve: No, the stack is in RAM. The stack pointer is the register that indicates exactly where the top of the stack is in RAM. Susan: Put that in the book again, darling. Repeat this many times, and then maybe I will remember it. Steve: It’s worth a try. Compiler’s-Eye View Now we’re just about ready to start analyzing a small program fragment to see how it is actually represented in Java byte codes. Here are the rules of this "game": 1. 2. 3. 4. All numbers in the Java program are decimal; all addresses of the byte codes are hexadecimal. Calculations use the stack to store intermediate values. Byte codes are stored at (hexadecimal) addresses starting at 0. All local variables are stored in a series of "local variable slots" in a reserved area of the stack. Figure 3.5 shows the program fragment that we’re going to compile. int i; int j; i = 5; j = i + 3; Figure 3.5: A really little numeric calculation Now we’re ready to start compiling. The first statement, int i;, tells me to allocate storage for an int variable called i; since i is an int, it takes up 4 bytes of memory, which corresponds to one "slot" on the stack. Therefore, the "memory map" at the beginning of our program fragment will look like Figure 3.6 so far, with the ?s indicating that the variable i hasn’t been initialized yet; that will happen before the program fragment starts. Problem Algorithms Java Interpreter Hardware 48 Chapter 3: BASICS OF PROGRAMMING Stack ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Local Value Meaning ³ ³ Variable ³ ³ Slots ³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ ???????? ³ i ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 3.6: Compiling, part 1 As you might have guessed, this exercise was the topic of a considerable amount of discussion with Susan. Here’s how it started: Susan: So the first thing we do with a variable is to tell the address that its name is i, but no one is home, right? It has to get ready to accept a value. Could you put a value in it without naming it, just saying that the first entry on the stack has a value of 5? Why does it have to be called i first? Problem Algorithms Java Interpreter Hardware Steve: The reason that we use names instead of addresses is because it’s much easier for people to keep track of names than it is to keep track of addresses. Thus, one of the main functions of a compiler is to allow us to use names that are translated into addresses for the computer’s use. Susan: What are those local variable slots? Steve: They’re the part of the stack that is used to store local variables. Susan: So is the stack where the compiler puts the variables? Steve: Yes, the compiler puts them in the local variable part of the stack. Susan: Are those the places in the stack that do the arithmetic? Steve: Close, but not quite: The compiler puts the variables in the local variable part of the stack and generates byte codes to transfer them from and to the active part of the stack, where the arithmetic byte codes can access them. The second statement, int j;, tells me to allocate storage for an int variable called j. Just as with i, j takes 4 bytes of storage, or one slot position on the stack. As before, the ?s indicate that no value has been assigned to this variable yet; it will also be initialized to 0 at run COMPILER’S-EYE VIEW 49 time, so the resulting "memory map" as of the beginning of our program fragment will look like Figure 3.7. Stack ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Local Value Meaning ³ ³ Variable ³ ³ Slots ³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ ???????? ³ i ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ 2 ³ ???????? ³ j ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 3.7: Compiling, part 2 The next line is blank, so we skip it. This brings us to the statement i = 5; which is an executable statement, so we need to generate one or more byte codes to execute it. Figure 3.8 shows what the code for this program fragment looks like so far, with the statement being compiled listed as a comment before the byte codes that are responsible for executing it.***** Stack ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Local Value Meaning ³ ³ Variable ³ ³ Slots ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ ???????? ³ i ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ 2 ³ ???????? ³ j ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Code ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Address Byte Code Description ³ ³ ³ ³ // i = 5; ³ ³ 00000000 iconst_5 Push 5 ³ ³ 00000001 istore_1 Pop into i ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 3.8: Compiling, part 3 Problem Algorithms Java Interpreter Hardware 50 Chapter 3: BASICS OF PROGRAMMING I’m sure you’re wondering what those iconst_5 and istore_1 instructions mean. Susan certainly did. Susan: What is "iconst"? Steve: A byte code instruction that pushes a (specified) constant on the stack. For example, iconst_5 means to push the value 5. Susan: So "push 5" means it puts 5 to the top of the stack? Steve: Right. The rest of the items on the stack stay on the stack, logically moving down one position. Problem Algorithms Java Interpreter Hardware As soon as we compile the rest of the code, we’ll see how the Java interpreter performs arithmetic operations. The last statement, j = i + 3;, is the most complicated statement in our program, and it’s not that complicated. As with the previous statement, it’s executable, which means we need to generate byte codes to execute it. The byte codes with addresses 2 through 5 are responsible for this operation, as we’ll see in gory detail as soon as we get done compiling. Figure 3.9 shows what the "memory map" looks like now. COMPILER’S-EYE VIEW 51 Stack ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Local Value Meaning ³ ³ Variable ³ ³ Slots ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ 00000000 ³ i ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ 2 ³ 00000000 ³ j ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Code ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Address Byte Code Description ³ ³ ³ ³ // i = 5; ³ ³ 00000000 iconst_5 Push 5 ³ ³ 00000001 istore_1 Pop into i ³ ³ ³ ³ // j = i + 3; ³ ³ 00000002 iload_1 Push i ³ ³ 00000003 iconst_3 Push 3 ³ ³ 00000004 iadd Pop top two stack ³ ³ entries, push the sum ³ ³ 00000005 istore_2 Pop into j ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 3.9: Compiling, part 4 Here’s the next installment of the byte-code discussion with Susan: Susan: So iload_1 is putting i into the local variable slot #1. Steve: That’s pretty close, but not exactly right. Actually, the iload_1 instruction gets the value of the local variable in slot #1 (in this case, i) and pushes it onto the stack. Susan: Then iload_1 is a retrieving function? Steve: Yes, it retrieves the value of the local variable in slot #1 and makes it available for calculation (by pushing it on the stack). Susan: What are the exact functions of iadd and istore and how do they do that? Steve: I’m glad you asked me that question. First, iadd is the "integer add" instruction. It pops the top two entries off the stack, adds them together, and pushes the result back on the stack. Problem Algorithms Java Interpreter Hardware 52 Chapter 3: BASICS OF PROGRAMMING The istore instruction is sort of the opposite of iload; it pops a value from the stack and stores it in a local variable. In our example program, we’re using the istore_2 instruction to store the result of our calculation into local variable #2 (that is, j). How It All Stacks Up Having examined what the compiler does at compile time with the preceding little program fragment, the next question is what happens when the compiled program is interpreted. When we start out, the stack and code areas of memory we’re concerned with will look like Figure 3.10.13 Stack Problem Algorithms Java Interpreter Hardware ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Local Value Meaning ³ ³ Variable ³ ³ Slots ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ 00000000 ³ i ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ 2 ³ 00000000 ³ j ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ Expression ³ ³ Evaluation (empty) ³ ³ Area ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Code ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Address Byte Code Description ³ ³ ³ ³ // i = 5; ³ ³ 00000000 iconst_5 Push 5 ³ ³ 00000001 istore_1 Pop into i ³ ³ ³ ³ // j = i + 3; ³ ³ 00000002 iload_1 Push i ³ ³ 00000003 iconst_3 Push 3 ³ ³ 00000004 iadd Pop top two stack ³ ³ entries, push the sum ³ ³ 00000005 istore_2 Pop into j ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 3.10: Execution, part 1 ______________________________________________________________________________ 13. The next byte code to be executed will be bold. HOW IT ALL STACKS UP 53 First of all, you should note that the variables i and j have been set to the default value for an int, namely, 0. This is guaranteed by the Java language specification. Now we’re just about ready to examine what all those byte codes do when we execute the program. Let’s start at the beginning, with the two common ways that electronic calculators do arithmetic operations. The first, used by most calculators except for those made by Hewlett-Packard, is called algebraic operation. This method, as the name implies, works in a way similar to how we write arithmetic expressions in Java. For example, to calculate the average of two variables, i and j, we would write the expression (i + j)/2. At run time, the code would add i and j, then divide by 2 to get the answer. While this method of evaluating arithmetic expressions is seemingly natural for humans, it is not the way that byte code programs work in Java. Instead, the Java compiler converts such expressions into a form similar to the one used by Hewlett-Packard calculators. This form is called RPN (for Reverse Polish Notation, because it was invented by the Polish logician Jan Lukasiewicz); it uses a stack to store values during the evaluation of an expression. The area of the stack used for this purpose is labeled as the "expression evaluation area" in the figures illustrating the execution of our program fragment. As you might not be surprised to hear, Susan had some questions about this method of calculating arithmetic expressions. Susan: What is the "expression evaluation area"? Steve: The part of the stack used to store intermediate values during execution of the byte codes. Susan: I need some more explanation of this. Steve: The expression evaluation area is a part of the stack used during the calculation of an arithmetic expression. For example, in the statement "j = i + 3", the value of the expression "i + 3" is calculated in a couple of steps. First, the value of i is pushed onto the stack; then 3 is pushed onto the stack; finally the "add" instruction adds the top two elements of the stack (i and 3). Now that we have the value of i + 3 on the top of the stack, that value is stored into j. Does that help? Susan: So the expression evaluation area is like your desktop? A place to work? Steve: Possibly, but maybe a better analogy would be a blackboard that you calculate something on, but clean off once you have the final result. Problem Algorithms Java Interpreter Hardware 54 Chapter 3: BASICS OF PROGRAMMING Susan: How do I know which byte codes push and which ones pop? Steve: You can look it up in The Java Virtual Machine Specification, by Tim Lindholm and Frank Yellin (Addison-Wesley, 1997). However, you won’t normally need to do this, because the compiler will take care of it for you. Now let’s see exactly how the evaluation of our statements works. Starting out with an empty expression evaluation area, we execute the iconst_5 byte code, which pushes the constant int value 5 onto the stack. This leaves us with the situation in Figure 3.11. Stack Problem Algorithms Java Interpreter Hardware ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Local Value Meaning ³ ³ Variable ³ ³ Slots ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ 00000000 ³ i ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ 2 ³ 00000000 ³ j ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ Expression ³ ³ Evaluation ³ ³ Area ³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ 00000005 ³ 5 ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Code ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Address Byte Code Description ³ ³ ³ ³ // i = 5; ³ ³ 00000000 iconst_5 Push 5 ³ ³ 00000001 istore_1 Pop into i ³ ³ ³ ³ // j = i + 3; ³ ³ 00000002 iload_1 Push i ³ ³ 00000003 iconst_3 Push 3 ³ ³ 00000004 iadd Pop top two stack ³ ³ entries, push the sum ³ ³ 00000005 istore_2 Pop into j ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 3.11: Execution, part 2 The next step is to execute the istore_1 byte code, which pops the value from the top of the stack into local variable 1 (i); that is, it sets i HOW IT ALL STACKS UP 55 to the value on the top of the stack, and removes that value from the stack. This leaves us with the situation in Figure 3.12. Note that the value of i has been changed to 5, and the expression evaluation area of the stack is empty. Stack ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Local Value Meaning ³ ³ Variable ³ ³ Slots ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ 00000005 ³ i ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ 2 ³ 00000000 ³ j ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ Expression ³ ³ Evaluation (empty) ³ ³ Area ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Code ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Address Byte Code Description ³ ³ ³ ³ // i = 5; ³ ³ 00000000 iconst_5 Push 5 ³ ³ 00000001 istore_1 Pop into i ³ ³ ³ ³ // j = i + 3; ³ ³ 00000002 iload_1 Push i ³ ³ 00000003 iconst_3 Push 3 ³ ³ 00000004 iadd Pop top two stack ³ ³ entries, push the sum ³ ³ 00000005 istore_2 Pop into j ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 3.12: Execution, part 3 Next, we start on the statement j = i + 3;. The Java interpreter will perform this operation by reloading the value of i (variable 1), pushing a literal 3 onto the stack, adding the top two elements of the stack, and finally storing the result into variable 2 (j). Let’s go over that step by step, starting with the byte code iload_1, which pushes the value of local variable 1 (i) onto the stack. This leaves us with the situation in Figure 3.13. Problem Algorithms Java Interpreter Hardware 56 Chapter 3: BASICS OF PROGRAMMING Stack ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Local Value Meaning ³ ³ Variable ³ ³ Slots ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ 00000005 ³ i ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ 2 ³ 00000000 ³ j ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ Expression ³ ³ Evaluation ³ ³ Area ³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ 00000005 ³ i ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Code ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Address Byte Code Description ³ ³ ³ ³ // i = 5; ³ ³ 00000000 iconst_5 Push 5 ³ ³ 00000001 istore_1 Pop into i ³ ³ ³ Problem j = i + 3; ³ Algorithms ³³ // 00000002 iload_1 Push i ³ Java ³ 00000003 iconst_3 Push 3 ³ iadd Pop top two stack ³ Interpreter ³ 00000004 ³ entries, push the sum ³ Hardware ³ 00000005 istore_2 Pop into j ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 3.13: Execution, part 4 Next, we execute the byte code iconst_3, which pushes the value 3 onto the stack. This leaves the situation shown in Figure 3.14. HOW IT ALL STACKS UP 57 Stack ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Local Value Meaning ³ ³ Variable ³ ³ Slots ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ 00000005 ³ i ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ 2 ³ 00000000 ³ j ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ Expression ³ ³ Evaluation ³ ³ Area ³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ 00000003 ³ 3 ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ 2 ³ 00000005 ³ i ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Code ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Address Byte Code Description ³ ³ ³ ³ // i = 5; ³ ³ 00000000 iconst_5 Push 5 ³ ³ 00000001 istore_1 Pop into i ³ ³ ³ ³ // j = i + 3; ³ ³ 00000002 iload_1 Push i ³ ³ 00000003 iconst_3 Push 3 ³ ³ 00000004 iadd Pop top two stack ³ ³ entries, push the sum ³ ³ 00000005 istore_2 Pop into j ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 3.14: Execution, part 5 Next, we execute the iadd byte code, which pops the top two entries in the stack, adds them together, and pushes the sum. This leaves the situation shown in Figure 3.15. Problem Algorithms Java Interpreter Hardware 58 Chapter 3: BASICS OF PROGRAMMING Stack ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Local Value Meaning ³ ³ Variable ³ ³ Slots ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ 00000005 ³ i ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ 2 ³ 00000000 ³ j ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ Expression ³ ³ Evaluation ³ ³ Area ³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ 00000008 ³ i+3 ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Code ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Address Byte Code Description ³ ³ ³ ³ // i = 5; ³ ³ 00000000 iconst_5 Push 5 ³ ³ 00000001 istore_1 Pop into i ³ ³ ³ Problem j = i + 3; ³ Algorithms ³³ // 00000002 iload_1 Push i ³ Java ³ 00000003 iconst_3 Push 3 ³ iadd Pop top two stack ³ Interpreter ³ 00000004 ³ entries, push the sum ³ Hardware ³ 00000005 istore_2 Pop into j ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 3.15: Execution, part 6 Finally, we execute the istore_2 byte code, which stores the top entry in the stack into local variable 2 (j). This leaves the situation shown in Figure 3.16. HOW IT ALL STACKS UP 59 Stack ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Local Value Meaning ³ ³ Variable ³ ³ Slots ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ 1 ³ 00000005 ³ i ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ 2 ³ 00000008 ³ j ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ Expression ³ ³ Evaluation (empty) ³ ³ Area ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Code ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Address Byte Code Description ³ ³ ³ ³ // i = 5; ³ ³ 00000000 iconst_5 Push 5 ³ ³ 00000001 istore_1 Pop into i ³ ³ ³ ³ // j = i + 3; ³ ³ 00000002 iload_1 Push i ³ ³ 00000003 iconst_3 Push 3 ³ ³ 00000004 iadd Pop top two stack ³ ³ entries, push the sum ³ ³ 00000005 istore_2 Pop into j ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 3.16: Execution, part 7 The difference between this situation and the last one is that the expression evaluation area of the stack is empty and the value of j has been set to 8. Here’s the next installment of my discussion with Susan on this topic: Susan: Now this may sound like a very dumb question, but please tell me where 5 comes from? I mean if you are going to move the value of 5 onto the stack, where is 5 hiding to take it from and to put it in the stack? Is it stored somewhere in memory that has to be moved, or is it simply a function of the user just typing in that value? Steve: No, it’s not a dumb question at all. The 5 is part of the byte code iconst_5. You see, because small numbers (0 through 5 and minus 1) are so common in Java programs, the Java interpreter has special byte codes to push those values onto the stack. If the number were greater than 5, it Problem Algorithms Java Interpreter Hardware 60 Chapter 3: BASICS OF PROGRAMMING would be stored as a literal value immediately following a byte code that means "push a literal value". By the way, don’t be misled by this example into thinking that all byte codes are 1 byte in length. It’s just a coincidence that all of the ones I’ve used here are of that length. The actual size of a byte-code instruction can vary considerably; in fact, some byte-code instructions can be virtually unlimited in length. Most byte codes, however, take from 1 to 3 bytes. A Cast of Characters This should give you some idea of how numeric variables and values work. But what about non-numeric ones? This brings us to the subject of two new variable types and the values they can contain. These are the char and its relative, the String. What are these good for, and how do they work?14 A variable of type char corresponds to 1 character of text, which in Java occupies 2 bytes (16 bits) of storage. Since a char has 16 bits, it can hold any of 65536 (2^16) values, which is the same number of Problem Algorithms values that a short can hold. So what’s the difference between these two types? Java Interpreter The main purpose of a char is to represent an individual letter, digit, punctuation mark, "special character" (e.g., $, #, %, and so on), Hardware or one of the other "printable" and displayable units from which words, sentences, and other textual data such as this paragraph are composed.15 We don’ t n e e d a n y w h e r e n e a r 6 5 , 5 3 6 p o s s i b i l i t i e s t o represent any character in English, as well as a number of European languages; in fact 256 possibilities is enough to handle all of the characters in those languages, so in most programming languages other than Java a char is only 1 byte long. However, the written forms of "ideographic" languages such as Chinese and Korean consist of far more than 256 characters, so 1 byte isn’t going to do the trick for these languages. While they have been supported to some extent by schemes that switch among a number of sets of 256 characters each, such clumsy approaches to the ______________________________________________________________________________ 14. In case you were wondering, the most common pronunciation of char has an a like the a in "married", while the ch sounds like "k". 15. As we will see shortly, not all characters have visible representations; some of these "nonprintable" characters are useful in controlling how our printed or displayed information looks. A CAST OF CHARACTERS 61 problem made programs much more complicated and error prone. As the international market for software is increasing rapidly, it has become more important to have a convenient method of handling large c h a r a c t e r s e t s. To solve this problem, the Unicode standard, which uses 2 bytes per character, has been developed. This is the representation that Java uses for its chars.16 Even in an ideographic language, one char isn’t good for much by itself, so we often use groups of them, called Strings, to represent a significant amount of text. Just as with numeric values, these variables can be set to literal values, which represent themselves. Figure 3.17 is an example of how to specify and use each of these types we’ve just encountered. public class Basic00 { public static void main( String args[ ] ) { char c1; char c2; String s1; String s2; String s3; c1 = ’A’; c2 = c1; s1 = "Congratulations! "; s2 = "You got an "; s3 = " on the test."; System.out.println( s1 + s2 + c2 + s3); } } Figure 3.17: Some real characters and Strings (code\Basic00\Basic00.java) This file, Basic00.java, is the source code for the first complete program we’ve seen, so there are a couple of new constructs that I’ll have to explain to you. Before we get to the explanation, however, I should mention that one of the reasons that Java currently doesn’t run under DOS or Windows 3.1 is that it requires long filenames, that is, ______________________________________________________________________________ 16. The Unicode standard is actually a "small" version of a standard that uses 32 bits per character, for the day when Unicode doesn’t have sufficient capacity; that should take care of any languages that alien civilizations might introduce to our planet. Problem Algorithms Java Interpreter Hardware 62 Chapter 3: BASICS OF PROGRAMMING ones that have more than 8 characters before the period or more than three characters after the period. In this case, of course, it is the extension, .java, that is not legal under DOS or Windows 3.1. By the way, in case the program in Figure 3.17 doesn’t seem very useful, that’s because it isn’t; it’s just an example of the syntax of defining and using variables and literal values. However, we’ll use these constructs to do useful work later, so going over them now isn’t a waste of time. The first construct we have to examine is the line public class Basic00, which has three components. The first component of this line is the keyword public, which means that we’re defining something that is generally available to any program that wants to use it. The particular kind of "something" that we’re making available is specified by the second component of this line: the keyword class, which tells the Java compiler that we are beginning the definition of a class. What is a class? That’s a very good question, because every program in Java is composed of the definitions of one or more classes. This is obviously a very important part of the language; however, you’ll need a significant amount of background in other aspects of the Java language before its explanation is likely to mean very much to you, so I’m deferring that discussion until Chapter 6. For now, just take Problem Algorithms my word that every Java program has to start with a line telling the compiler the name of the class that we’re defining. Java Interpreter As this brief explanation suggests, the third component of this line is Basic00, which is the name of the particular class we’re defining in Hardware this program. We could call it whatever we wanted, but since it’s the first example program in this chapter on the basics of programming, I named it Basic00 so that you and I could keep track of it more easily. Once we have specified the name of the class that we’re creating, we can define the piece of code that is going to actually do the work for us. That’s the job of the next line: public static void main( String args[ ] ) This will be the first line of every main function that we will examine in this book. Eventually I’ll be able to explain exactly what every part of this line means, but you don’t have enough background yet. Therefore, you’ll have to take my word that everything on that line will eventually mean something. However, there is one thing I can explain: the meaning of main. Java has a rule that execution always starts at the place called main. Since this is where we want our program to start executing, we have A CAST OF CHARACTERS 63 to call it by that name; marking the place where we want to start is the main purpose of the line we’re discussing. You may also be puzzled by the function of the other statements in t h i s p r o g r a m . I f s o , y o u ’ r e n o t a l o n e . L e t ’s see the discussion that Susan and I had about that topic. Susan: Okay, in the example why did you have to write c2 = c1;? Why not B? Why make one thing the same thing as the other? Make it different. Why would you even want c2=c1; and not just say c1 twice, if that is what you want? Steve: It’s very hard to think up examples that are both simple enough t o e x p l a i n a n d r e a l i s t i c e n o u g h t o m a k e s e n s e . Y o u’ r e r i g h t t h a t t h i s example doesn’t do anything useful; I’m just trying to introduce what both the char type and the String type look like. Susan: Come to think of it, what does c1=’A’; have to do with the statement s1= "Congratulations! ";? I don’t see any relationship between one thing and the other. Steve: This is the same problem as the last one. They have nothing to do with one another; I’m using an admittedly contrived example to show how these variables are used. Susan: I am glad now that your example of chars and Strings (put together) didn’t make sense to me. That is progress; it wasn’t supposed to. What does this useless but hopefully instructive program do? As is always the case, we have to tell the compiler what the types of our variables are before we can use them. In this case, c1 and c2 are of type char, whereas s1, s2, and s3 are Strings. After taking care of these formalities, we can start to use the variables. In the first executable statement, c1 = ’A’;, we set the char variable c1 to a literal value, in this case a capital A; we need to surround this with single quotation marks (’) to tell the compiler that we mean the letter A rather than a variable named A. In the next line, c2 = c1;, we set c2 to the same value as c1 holds, which of course is ’A’ in this case. The next executable statement, s1 = "Congratulations! ";, as you might expect, sets the String variable s1 to the value "Congratulations! ", which is a String literal.17 A String literal is a type of literal that we use to assign values to variables of type String. In the statement s1 = "Congratulations! "; we use a ______________________________________________________________________________ 17. Please note that there is a space (blank) character at the end of that String literal, after the exclamation point (!). That space is part of the literal value. Problem Algorithms Java Interpreter Hardware 64 Chapter 3: BASICS OF PROGRAMMING quotation mark, in this case the double quote ("), to tell the compiler where the literal value starts and ends. You may be wondering why we need two different kinds of quotes in these two cases. There really isn’t a very good reason that I can think of other than to allow the compiler to tell whether we’re using a compatible type of literal for a char or a String variable. As far as I can tell, this distinction is left over from C and C++, where it made more sense, but for reasons that aren’t relevant here. The Right Type As the above discussion suggests, every variable in Java has a type. While some languages allow the same variable to be used in different ways at different times, in Java any given variable always has the same type; for example, a char variable can’t change into an int. At first glance, it seems that it would be much easier for programmers to be able to use variables any way they like; why is Java so restrictive? The Java type system, as this feature of a language is called, is specifically designed to minimize the risk of misinterpreting or otherwise misusing a variable. It’s entirely too easy in some Problem Algorithms languages to change the type of a variable without meaning to; the resulting bugs can be very difficult to find, especially in a large Java Interpreter program. In Java, the usage of a variable can be checked by the compiler. This static type checking allows the compiler to tell you Hardware about many errors that otherwise would not be detected until the program is running (dynamic type checking ) . T h i s i s p a r t i c u l a r l y important in systems that need to run continuously for long periods of time. While you can reboot your machine if your word processor crashes due to a run-time error, this is not acceptable as a solution for errors in the telephone network, for example. Of course, you probably won’t be writing programs demanding the degree of reliability that the telephone network requires any time soon, but strict static type checking is still worthwhile in helping eliminate errors at the earliest possible stage in the development of our programs. You might get the impression from this discussion that Java is suitable for developing large applications that have to run indefinitely. Is this true? REALITY CHECK 65 Reality Check Most other books I’ve seen on Java contain a statement something like the following, often at roughly this point in the exposition: Java is the be-all and end-all of computer languages. It is infinitely superior to every other language, especially C++, which has served its purpose now that its obvious successor, Java, has been invented. Java is suitable for every possible application, and most impossible ones. Furthermore, Java is a very simple language that you can learn in a few minutes, probably in your sleep.18 This is considerably at variance with the truth. Java has some advantages over other languages, including C++. It also has some serious drawbacks that make it eminently unsuitable for many types of applications, especially large ones that have to operate for a long time without fail; for example, it would not be a good idea to write programs to control the telephone network in Java. I’ll explain some of these drawbacks as we run across them in this book. As for the claim of how simple Java is: I can’t agree with that either. Java is not significantly simpler than other computer languages, and in particular it’s not any simpler than the part of C++ that you should use to design new programs (as contrasted with the "dark corners" of C++ that you can stumble into when dealing with old programs). However, you don’t have to take my word for the complexity of Java; you should have a pretty good idea of how complex it is by the time you get through this book! Some Strings Attached After that info-mercial for the advantages of static type checking (and deflation of some of the hype about Java), we can resume our e x a m i n a t i o n o f S t r i n gs. You may have noticed that there’s a s p a c e character at the end of the String "Congratulations! ". That’s another reason why we have to use a special character like " (the double quote) to mark the beginning and end of a String; how else would the compiler know whether that space is supposed to be part of the String or not? The space character is one of the non-printing characters (or non-display characters) that controls the format of our displayed or printed information; imagine how hard it would be to read this ______________________________________________________________________________ 18. I know I left out the part about its curing cancer, but I don’t want to go overboard. Problem Algorithms Java Interpreter Hardware 66 Chapter 3: BASICS OF PROGRAMMING book without space characters! While we’re on the subject, I should also tell you about some other characters that have special meaning to the compiler. They are listed in Figure 3.18. Name Graphic Use single quote double quote semicolon curly braces parentheses backslash ’ " ; {} () \ surrounds a single-character value surrounds a multi-character value ends a statement groups statements together surrounds part of a statement19 tells the compiler that the next character should be treated differently from the way that it would normally be treated20 Figure 3.18: Special characters for program text I compiled Figure 3.18 at the instigation of guess who. Problem Algorithms Java Interpreter Hardware Susan: How about you line up all your cute little " ’ \ ; things and just list their meanings? I forget what they are by the time I get to the next one. Your explanations of them are fine, but they are scattered all over the place; I just want one place that has all the explanations. Steve: That’s a good idea; I think I will. Our next task will be to see how we get the values of our Strings and chars to show up on the screen. In and Out Most programs need to interact with their users, both to ask them what they want and to present the results when they are available. The computer term for this topic is I/ O ( s h o r t f o r " i n p u t / o u t p u t " ) . We’ll start by getting information from the keyboard and displaying ______________________________________________________________________________ 19. I’ll be more specific later, when we have seen some examples. 20. For example, if you wanted to insert a " in a String, you would have to use \", because just a plain " would indicate the end of the String. That is, if you were to set a String to the literal "This is a \"String\".", it would display as: This is a "String". IN AND OUT 67 it on the screen; later, we’ll go over the more complex I/O functions that allow us to read and write data on the disk. The final line of code in Figure 3.17, System.out.println( s1 + s2 + c2 + s3);, displays information on the screen. In this case, the output will be "Congratulations! You got an A on the test.". How does this occur exactly? The first construct on this line is System.out, which is a predefined Java variable that is "connected" to the screen. This variable has several built-in ways to display information; the one we’re using here is called println, which is short for "print and move to the next line". The value of the expression inside the parentheses after println will be displayed on the screen (the print part of println), and the cursor will be moved to the beginning of the next line (the ln part of println).println That explains why we’ll see some output on the screen, but it doesn’t explain the exact output that we’ll see, which depends on what’s inside the parentheses. In this case, that’s s1 + s2 + c2 + s3. I don’t blame you if you have some trouble understanding how we can "add" Strings and chars together; that’s not exactly intuitively obvious to the casual observer. What the + means here actually is something like "add", but more precisely it means "add some text data to the end of some other text data". In the current case, that whole expression produces the following sequence of events: 1. 2. 3. Take the contents of s2, and add that to the end of the contents of s1. Since s1 contains "Congratulations! " and s2 contains "You got an ", the result will be the value "Congratulations! You got an ". Take the results of the previous operation and add the contents of c1 (’A’) to its end, resulting in the value "Congratulations! You got an A". Take the results of the previous operation and add the contents of s3 (" on the test") to its end, resulting in the value "Congratulations! You got an A on the test." Finally, the resulting value will be displayed on the screen and the cursor will be moved to the next line. Susan had an incisive observation about this example program. Susan: What if the student got a ’B’? Then the second sentence would read "You got an B", which is incorrect. Steve: That’s a good point. I think I’ll make fixing that problem into an exercise. Problem Algorithms Java Interpreter Hardware 68 Chapter 3: BASICS OF PROGRAMMING So much for (simple) output. Input from the keyboard is almost as simple. Let’s modify our little sample to use it, as shown in Figure 3.19. import WAJ.*; public class Basic02 { public static void main( String args[ ] ) { char c1; char c2; String s1; String s2; String s3; String s4; System.out.print("What is your name? "); s4 = RWVar.readString(System.in); c1 = ’A’; c2 = c1; Problem Algorithms Java Interpreter Hardware s1 = "Congratulations, "; s2 = "! You got an "; s3 = " on the test."; System.out.println( s1 + s4 + s2 + c2 + s3); } } Figure 3.19: Simple input (code\Basic00\Basic02.java) There are a couple of new things in this program. Let’s start at the top with the line import WAJ.*;. I’ll explain exactly what that line means later, but for now it’s sufficient to know that this tells the Java compiler where to find some code that I’ve written to make it easier for you to write simple Java programs. The line System.out.print("What is your name? "); is almost like the simple output statement we’ve seen before, with two exceptions. First, it displays a constant String value on the screen ( "What is your name? "), rather than the calculated value we saw earlier. Second, instead of using the println operation, it uses print. The difference between these two is simply that print doesn’t move the cursor to the next line, as println does. In the current case, we want to let the user of IN AND OUT 69 the program type his or her answer in on the same line as the question rather than on the next line, so this is appropriate. The next line that has anything unfamiliar is the one that says s4 = RWVar.readString(System.in);. You should be able to guess that the first p a r t o f t h a t l i n e , s 4 = , s e t s t h e String variable s4 t o s o m e v a l u e , b u t what value is that exactly? It’s whatever you type in when you see the question What is your name? on the screen. That’s what RWVar.readString is for: It takes input up to the next ENTER, and turns it into a String. In this case, we use that String to set the value of the String variable on the left of the equals sign. There are similar ways to read other kinds of variables, like ints, as we’ll see later. But what about System.in? As its name suggests, this is the counterpart to System.out; it supplies characters from the keyboard rather than writing characters to the screen. Susan had some questions about these little programs, beginning with the question of case sensitivity. Susan: Are the words such as char and public case sensitive? I had capitalized a few of them just out of habit because they begin the sentence and I am not sure if that was the reason the compiler gave me so many error messages. I think after I changed them I reduced a few messages. Steve: Everything in Java is case sensitive. That includes keywords like char, public, and so on, as well as your own variables. That is, if you have a variable called Name and another one called name, those are completely different and unrelated to one another. This also applies to System.in and System.out; you have to write them just as they appear here, or the compiler won’t be able to figure out what you mean. Susan: What does RWVar stand for? Steve: "Reading and writing variables". Susan: Why is the word System used? Steve: Because those variables (System.out and System.in) are supplied by the system. Susan: The WAJ.* is confusing to me; what does it do? Steve: It tells the compiler that we want to be able to use some facilities that are stored under the name WAJ (which is short for "Who’s Afraid of Java?"). That’s where I’ve put RWVar, so if we hadn’t included the line Problem Algorithms Java Interpreter Hardware 70 Chapter 3: BASICS OF PROGRAMMING import WAJ.*, we wouldn’t be able to use RWVar.readString and the other RWVar facilities. If Only You Knew In our examples so far, the program always executes the same statements in the same order. However, any real program is going to need to alter its behavior according to the data it is processing. For example, in a banking application, it might be necessary to send out a notice to a depositor whenever the balance in a particular account drops below a certain level; or perhaps the depositor would just be charged some exorbitant fee in that case. Either way, the program has to do something different depending on the balance. In particular, let’s suppose that the "Absconders and Defaulters National Bank" has a minimum balance of $10,000. Furthermore, let’s assume that if you have less than that amount on deposit, you are charged a $20 "service charge". However, if you are foolish enough to leave that ridiculous amount of money on deposit, then they will graciously allow you to get away with not paying them for the privilege of lending them your money (without interest, of course). To determine whether or not you should be charged for your checking account, the bank can use an if statement, as shown in Figure 3.20. Problem Algorithms Java Interpreter import WAJ.*; Hardware public class Basic03 { public static void main( String args[ ] ) { int balance; System.out.print("Please enter your bank balance: "); balance = RWVar.readInt(System.in); if (balance < 10000) System.out.println("Please remit $20 service charge."); else System.out.println("Have a nice day!"); } } Figure 3.20: Using an if statement (code\Basic03\Basic03.java) IF ONLY YOU KNEW 71 This program starts by displaying the line Please enter your bank balance: on the screen. Then it uses the line balance = RWVar.readInt(System.in); to allow you to type in your balance, followed by the ENTER key (so it knows when you’re done). This RWVar.readInt facility is similar to our previous use of RWVar.readString, except that it read an int rather than a String value. Of course, System.in still supplies data from the keyboard. Next, the conditional statement checks whether you’re a "good customer". If your balance is less than $10,000, the next statement is executed, which displays the line Please remit $20 service charge.21 If the condition is false (that is, you have at least $10,000 in the bank), the computer skips the statement that asks you to remit $20; instead, it executes the one after the else, which tells you to have a nice day. That’s what else is for; it specifies what to do if the condition specified in the if statement is false (that is, not true). If you typed in a number 10000 or higher, the program would display the line22 Have a nice day! You don’t have to specify an else if you don’t want to. In that case, if the if condition isn’t true, the program just goes to the next statement as though the if had never been executed. While We’re on the Subject The while statement is another way of affecting the order of program execution. This conditional statement executes the statement under its control as long as a certain condition is true. Such potentially repeated execution is called a loop; a loop controlled by a while s t a t e m e n t i s c a l l e d , l o g i c a l l y e n o u g h , a while loop . F i g u r e 3 . 2 1 i s a ______________________________________________________________________________ 21. This explanation assumes that the "10000" is the balance in dollars. Of course, this doesn’t account for the possibility of balances that aren’t a whole number of dollars. I’ll mention a possible solution to this problem later. 22. Please note that you cannot include the "," in a number in your programs, whether you’re writing the program or entering data when it runs. You have to type "10,000" as "10000" or you’ll get an error either at compile time or when the program is running. Problem Algorithms Java Interpreter Hardware 72 Chapter 3: BASICS OF PROGRAMMING program that uses a while loop to challenge the user to guess a secret number from 0 to 9, and keeps asking for guesses until the correct answer is entered. import WAJ.*; public class Basic04 { public static void main( String args[ ] ) { int Secret; int Guess; Secret = 3; System.out.println("Try and guess my number. Hint: It’s from 0 to 9"); Guess = RWVar.readInt(System.in); while (Guess != Secret) { System.out.println("Sorry, that’s not correct."); Guess = RWVar.readInt(System.in); } Problem Algorithms Java System.out.println("You guessed right!"); Interpreter } } Hardware Figure 3.21: Using a while statement (code\Basic04\Basic04.java) There are a few wrinkles here that we haven’t seen before. Although the while statement itself is fairly straightforward, the meaning of its condition != isn’t intuitively obvious. However, if you consider the problem we’re trying to solve, you’ll probably come to the (correct) conclusion that != means "not equal", since we want to keep asking for more guesses while the Guess is not equal to our Secret number.23 Since there is a comparison operator that tests for "not equal", you might want to know how to test for "equal" as well. The answer is that you can test whether two int values are equal by using the == operator; we’ll see in the next chapter why we can’t use = for this task. ______________________________________________________________________________ 23. You may be wondering why we need parentheses around the expression Guess != Secret. The conditional expression has to be in parentheses so that the compiler can tell where it ends and the statement to be controlled by the while begins. WHILE WE’RE ON THE SUBJECT 73 Would an if statement with an else clause would serve as well as the while? After all, if is used to select one of two alternatives, and the else could select the other one. The answer is that this would allow the user to take only one guess; the while loop lets the user try again as many times as needed to get the right answer. Now you should have enough information to be able to write a simple program of your own, as Susan asked to do at this point. Susan: Based on what you have presented in the book so far, send me a setup, an exercise for me to try to figure out how to program, and I will give it a try. I guess that is the only way to do it. I can’t even figure out a programmable situation on my own. So if you do that, I will do my best with it, and that will help teach me to think. (Can that be?) Now, if you do this, make it simple, and no tricks. Of course, I did give her the exercise she asked for (exercise 1), but also of course, that didn’t end the matter. She decided to add her own flourish, which resulted in exercise 2. Separate but Possibly Equal It would be convenient to compare two variables of any type by just using == or !=, the former to tell whether they are equal and the latter whether they are unequal. Unfortunately, this won’t work. As we’ll see in much more detail later, most types of variables in Java fall in one of two categories: primitive (sometimes called native) and user-defined. Strings are an exception, being sort of a cross between these two types, acting more like the latter. You can indeed compare primitive variables such as ints and chars by using the == or != operators; however, to compare non-primitive variables, including Strings, you have to use the equals facility. In particular, to compare whether two Strings, a and b, have the same value, you have to write if (a.equals(b)). Does this seem much clumsier than just writing if (a == b), as you would do to compare two ints? Many diehard Java supporters claim that it’s much better to have two different ways to compare variables, depending on whether they are actually part of the language (primitive) or are added on afterwards (user-defined and Strings). Personally, I think it would be better to be able to treat all types of variables in the same way, but maybe I’m missing something here.24 ______________________________________________________________________________ 24. And maybe pigs can fly. Problem Algorithms Java Interpreter Hardware 74 Chapter 3: BASICS OF PROGRAMMING How to Do the Exercises We’re ready for the exercises that Susan asked for, along with some others of the same general level of difficulty. To write programs to solve these exercises, see the section titled "Writing and compiling your own programs" in the file "\readme.txt" on the CD in the back of the book. Once you have followed those instructions to write and run a program, it may work the first time you try them. However, if it doesn’t (which is quite likely), you will need more information about what’s happening in the program. Fortunately, Java compilers come with a debugger, which you can use to trace execution in your program. To use the debugger for your program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. Exercises 1. Problem Algorithms Java Interpreter Hardware 2. 3. 4. 5. 6. Write a program that asks the user to type in the number of people that are expected for dinner, not counting the user. Assuming that the number typed in is n, display a message that says "A table for (n+1) is ready.". For example, if the user types 3, display "A table for 4 is ready.". Modify the program from exercise 1 to display an error message if the number of guests is more than 20. Write a program that asks the user to type in his or her name and age. If the age is less than 47, then indicate that the user is a youngster; otherwise, that he or she is getting on in years. Write a program that asks the user whether Susan is the world’s most tenacious novice. If the answer is "yes", then acknowledge the user’s correct answer; if the answer is "no", then indicate that the answer is erroneous. If neither "yes" nor "no" is typed in, chastise the user for not following directions. Write a program that calculates how much extra allowance a teenager can earn by doing extra chores. Her allowance is calculated as $10 if she does no extra chores; she gets $1 additional for each extra chore she does. Modify the program in Figure 3.17 to ask for the grade and display it correctly whether it is an A, B, C, D, or F. Answers to exercises can be found at the end of the chapter. JUST UP THE BLOCK 75 Just up the Block Our most recent programming example has contributed another item to our arsenal of programming weapons; namely, the ability to group several statements into one logical section of a program. That’s the function of the curly braces, { and }. The first one of these starts such a section, called a block, and the second one ends the block. Because the two statements after the while are part of the same block, they are treated as a unit; both are executed if the condition in the while is true, and neither is executed if it is false. A block can be used anywhere that a statement can be used, and is treated in exactly the same way as if it were one statement.25 At the Fair Now we’re ready to write a program that vaguely resembles a solution to a real problem. We’ll start with a simple, rural type of programming problem. Imagine that you are at a county fair. The contest for the heaviest pumpkin is about to get underway, and the judges have asked for your help in operating the "pumpkin scoreboard". This device has one slot for the current pumpkin weight (the CurrentWeight slot), and another slot for the highest weight so far (the HighestWeight slot); each slot can hold three digits from 0 to 9 and therefore can indicate any weight from 0 to 999. The judges want you to maintain an up-to-date display of the current weight and of the highest weight seen so far. The weights are expressed to the nearest pound. How would you go about this task? Probably the best way to start is by setting the number in both slots to the first pumpkin weight called out. Then, as each new weight is called out, you change the number in the CurrentWeight slot to match the current weight; if it’s higher than the number in the HighestWeight slot, you change that one to match as well. Of course, you don’t have to do anything to the HighestWeight slot when a weight less than the previous maximum is called out, because that pumpkin can’t be the winner. How do we know when we are done? Since a pumpkin ______________________________________________________________________________ 25. If you look at someone else’s Java program, you’re likely to see a different style for lining up the {} to indicate where a block begins and ends. As you’ll notice, my style puts the { and } on separate lines rather than running them together with the code they enclose, to make them stand out, and indents them further than the conditional statement. I find this the clearest, but this is a matter where there is no consensus. The compiler doesn’t care how you indent your code or whether you do so at all; it’s a stylistic issue. Problem Algorithms Java Interpreter Hardware 76 Chapter 3: BASICS OF PROGRAMMING entered in this contest has to have a weight of at least 1 pound, you enter 0 as the weight when the contest is over. At that point, the number in the HighestWeight slot is the weight of the winner. The procedure you have just imagined performing can be expressed a bit more precisely by the following algorithm: 1. 2. 3. 4. 5. Ask for the first weight. Set the number in the CurrentWeight slot to this value. Copy the number in the CurrentWeight slot to the HighestWeight slot. Display both the current weight and the highest weight so far (which are the same, at this point). While the CurrentWeight value is greater than 0 (that is, there are more pumpkins to be weighed), do steps 5a to 5d. a. Ask for the next weight. b. Set the number in the CurrentWeight slot to this weight. c. If the number in the CurrentWeight slot is greater than the number in the HighestWeight slot, copy the number in the CurrentWeight slot to the HighestWeight slot. d. Display the current weight and the highest weight so far. Stop. The number in the HighestWeight slot is the weight of the winner. 6. Problem Algorithms Figure 3.22 is the translation of our little problem into Java. Susan Java Interpreter h a d a q u e s t i o n a b o u t t h e f o r m a t t i n g o f t h e o u t p u t s t a t e m e n t System.out.println("Highest weight " + HighestWeight);. Hardware Susan: Why do we need both "Highest weight" and HighestWeight in this line? Steve: Because "Highest weight" is displayed on the screen to tell the user that the following number is supposed to represent the highest weight seen so far. On the other hand, HighestWeight is the name of the v a r i a b l e t h a t h o l d s t h a t i n f o r m a t i o n , s o i n c l u d i n g HighestWeight in the output statement will result in displaying the highest weight we’ve seen so far on the screen. Of course, the same analysis applies to the next line, which displays the label "Current weight" and the value of the variable CurrentWeight. AT THE FAIR 77 English Java ------------------------------------------------------------------------------------------------------------------First, we have to tell the compiler what we’re up to in this program. ------------------------------------------------------------------------------------------------------------------Tell the compiler | where to find the | RWVar input and | output code | import WAJ.*; | We’re defining | public class Pump1 a class named Pump1 | | This is the main | { part of the program | public static void main( String args[ ] ) | Start of program | { | Define variables | int CurrentWeight; | int HighestWeight; | ------------------------------------------------------------------------------------------------------------------Here’s the start of the "working" code: ------------------------------------------------------------------------------------------------------------------| Ask for the first | weight | System.out.print("Please enter the first weight: "); | Set the number in | the CurrentWeight | slot to the value | entered by the user | CurrentWeight = RWVar.readInt(System.in); | Copy the number in | the CurrentWeight | slot to the | HighestWeight slot | HighestWeight = CurrentWeight; | Display the current | System.out.println("Current weight " + CurrentWeight); and highest weights | System.out.println("Highest weight " + HighestWeight); | Figure 3.22: A Java Program (code\Pump1\Pump1.java) Problem Algorithms Java Interpreter Hardware 78 Chapter 3: BASICS OF PROGRAMMING While the number in the CurrentWeight slot is greater than 0 (i.e., there are more pumpkins to be weighed) | | | | | | while (CurrentWeight > 0) | Start repeated | { steps | | Ask for the next | weight | System.out.print("Please enter the next weight: "); | Set the number in | the CurrentWeight | slot to this value | CurrentWeight = RWVar.readInt(System.in); | If the number in | the CurrentWeight | slot is more than | the number in the | HighestWeight slot, | if (CurrentWeight > HighestWeight) | then copy the | Problem | Algorithms number in the CurrentWeight slot | Java to the | Interpreter HighestWeight slot | HighestWeight = CurrentWeight; Hardware | Display the current | System.out.println("Current weight "+CurrentWeight); and highest weights | System.out.println("Highest weight "+HighestWeight); | End repeated steps | in while loop | } | ------------------------------------------------------------------------------------------------------------------We’ve finished the job; now to clean up ------------------------------------------------------------------------------------------------------------------| End of main part of | program | } | End of class | } | ------------------------------------------------------------------------------------------------------------------- Figure 3.22 continued Susan had some questions about variable names. AT THE FAIR 79 Susan: Tell me again what the different ints mean in this figure. I am confused; I just thought an int held a variable like i. What is going on when you declare HighestWeight an int? So do the "words" HighestWeight work in the same way as i? Steve: An int is a variable. The name of an int is made up of one or more characters; the first character must be a letter or an underscore (_), whereas any character after the first must be either a letter, an underscore, a dollar sign, or a digit from 0 to 9. To define an int, you write a line that gives the name of the int. This is an example: int HighestWeight;. Susan: OK, but then how does i take 4 bytes of memory and how does HighestWeight take up 4 bytes of memory? They look so different, how do you know that HighestWeight will fit into an int? Steve: The length of the names that you give variables has nothing to do with the amount of storage that the variables take up. After the compiler gets through with your program, there aren’t any variable names; each variable that you define in your source program is represented by the address of some area of storage. If the variable is an int, that area of storage is 4 bytes long; if it’s a char (or a short), the area of storage is 2 bytes long. Susan: Then where do the names go? They don’t go "into" the int? Steve: A variable name doesn’t "go" anywhere; it tells the compiler to set aside an area of memory of a particular length that you will refer to by a given name. If you write int xyz; you’re telling the compiler that you are going to use an int (that is, 4 bytes of memory) called xyz. Susan: If that is the case, then why bother defining the int at all? Steve: So that you (the programmer) can use a name that makes sense to you. If the compiler had to assign names itself, it wouldn’t be very likely to give variables names that you would like! The topic of the import statement was the cause of some discussion with Susan. Here’s the play by play: Susan: Is import a command? Steve: Right; it’s a command to the compiler. Susan: Then what are the words we have been using for the most part called? Are those just called code or just statements? Can you make a list of commands to review? Problem Algorithms Java Interpreter Hardware 80 Chapter 3: BASICS OF PROGRAMMING Steve: The words that are defined in the language, such as if, while, for, and the like, are called keywords. User-defined names such as variable names are called identifiers. Susan: So import WAJ.* is a code to tell the compiler that it is using info from the WAJ library? S t e v e: Essentially correct; to be more precise, when we import WAJ.*, we’re telling the compiler to look into the WAJ directory for definitions that we’re going to use. Susan: Then that WAJ file contains the secondary code of byte codes to transform RWVar.readInt and RWVar.readString into something workable? Steve: Actually, it’s in the WAJ directory, not the WAJ file, but you’re on the right track. Susan: So the import statement file directs the compiler to that section in the library where that byte code is stored? In other words, it is like telling the compiler to look in section XXX to find the byte code? Steve: Right. Problem Finally, the closing curly brace, }, tells the compiler that it can stop Algorithms compiling the current block, which in this case is the one called main. Java Interpreter Without this marker, the compiler would tell us that we have a missing }, which of course would be true. Hardware Novice Alert Susan decided a little later in our collaboration that she wanted to try to reproduce this program just by considering the English description, without looking at my solution. She didn’t quite make it without peeking, but the results are illuminating nevertheless. Susan: What I did was to cover your code with a sheet of paper and just tried to get the next line without looking, and then if I was totally stumped, I would look. Anyway, when I saw that if statement, then I knew what the next statement would be but I am still having problems with writing backwards. For example: if (CurrentWeight > HighestWeight) HighestWeight = CurrentWeight; NOVICE ALERT 81 That is so confusing because we just want to say that if the current weight is higher than the highest weight, then the current weight will be the new highest weight, so I want to write CurrentWeight = HighestWeight. Anyway, when I really think about it, I know it makes sense to do it the right way; I’m just having a hard time thinking like that. Any suggestions on how to think backward? Steve: What that statement means is "set H i g h e s t W e i g h t to the current value of CurrentWeight". The point here is that = does not mean "is equal to"; it means "set the variable to the left of the = to the value of the expression to the right of the =". It’s a lousy way of saying that, but that’s what it means. Susan: Anyway, then maybe I am doing something wrong, and I am tired, but after I compiled the program and ran it, I saw that the HighestWeight label was run in together with the highest number and the next sentence, which said "Please enter the next weight". All those things were on the same line and I thought that looked weird; I tried to fix it but the best I had the stamina for at the moment was to put a space between the " and the P, to at least make a separation. Steve: It sounds as though you need some printlns in there to separate the lines. Now it’s time for some review on what we’ve covered in this chapter. Review We started out by discussing the tremendous reliability of computers; w h e n e v e r y o u h e a r " i t’s the computer’s fault", the overwhelming likelihood is that the software is to blame rather than the hardware. Then we took a look at the fact that, although computers are calculating engines, many of the functions for which we use them don’t have much to do with numeric calculations; for example, the most common use of computers is probably word processing, which doesn’t use much in the way of addition or subtraction. Nevertheless, we started out our investigation of programming with numeric variables, which are easier to understand than non-numeric ones. To use variables, we need to write a Java program, which consists primarily of a list of operations to be performed by the computer, along with directions that influence how these operations are to be translated into byte codes. Problem Algorithms Java Interpreter Hardware 82 Chapter 3: BASICS OF PROGRAMMING That led us into a discussion of why and how our Java program is translated into byte codes by a compiler. We examined an example program that contained simple source code statements, including some that define variables and others that use those variables and constants to calculate results. We covered the symbols that are used to represent the operations of addition, subtraction, multiplication, division, and assignment, which are +, –, *, /, and =, respectively. Whereas the first four of these should be familiar to you, the last one is a programming notion rather than a mathematical one. This may be confusing because the operation of assignment is expressed by the = sign, but is not the same as mathematical equality. For example, the statement x = 3; does not mean "x is equal to 3", but rather "set the variable x to the value 3". Then we spent some time pretending to be a compiler, to see how a simple Java program looks from that point of view, in order to improve our understanding of what the compiler does with our programs. This exercise involved keeping track of the locations of variables and instructions, and watching the effect of the instructions on the stack and variables. During this exploration of the machine, we got acquainted with the byte-code representation of instructions, which is the actual form of a program that the Java interpreter can Problem Algorithms understand. After a detailed examination of what the compiler does with our source code at compile time, we followed what would Java Interpreter happen at run time (that is, if the sample program were actually executed by the Java interpreter). Hardware Then we began to look at two data types that can hold nonnumeric data, namely, the char and the String. The char occupies to 2 bytes of storage, corresponding to one character of data. Examples of appropriate values for a char variable include letters (a–z, A–Z), digits (0–9), and special characters (e.g., ! @ # $ %), as well as "nonprintable" characters such as the "space", which causes output to move to the next character position on the screen. One char isn’t much information, so we often want to deal with groups of them as a single unit; an example would be a person’s name. This is the province of the String variable type: Variables of this type can handle an indefinitely long group of chars. At the beginning of our sample program for Strings and chars, we encountered the line public static void main(String args[ ]), which indicates where we want to start executing our program. A Java program always starts execution at the place indicated by such a line. As we continued looking at the sample program for Strings and chars, we saw how to assign literal values to both of these types, and noticed that two different types of quotes are used to mark off the REVIEW 83 literal values: the single quote (’), which is used in pairs to surround a literal char value consisting of exactly one char, such as ’a’; and the double quote ("), which is used in pairs to surround a literal String value such as "This is a test". This led us to the discussion of the way in which the compiler regulates our access to variables by their type, which is defined at compile time. This is called the type system; Java uses this static type checking to help make Java programs more robust than programs written in languages that use dynamic type checking, where these errors are not detected until run time. After a short discussion of some of the special characters that have a predefined meaning to the compiler, we took an initial glance at the mechanisms that allow us to get information into and out of the computer, known as I/O. We looked at the println function, which provides display on the screen when coupled with the built-in destination called System.out. Immediately afterwards, we encountered the input function RWVar.inputString and its partner System.in, which team up to give us input from the keyboard. Next, we went over some program organization concepts, including the if statement, which allows the program to choose between two alternatives; the while statement, which causes another statement to be executed while some condition is true; and the block, which allows several statements to be grouped together into one logical statement. Blocks are commonly used to enable several statements to be controlled by an if or while statement. At last we were ready to write a simple program that does something resembling useful work, and we did just that. The starting point for this program, as with all programs, was to define exactly what the program should do; in this case, the task was to keep track of the pumpkin with the highest weight at a county fair. The next step was to define a solution to this problem in precise terms. Then we broke the solution down into steps small enough to be translated directly into Java. Of course, the next step after that was to do that translation. Finally, we went over the Java code, line by line, to see what each line of the program did. Now that the review is out of the way, we’re about ready to continue with some more Java in Chapter 4. First, though, let’s step back a bit and see where we are right now. Problem Algorithms Java Interpreter Hardware 84 Chapter 3: BASICS OF PROGRAMMING Conclusion We’ve come a long way from the beginning of this chapter. Starting from basic information on how the hardware works, we’ve made it through our first actual, runnable program. By now, you should have a much better idea whether you’re going to enjoy programming (and this book). Assuming you aren’t discouraged on either of these points, let’s proceed to gather some more tools, so we can undertake a bigger project. Answers to Exercises 1. Problem Algorithms Java Interpreter Hardware Susan’s answer to this problem follows, after a short discussion about formatting the output of this program to make it look better. While we’re on the topic of formatting, the reason that this program uses two lines to produce the sentence "Please type in the number of guests of your dinner party." is so that the program listing will fit on the page properly. If you prefer, you can combine those into one line that says System.out.print("Please type in the number of guests of your dinner party. ");. Of course, this also applies to the next exercise. Here’s that conversation about formatting the output of this program to make it look better: Steve: By the way, you might want to add a " " in front of the is in is ready, so that the number doesn’t run up against the is. That would make the line look like this: System.out.println("A table for " + n + " is ready. "); Susan: Okay. And Figure 3.23, as promised, is Susan’s answer to the first dinner party exercise. ANSWERS TO EXERCISES 85 import WAJ.*; public class Basic05 { public static void main( String args[ ] ) { int n; System.out.print("Please type in the number of guests "); System.out.print("of your dinner party. "); n = RWVar.readInt(System.in); n = n + 1; System.out.println("A table for " + n + "is ready. "); } } Figure 3.23: First dinner party program (code\Basic05\Basic05.java) To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Basic05". 2. Susan didn’t have too much trouble with this one, but did have some questions on how to use else. First, here’s our discussion. Steve: Congratulations on getting your program to work! Susan: Now, let me ask you this: Can you ever modify else? That is, could I have written else (n>20), or does else always stand alone? Steve: You can make the controlled block of an if statement or an else statement another if or else. In fact, you can have as many "nested" if or else statements as you wish; however, it’s best to avoid very deep nesting because it tends to confuse the next programmer who has to read the program. Figure 3.24 is an example of an else whose controlled block is an if statement. Problem Algorithms Java Interpreter Hardware 86 Chapter 3: BASICS OF PROGRAMMING if (x < y) { System.out.println("x is less than y"); else { if (x > y) System.out.println("x is greater than y"); else System.out.println("x must be equal to y!"); } } Figure 3.24: else if example As promised, Figure 3.25 is Susan’s answer to exercise 2. import WAJ.*; Problem Algorithms Java Interpreter Hardware public class Basic06 { public static void main( String args[ ] ) { int n; System.out.print("Excluding yourself, please type the "); System.out.print("number of guests in your dinner party.\n"); n = RWVar.readInt(System.in); if (n>20) System.out.println("Sorry, your party is too large. "); else { n = n + 1; System.out.println("A table for " + n + " is ready. "); } } } Figure 3.25: Second dinner party program (code\Basic06\Basic06.java) To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Basic06". ANSWERS TO EXERCISES 3. 87 The program should look like Figure 3.26. import WAJ.*; public class Basic07 { public static void main( String args[ ] ) { String name; int age; System.out.print("What is your name? "); name = RWVar.readString(System.in); System.out.println("Thank you, " + name); System.out.print("What is your age? "); age = RWVar.readInt(System.in); if (age < 47) System.out.println("My, what a youngster!"); else System.out.println("Hi, Granny!"); } } Figure 3.26: Name and age program (code\Basic07\Basic07.java) One point that might be a bit puzzling in this program is why it’s not necessary to use println in the lines that send data to System.out before we ask the user for input. For example, in the sequence System.out.print("What is your age? "); age = RWVar.readInt(System.in); how do we know that the literal String "What is your name? " has been displayed on the terminal before the user has to type in the answer? Obviously, it would be hard for the user to answer our request for information without a clue as to what we’re asking for. As it happens, this is handled by the RWVar.readInt facility. When we use that facility to do output to the screen and input from the keyboard, we can be sure that any screen output we have already requested will be displayed before any input is requested from the user via the keyboard. Problem Algorithms Java Interpreter Hardware 88 Chapter 3: BASICS OF PROGRAMMING 4. Figure 3.27 shows Susan’s program, which is followed by our discussion. import WAJ.*; public class Basic08 { public static void main( String args[ ] ) { String answer; System.out.print("Please respond to the following statement "); System.out.print("with either yes or no\n"); System.out.print("Susan is the world’s most tenacious novice.\n"); answer = RWVar.readString(System.in); if (answer.equals("yes") == false) if (answer.equals("no") == false) System.out.println("Please answer with either yes or no."); Problem Algorithms Java Interpreter Hardware if (answer.equals("yes")) System.out.print("Your answer is correct\n"); if (answer.equals("no")) System.out.print("Your answer is erroneous\n"); } } Figure 3.27: Novice program (code\Basic08\Basic08.java) Susan: Steve, look at this. It even runs! Also, I wanted to ask you one more question about this program. I wanted to put double quotes around the words yes and no in the third output statement because I wanted to emphasize those words, but I didn’t know if the compiler could deal with that so I left it out. Would that have worked if I had? Steve: Not if you just added quotes, because " is a special character that means "beginning or end of literal String". Here’s what you would have to do to make it work: System.out.println("Please answer with either \"yes\" or \"no\"."); The \ is a way of telling the compiler to treat the next character differently from its normal usage. In this case, we are telling the compiler to treat the special character " as "not special"; that is, \" means ANSWERS TO EXERCISES 89 "just the character double quote, please, and no nonsense". This is called an escape, because it allows you to get out of the trap of having a " mean something special. We also use the \ to tell the compiler to treat a "nonspecial" character as "special"; for example, we use it to make up special characters that don’t have any visual representation. An example is ’\n’, the "newline" character, which means "start a new line on the screen". Susan : S o i f w e w a n t t o w r i t e s o m e c h a r a c t e r t h a t m e a n s s o m e t h i n g "special", then we have to use a \ in front of it to tell the compiler to treat it like a "regular" character? Steve: Right. Susan: And if we want to write some character that is "regular" and make it do something "special", then we have to use a \ in front of it to tell the compiler that it means something "special"? That’s weird. Steve: It may be weird, but that’s the way it works. Susan: I just now got it. I was going to say, why would you put the first quotation mark before the slash, but now I see. Since you are doing a newline character, you have to have quotes on both sides to surround it which you don’t usually have to do because the first quotes are usually started at the beginning of the sentence, and in this case the quote was already ended. OK, thanks for clearing that up. Steve: You’ve got it. Susan: Another thing I forgot is how you refer to the statements in () next to the if keywords; what do you call the info that is in there? Steve: The condition. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Basic08". Problem Algorithms Java Interpreter Hardware 90 Chapter 3: BASICS OF PROGRAMMING 5. Figure 3.28 is Susan’s version of this program. import WAJ.*; public class Basic09 { public static void main( String args[ ] ) { int x; System.out.print("Elena can increase her $10 allowance each week "); System.out.println("by adding new chores."); System.out.print("For every extra chore Elena does, she gets "); System.out.println("another dollar."); System.out.println("How many extra chores were done? "); x = RWVar.readInt(System.in); if (x==0) { System.out.print("There is no extra allowance for Elena "); System.out.println("this week. "); } else { x = x + 10; System.out.print("Elena will now earn " + x); System.out.println(" dollars this week."); } Problem Algorithms Java Interpreter Hardware } } Figure 3.28: Allowance program (code\Basic09\Basic09.java) To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Basic09". ANSWERS TO EXERCISES 6. 91 Figure 3.29 is an answer to this problem. import WAJ.*; public class Basic10 { public static void main( String args[ ] ) { char c1; String grade; String s1; String s2; String s3; System.out.print("What grade did you get? "); grade = RWVar.readString(System.in); if (grade.equals("A")) s1 = "Congratulations! "; else if (grade.equals("B")) s1 = "Congratulations. "; else s1 = ""; if (grade.equals("A")) s2 = "You got an "; else s2 = "You got a "; s3 = " on the test."; System.out.println( s1 + s2 + grade + s3); } } Figure 3.29: Grading program (code\Basic10\Basic10.java) To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Basic10". Problem Algorithms Java Interpreter Hardware Chapter 4 More Basics A Modest Proposal Now that we have seen how to write a simple program in Java, it’s time to acquire some more tools. We’ll extend our example program from Chapter 3 for finding the heaviest pumpkin. Eventually, we want to provide the weights of the three heaviest pumpkins, so that first, second, and third prizes can be awarded. It might seem that this would require just a minor modification of the previous program, in which we would keep track of the heaviest so far, second heaviest so far, and third heaviest so far, rather than merely the heaviest so far. However, this modification turns out to be a bit more complicated than it seems. Since this book is intended to teach you how to program using Java, rather than just how to use the Java language, it’s worth investigating why this is so. First, though, here are some definitions and objectives for this chapter. Definitions An array is a group of elements of the same type; for example, we can create an array of chars. The array name corresponds to the address of the first of these elements; the other elements follow the first one immediately in memory. We can refer to the individual elements by their indexes; so, if we have an array of chars called m_Data, m_Data[i] refers to the ith char in the array. An element is one of the variables that makes up an array. 93 94 Chapter 4: MORE BASICS A for statement is a loop control statement that causes its controlled block to be executed while a specified logical expression (the continuation expression) is true. It also provides for a starting expression to be executed before the first execution of the controlled statement, and a modification expression to be executed after every execution of the controlled statement. For example, in the for statement for (i = 0; i < 10; i ++), the initialization expression is i = 0, the continuation expression is i < 10, and the modification expression is i ++. Objectives for This Chapter By the end of this chapter, you should: 1. 2. 3. Problem Algorithms 4. Java Interpreter 5. Hardware 6. 7. Understand the likelihood of error in even a small change to a program. Be aware that even seemingly small changes in a problem can result in large changes in the program that solves the problem. Have some understanding of the type of thinking needed to solve problems with programming. Understand the selection sorting algorithm for arranging values in order. Understand how to use an array to maintain a number of values under one name. Be able to use the for statement to execute program statements a (possibly varying) number of times. Be familiar with the arithmetic operators ++ and +=, which are used to modify the value of variables. Algorithmic Thinking Let’s take our program modification one step at a time, starting with just the top two weights. Figure 4.1 is one possible way to handle this version of the problem. ALGORITHMIC THINKING 95 import WAJ.*; public class Pump1a { public static void main( String args[ ] ) { int CurrentWeight; int HighestWeight; int SecondHighestWeight; //1 System.out.print("Please enter the first weight: "); CurrentWeight = RWVar.readInt(System.in); HighestWeight = CurrentWeight; SecondHighestWeight = 0; System.out.println("Current weight " + CurrentWeight); System.out.println("Highest weight " + HighestWeight); //2 while (CurrentWeight > 0) { System.out.print("Please enter the next weight: "); CurrentWeight = RWVar.readInt(System.in); if (CurrentWeight > HighestWeight) { SecondHighestWeight = HighestWeight; HighestWeight = CurrentWeight; } System.out.println("Current weight " + CurrentWeight); System.out.println("Highest weight " + HighestWeight); System.out.print("Second highest weight "); System.out.println(SecondHighestWeight); } //3 //4 //5 } } Figure 4.1: Finding the top two weights, first try (code\Pump1a\Pump1a.java) The reasons behind some of the new code should be fairly obvious, but we’ll go over them anyway. The new lines have numbered comments //1 through //5 so you can find them easily. First, of course, we need a new variable, SecondHighestWeight, to hold the c u r r e n t v a l u e o f t h e s e c o n d - h i g h e s t w e i g h t w e’ve seen so far (line //1). Then, when the first weight is entered, the statement SecondHighestWeight = 0; sets the SecondHighestWeight to 0 (line //2). After all, there isn’t any second-highest weight when we’ve only seen one weight. The first nonobvious change is the addition of the statement Problem Algorithms Java Interpreter Hardware 96 Chapter 4: MORE BASICS SecondHighestWeight = HighestWeight;, which copies the old HighestWeight t o SecondHighestWeight, w h e n e v e r t h e r e ’ s a n e w h i g h e s t w e i g h t ( l i n e //3). On reflection, however, this should make sense; when a new high is detected, the old high must be the second-highest value (so far). Also, we have to copy the old HighestWeight to SecondHighestWeight before we change HighestWeight. After we have set HighestWeight to a new value, it’s too late to copy its old value into SecondHighestWeight. Finally, of course, we have to display the last value of the second highest weight (lines //4 and //5). First, let’s see how Susan viewed this solution: Susan: I noticed that you separate out the main program {} from the other {} by indenting. Is that how the compiler knows which set of {} goes to which statements and doesn’t confuse them with the main ones that are the body of the program? Steve: The compiler doesn’t care about indentation at all; that’s just for the people reading the program. All the compiler cares about is the number of { it has seen so far without matching }. There aren’t any hard rules about this; it’s a "religious" issue in Java, where different programmers can’t agree on the best way. Problem Algorithms Java Interpreter Hardware Susan: Now on this thing with setting SecondHighestWeight to 0. Is that initializing it? See, I know what you are doing, and yet I can’t see the purpose of doing this clearly, unless it is initializing, and then it makes sense. Steve: That’s correct. Susan: How do you know how to order your statements? For example, why did you put the SecondHighestWeight = HighestWeight; above the other statement? What would happen if you reversed that order? Steve: Think about it. Let’s suppose that CurrentWeight is 40 HighestWeight is 30 SecondHighestWeight is 15 and the statements were executed in the following order: 1. HighestWeight = CurrentWeight 2. SecondHighestWeight = HighestWeight What would happen to the values? Well, statement 1 would set HighestWeight to CurrentWeight, so the values would be like this: ALGORITHMIC THINKING 97 CurrentWeight is 40 HighestWeight is 40 SecondHighestWeight is 15 Then statement 2 would set SecondHighestWeight to HighestWeight, leaving the situation as follows: CurrentWeight is 40 HighestWeight is 40 SecondHighestWeight is 40 This is clearly wrong. The problem is that we need the value of HighestWeight before it is set to the value of CurrentWeight, not afterward. After that occurs, the previous value is lost. Susan: Yes, that is apparent; I was just wondering if the computer had to read it in the order that you wrote it, being that it was grouped together in the {}. For example, you said that the compiler doesn’t read the {} as we write them, so I was wondering if it read those statements as we write them. Obviously it has to. So then everything descends in a progression downward and outward, as you get more detailed in the instructions. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Pump1a". A Prize Catch This program may seem to keep track of the highest and secondhighest weights correctly, but in fact there’s a hole in the logic. To be exact, it doesn’t work correctly when the user enters a new value that’s less than the previous high value but more than the previous second-high value. In that case, the new value should be the secondhigh value, even though there’ s n o n e w h i g h v a l u e . F o r e x a m p l e , suppose that you enter the following weights: 5, 2, 11, 3, 7. If we were to update SecondHighestWeight only when we see a new high, our program would indicate that 11 was the high, and 5 the second highest; since neither 3 nor 7 is a new high, SecondHighestWeight would remain as it was when the 11 was entered. Here’s what ensued when Susan tried out the program and discovered this problem: Problem Algorithms Java Interpreter Hardware 98 Chapter 4: MORE BASICS Susan: Steve, the program! I have been playing with it. Hey, this is fun, but look, it took me a while. I had to go over it and over it, and then I was having trouble getting it to put current weights that were higher than second weights into the second-highest weight slot. For example, if I had a highest weight of 40 and the the second-highest weight of 30 and then selected 35 for a current weight, it wouldn’t accept 35 as the second-highest weight. It increased the highest weights just fine and it didn’t change anything if I selected a lower number of the two for a current weight. Or did you mean to do that to make a point? I am supposed to find the problem? I bet that is what you are doing. Steve: Yep, and I’m not sorry, either.<G> Susan: You just had to do this to me, didn’t you? OK, what you need to do is to put in a statement that says if the current weight is greater than the second-highest weight, then set the second-highest weight to the current weight, as illustrated in Figure 4.2. Problem Algorithms Java Interpreter Hardware else { if (CurrentWeight > Second HighestWeight) Second HighestWeight = CurrentWeight; } Figure 4.2: Susan’s version of an if statement with an else clause I hope you are satisfied. Steve: Satisfied? Well, no, I wouldn’t use that word. How about ecstatic? You have just figured out a bug in a program, and determined what the solution is. Don’t tell me you don’t understand how a program works. Now I have to point out something about your code. I understood what you wrote perfectly. Unfortunately, compilers aren’t very smart, and therefore have to be extremely picky. So you have to make sure to spell the variable names correctly. This would make your answer like the if clause shown in Figure 4.3. Congratulations again. As Susan figured out, we have to add an else clause to our if statement, so that the corrected version of the statement looks like Figure 4.3. A PRIZE CATCH 99 if (CurrentWeight > HighestWeight) { SecondHighestWeight = HighestWeight; HighestWeight = CurrentWeight; } else { if (CurrentWeight > SecondHighestWeight) SecondHighestWeight = CurrentWeight; } Figure 4.3: Using an if statement with an else clause In this case, the condition in the first if is checking whether C u r r e n t W e i g h t is greater than the previous HighestWeight; when this is true, we have a new HighestWeight and need to update both HighestWeight and SecondHighestWeight. However, if CurrentWeight is not greater than HighestWeight, the else clause is executed. This else contains another if that checks whether CurrentWeight is greater than the current value of SecondHighestWeight. If so, SecondHighestWeight is set to the value of CurrentWeight. What happens if two (or more) pumpkins are tied for the highest weight? In that case, the first one of them to be encountered is going to set HighestWeight, as it will be the highest yet encountered. When t h e s e c o n d p u m p k i n o f t h e s a m e w e i g h t i s s e e n , i t w o n’ t t r i g g e r a change to HighestWeight, since it’s not higher than the current occupant of that variable. It will pass the test in the else clause, if (CurrentWeight > SecondHighestWeight), however, which will cause SecondHighestWeight to be set to the same value as HighestWeight. This is reasonable behavior, unlikely to startle the (hypothetical) user of the program, and therefore is good enough for our purposes. In a real application program, we’d have to try to determine what the user of this program would want us to do. Figure 4.4 shows the corrected program. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. Assuming that you’ve installed the examples on drive C:, the location of this program is "c:\whosj\code\Pump2". Problem Algorithms Java Interpreter Hardware 100 Chapter 4: MORE BASICS import WAJ.*; public class Pump2 { public static void main( String args[ ] ) { int CurrentWeight; int HighestWeight; int SecondHighestWeight; System.out.print("Please enter the first weight: "); CurrentWeight = RWVar.readInt(System.in); HighestWeight = CurrentWeight; SecondHighestWeight = 0; System.out.println("Current weight " + CurrentWeight); System.out.println("Highest weight " + HighestWeight); while (CurrentWeight > 0) { System.out.print("Please enter the next weight: "); CurrentWeight = RWVar.readInt(System.in); if (CurrentWeight > HighestWeight) { SecondHighestWeight = HighestWeight; HighestWeight = CurrentWeight; } else { if (CurrentWeight > SecondHighestWeight) SecondHighestWeight = CurrentWeight; } System.out.println("Current weight " + CurrentWeight); System.out.println("Highest weight " + HighestWeight); System.out.println("Second highest weight " + SecondHighestWeight); } Problem Algorithms Java Interpreter Hardware } } Figure 4.4: Finding the top two weights (code\Pump2\Pump2.java) B y t h e w a y , s i n c e w e ’ve just been using the if statement pretty heavily, this would be a good time to list all of the conditions that it can test. We’ve already seen some of them, but it can’t hurt to have them all in one place. Figure 4.5 lists these conditions with translations. A PRIZE CATCH 101 _______________________________________________________ Condition Controlled block will be executed if: Symbol _______________________________________________________ > First item is larger than second item. < First item is smaller than second item. >= First item is larger than or equal to second item. <= First item is smaller than or equal to second item. != First item differs from second item. == First item has the same value as the second item. _______________________________________________________ Figure 4.5: What if? You may wonder why we have to use == to test for equality rather than just =. That’s because = means "assign right-hand value to variable on left", rather than "compare two items for equality". Susan had some questions about these comparison operators. Susan: Are these always conditions of if statements? Aren’t they used for anything else? Steve: Yes. They’re used in while and for loops. Susan: I knew that. Steve: Sometimes they’re also used to do a comparison and save the result for later use. Susan: I didn’t know that. Steve: You learn something every day (if you’re lucky). What a Tangled Web We Weave I hope this excursion has given you some appreciation of the subtleties that await in even the simplest change to a working program; many experienced programmers still underestimate such difficulties and the amount of time that may be needed to ensure that the changes are correct. I don’t think it’s necessary to continue along the same path with a program that can award three prizes. The Problem Algorithms Java Interpreter Hardware 102 Chapter 4: MORE BASICS principle is the same, although the complexity of the code grows with the number of special cases we have to handle. Obviously, a solution that could handle any number of prizes without special cases would be a big improvement, but it will require some major changes in the organization of the program. That’s what we’ll take up next. You May Already Have Won One of the primary advantages of the method we’ve used so far to find the heaviest pumpkin(s) is that we didn’t have to save the weights of all the pumpkins as we went along. If we don’t mind saving all the weights, then we can solve the "three prize" problem in a different way. Let’s assume for the purpose of simplicity that there are only five weights to be saved, in which case the solution looks like this: 1. 2. Read in all of the weights. Make a list consisting of the three highest weights in descending order. Award the first, second, and third prizes, in that order, to the three entries in the list of highest weights. Problem 3. Algorithms Java Interpreter Now let’s break those down into substeps which can be more Hardware easily translated into Java. 1. Read in all of the weights. a. Read first number. b. Read next number. c. If we haven’t read five weights yet, go back to 1b. Now we have all the numbers; proceed to calculation phase. 2. Make a list consisting of the three highest weights in descending order. a. Find the largest number in the original list of weights. b. Copy it to the sorted list. c. If we haven’t found the three highest numbers, go back to 2a. YOU MAY ALREADY HAVE WON 103 Oops. That’s not going to work, since we’ll get the same number each time.1 To prevent that from happening, we have to mark off each number as we select it. Here’s the revised version of step 2: 2. Make a list consisting of the three highest weights in descending order. a. Find the largest number in the original list of weights. b. Copy it to the sorted list. c. Mark it off in the original list of weights, so we don’t select it again. d. If we haven’t found the three highest numbers, go back to 2a. Now we’re ready for output. 3. Award the first, second, and third prizes, in that order, to the three entries in the list of highest weights. a. Write first number. b. Write another number. c. If we haven’t done them all, go back to 3b. Unlike our previous approach, this obviously can be generalized to handle any number of prizes. However, we have to address two problems before we can use this approach: First, how do we keep track of the weights? And second, how do we select out the highest three weights? Both of these problems are much easier to solve if we don’t have a separate variable for each weight. Variables, by the Numbers The solution to our first question is to use an array. This is a variable containing a number of "sub-variables" that can be addressed by position in the array; each of these sub-variables is called an element. An array has a name, just like a regular variable, but the elements do not. Instead, each element has a number, corresponding to its position in the array. For example, we might want to create an ______________________________________________________________________________ 1. I realize I’m breaking a cardinal rule of textbooks: Never admit that the solution to a problem is anything but obvious, so the student who doesn’t see it immediately feels like an idiot. In reality, even a simple program is difficult to get right, and indicating the sort of thought processes that go into analyzing a programming problem might help demystify this difficult task. Problem Algorithms Java Interpreter Hardware 104 Chapter 4: MORE BASICS array of int values called Weight, with five elements. To do this, we would write this line: int[ ] Weight = new int[5];.2 We haven’t heard from Susan for a while, but the following exchange should make up for that. Susan: So then using an array is just another way of writing this same program, only making it a little more efficient? Steve: In this case, the new program can do more than the old program could: The new program can easily be changed to handle virtually any number of prizes, whereas the old program couldn’t. Susan: So there is more than one way to write a program that does basically the same thing? Steve: As many ways as there are to write a book about the same topic. Problem Algorithms Java Interpreter Hardware Susan: I find this to be very odd. I mean, on the one hand the code seems to be so unrelentingly exact; on the other, it can be done in as many ways as there are artists to paint the same flower. That must be where the creativity comes in. Then I would expect that the programs should behave in different manners, yet accomplish the same goal. Steve: It’s possible for two programs to produce similar (or even exactly the same) results from the user’s perspective and yet work very differently internally. For example, the array version of the weighing program could produce exactly the same final results as the original version, even though the method of finding the top two weights was quite different. Now we can refer to the individual elements of the array called Weight by using their numbers, enclosed in square brackets ([ ]); the number in the brackets is called the index.3 Here are some examples: Weight[1] = 123; Weight[2] = 456; Weight[3] = Weight[1] + Weight[2]; Weight[i+1] = Weight[i] + 5; ______________________________________________________________________________ 2. We’ll see how arrays work in more detail in Chapters 6 and 8. 3. By the way, if you’re wondering how to pronounce Weight[i], it’s "weight sub i". "Sub" is short for subscript, which is an old term for "index". VARIABLES, BY THE NUMBERS 105 As these examples indicate, an element of an array can be used anywhere a "regular" variable can be used.4 But the most valuable difference between a regular variable and an element of an array is that we can vary which element we are referring to in a given statement, by varying its index. Take a look at the last sample line, in which two elements of the array Weight are used; the first one is e l e m e n t i + 1 a n d t h e o t h e r i s e l e m e n t i . A s t h i s i n d i c a t e s , w e d o n’ t have to use a constant value for the element number but can calculate it while the program is executing; in this case, if i is 1, the two elements referred to are element 2 and element 1, while if i is 5, the two elements are elements 6 and 5, respectively. The ability to refer to an element of an array by number rather than by name allows us to write statements that can refer to any element in an array, depending on the value of the index variable in the statements. Figure 4.6, which solves our three-prize problem, shows how this works in practice. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Vect1". This program uses several new features of Java which need some explanation. First, of course, there is the line that defines the array Weight: int[ ] Weight = new int[5]; As you might have guessed, this means that we want an array of five elements, each of which is an int. As we have already seen, this means that there are five distinct index values, each of which refers to one element. However, what isn’t so obvious is what those five distinct index values actually are. You might expect them to be 1, 2, 3, 4, and 5; actually, they are 0, 1, 2, 3, and 4. This method of referring to elements in an array is called zerobased indexing. Although it might seem arbitrary to start counting at 0 rather than at 1, assembly language programmers find it perfectly natural, because the calculation of the address of an element is simpler with such indexing; the formula is "(address of first element) + (element number) * (size of element)". ______________________________________________________________________________ 4. What I’m calling a regular variable here is technically known as a scalar variable; that is, one with only one value at any given time. Problem Algorithms Java Interpreter Hardware 106 Chapter 4: MORE BASICS import WAJ.*; public class Vect1 { public static void main( String args[ ] ) { int[ ] Weight = new int[5]; int[ ] SortedWeight = new int[3]; int HighestWeight; int HighestIndex; int i; int k; System.out.println("I’m going to ask you to type in five weights, in pounds."); for (i = 0; i < 5; i ++) { k = i + 1; System.out.print("Please type in weight #" + k + ": "); Weight[i] = RWVar.readInt(System.in); } for (i = 0; i < 3; i ++) { HighestWeight = 0; HighestIndex = 0; for (k = 0; k < 5; k ++) { if (Weight[k] > HighestWeight) { HighestWeight = Weight[k]; HighestIndex = k; } } SortedWeight[i] = HighestWeight; Weight[HighestIndex] = 0; } Problem Algorithms Java Interpreter Hardware System.out.println("The highest weight was: " + SortedWeight[0]); System.out.println("The second highest weight was: " + SortedWeight[1]); System.out.println("The third highest weight was: " + SortedWeight[2]); } } Figure 4.6: Using an array (code\Vect1\Vect1.java) VARIABLES, BY THE NUMBERS 107 Why does it matter what assembly language programmers find natural? This bit of history is relevant because C, a predecessor of Java, was originally intended to replace assembly language so that programs could be moved from one machine architecture to another with as little difficulty as possible. One reason for some of the eccentricities of Java is that it takes a lot of its syntax from C++ and its predecessor, C.5 The last two lines in the variable definition phase define two variables, called i and k, which have been traditional names for index variables (i.e., variables used to hold indexes) since at least the ______________________________________________________________________________ 5. Here’s an interesting side note on a case where the inventors of a commonly used facility should have used zero-based indexing, but didn’t. We’re still suffering from the annoyances of this one. Long ago, there was no standard calendar, with year numbers progressing from one to the next, when January 1st came around. Instead, years were numbered relative to the reign of the current monarch; for example, the Bible might refer to "the third year of Herod’s reign". This was fine in antiquity, when most people really didn’t care what year it was. There were few retirement plans or fiftieth wedding anniversaries to celebrate anyway. However, it was quite annoying to historians to try to calculate the age of someone who was born in the fourth year of someone’s reign and died in the tenth year of someone else’s. According to Grolier’s Multimedia Encyclopedia: "About AD 525, a monk named Dionysius Exiguus suggested that years be counted from the birth of Christ, which was designated AD (anno Domini, "the year of the Lord") 1. This proposal came to be adopted throughout Christendom during the next 500 years. The year before AD 1 is designated 1 BC (before Christ)." The encyclopedia doesn’t state when the use of the term BC started, but the fact that its translation is English is a suspicious sign indicating that this was considerably later. In any event, this number system made matters quite a bit easier. Now you could tell that someone who was born in AD 1200 and died in AD 1250 was approximately 50 years old at death. Unfortunately, however, there was still a small problem. Zero hadn’t yet made it to Europe from Asia when the new plan was adopted, so the new calendar numbered the years starting with 1, rather than 0; that is, the year after 1 BC was 1 AD. Although this may seem reasonable, it accounts for a number of oddities of our current calendar. 1. 2. 3. Date ranges spanning AD and BC are hard to calculate, since you can’t just treat BC as negative. For example, if someone were born in 1 BC and died in 1 AD, how old was that person? You might think that this could be calculated as 1 – (–1), or 2; however, the last day of 1 BC immediately preceded the first day of 1 AD, so the person might have been only a few days old. The twentieth century consists of the years 1901 to 2000; the year numbers of all but the last year of that century actually start with the digits 19 rather than 20. Similarly, the third millennium starts on January 1, 2001, not 2000. The reason for the second and third of these oddities is that since the first century started in 1 AD, the second century had to start in 101 AD; if it started in 100 AD, the first century would have consisted of only 99 years (1–99), rather than 100. If only they had known about the zero! Then the zeroth century would have started at the beginning of 0 AD and ended on the last day of 99 AD. The first century would have started at 100 AD, and so on; coming up to current time, we would be living through the last years of the nineteenth century, which would be defined as all of those years whose year numbers started with 19. The second millennium would start on January 1, 2000, as everyone would expect. Problem Algorithms Java Interpreter Hardware 108 Chapter 4: MORE BASICS invention of FORTRAN in the 1950s.6 The inventors of FORTRAN used a fairly simple method of determining the type of a variable: If it began with one of the letters I through N, it was an integer. Otherwise, it was a floating-point variable (i.e., one that can hold values that contain a fractional part, such as 3.876). This rule was later changed so that the user could specify what type the variable was, as we do in Java, but the default rules were the same as in the earlier versions of FORTRAN, to allow programs using the old rules to continue to compile and run correctly. Needless to say, Susan had some questions about the names of index variables. Susan: So whenever you see i or k you know you are dealing with an array? Steve: Not necessarily. Variables named i and k are commonly used as indexes, but they are also used for other purposes sometimes. Susan: Anyway, if i and k are sometimes used for other purposes, then the compiler doesn’t care what you use as indexes? Again, no rules, just customs? Problem Algorithms Java Interpreter Hardware Steve: Right. It’s just for the benefit of other programmers, who will see i and say "oh, this is probably an index variable". I suspect one reason for the durability of these short names is that they’re easy to type, and many programmers aren’t very good t y p i s t s .7 I n J a v a , t h e l e t t e r s i , j , k, m , a n d n a r e c o m m o n l y u s e d a s ______________________________________________________________________________ 6. Actually, the second most common index variable, after i, is probably j. However, I don’t use j as an index variable in this book because in the font I use for program listings, the bottom curve of the j runs into the opening [ and thus makes the listings hard to read. 7. I s t r o n g l y r e c o m m e n d l e a r n i n g h o w t o t y p e ( i . e . , t o u c h - t y p e ) . I w a s a p r o f e s s i o n a l programmer without typing skills for over 10 years before agreeing to type (someone else’s) book manuscript. At that point, I decided to teach myself to touch-type, so I wrote a Dvorak keyboard driver for my Radio Shack Model III computer and started typing. In about a month I could type faster than with my previous two finger method and eventually got up to 80+ words per minute on English text. If you’ve never heard of the Dvorak keyboard, it’s the one that has the letters laid out in an efficient manner; the "home row" keys are AOEUIDHTNS rather than the absurd set ASDFGHJKL;. This "new" (1930s) keyboard layout reduces effort and increases speed and accuracy compared to the old QWERTY keyboard, which was invented in the 1880s to prevent people from typing two keys in rapid succession and jamming the type bars together. This problem has been nonexistent since the invention of the Selectric typewriter (which uses a ball rather than type bars) in the 1960s, but inertia keeps the old layout in use even though it is very inefficient. In any event, since I learned to type, writing documentation has required much less effort. This applies especially to writing articles or books, which would be a painful process otherwise. VARIABLES, BY THE NUMBERS 109 indexes; however, l (the letter "ell") generally isn’t, because it looks too much like a 1 (the numeral one). The compiler doesn’t get confused by this resemblance, but programmers very well might. After the variable definitions are out of the way, we can proceed to the executable portion of our program. First, we type out a note to the user, stating what to expect, with the line: System.out.println("I’m going to ask you to type in five weights, in pounds.");. Then we get to the code in Figure 4.7. for (i = 0; i < 5; i ++) { k = i + 1; System.out.print("Please type in weight #" + k + ": "); Weight[i] = RWVar.readInt(System.in); } Figure 4.7: Using a for statement (from code\Vect1\Vect1.java) The first line here is called a for statement, which is used to control a f o r l o o p ; t h i s i s a l o o p c o n t r o l f a c i l i t y s i m i l a r t o t h e while l o o p w e encountered in Chapter 3. The difference between these two statements is that a for loop allows us to specify more than just the condition under which the controlled block will be repetitively executed.8 A for statement specifies three expressions (separated by ";") that control the execution of the for loop: a starting expression, a continuation expression, and a modification expression. In our case, these are i = 0, i < 5, and i ++, respectively. Let’s look at the function and meaning of each of these components. First, the starting expression, i = 0. This is executed once, before the block controlled by the for statement is executed. In this case, we use it to set our index variable, i, to 0, which will refer to the first element of our Weight array. Next, the continuation expression, i < 5. This specifies under what conditions the statement controlled by the for will be executed; in this case, we will continue executing the controlled statement as long as the value of i is less than 5. Be warned that the continuation ______________________________________________________________________________ 8. You may sometimes see the term controlled statement used in place of controlled block; since, as we have already seen, a block can be used anywhere that a single statement can be used, controlled statement and controlled block are actually just two ways of saying the same thing. Problem Algorithms Java Interpreter Hardware 110 Chapter 4: MORE BASICS expression is actually executed before every execution of the controlled block; thus, if the continuation expression is false when the loop is entered, the controlled block will not be executed at all. The notion of the continuation expression is apparently confusing to some novices. Susan fell into that group. Susan: In your definition of for, how come there is no ending expression? Why is it only a modification expression? Is there never a case for a conclusion? Steve: The "continuation expression" tells the compiler when you want to continue the loop; if the continuation expression comes out false, then the loop terminates. That serves the same purpose as an "ending expression" might, but in reverse. Finally, let’s consider the modification expression, i ++.9 This is exactly equivalent to i = i + 1, which means "set i to one more than its current value", an operation technically referred to as incrementing a variable. You may wonder why we need two ways to say the same thing; actually, there are a few reasons. One is that ++ requires less typing, which as we know isn’t a strong point of many programmers; Problem also, the ++ (pronounced "plus plus") operator doesn’t allow the Algorithms possibility of mis-typing the statement as, for example, i = j + 1;, when Java you really meant to increment i. Another reason why this feature was Interpreter added to the C language is that, in the early days of C, compiler Hardware t e c h n o l o g y w a s n ’ t v e r y a d v a n c e d , a n d t h e ++ o p e r a t o r a l l o w e d t h e production of more efficient programs. You see, many machines can add one to a memory location by a single machine language instruction, usually called something like increment memory. Even a simple compiler can generate an "increment memory" instruction as a translation of i ++, whereas it takes a bit more sophistication for the compiler to recognize i = i + 1 as an increment operation. Since incrementing a variable is a very common operation in C this was worth handling specially, and this special handling has been carried over into C++ and subsequently to Java. Now that we have examined all the parts of the for statement, we can see that its translation into English would be something like this: 1. 2. Set the index variable i to 0. If the value of i is less than 5, execute the following block (in this case, the block with the System.out.print and RWVar.readInt ______________________________________________________________________________ 9. You don’t need a space between the variable name and the ++ operator; however, I think it’s easier to read this way. VARIABLES, BY THE NUMBERS 3. 111 statements). Otherwise, skip to the next statement after the end of the controlled block; that is, the one following the closing }. Add one to the value of i and go back to step 2. Susan didn’t think these steps were very clear. Let’s listen in on the conversation that ensued. Susan: Where in the for statement does it say to skip to the next statement after the end of the controlled block when i is 5 or more? Steve: It doesn’t have to. Remember, the point of {} is to make a group of statements act like one. A for statement always controls exactly one "statement", which can be a block contained in {}. Therefore, when the continuation expression is no longer true, the next "statement" to be executed is whatever follows the } at the end of the block. Susan: Okay, now I get it. The {} work together with the < 5 to determine that the program should go on to the next statement. Steve: Right. Susan: Now, on the "controlled block" — so other statements can be considered controlled blocks too? I mean is a controlled block basically just the same thing as a block? I reviewed your definition of block, and it seems to me that they are. I guess it is just a statement that in this case is being controlled by for. S t e v e : C o r r e c t . I t ’ s c a l l e d a controlled block because it’s under the control of another statement. Susan: So if we used while before the {} then that would be a while controlled block? Steve: Right. Susan: Then where in step 3 or in i++ does it say to go back to step 2? Steve: Again, the for statement executes one block (the controlled block) repeatedly until the continuation expression is false. Since a block is equivalent to one statement, the controlled block can also be referred to as the controlled statement . I n t h e c u r r e n t e x a m p l e , t h e b l o c k t h a t i s controlled by the for loop consists of the following lines: Problem Algorithms Java Interpreter Hardware 112 Chapter 4: MORE BASICS { k = i + 1; System.out.print("Please type in weight #" + k + ": "); Weight[i] = RWVar.readInt(System.in); } Susan: Okay. But now I am a little confused about something else here. I thought that System.out statements were just things that you would type in to be seen on the screen. Steve: That’s correct, except that System.out is a variable used for I/O, not a statement. Susan: So then why do we need that k thing? Can’t we just say "Please type in weight #" + i + 1 + ": "? Problem Algorithms Java Interpreter Hardware Steve: No, that won’t work. If we wrote the line that way, it would start out by displaying "Please type in weight #01". This is another of the "features" of Java that I’m a little dubious about.10 The problem is that the + sign doesn’t mean "add" when one of the things being "added" is a String; instead, it means "add a String to the end of another String". Even if the second item being "added" isn’t a String, that doesn’t matter; it will be made into a String and then stuck onto the end of the first item. This is useful when we want to run a bunch of Strings and numbers together on a line, but it is a pain when we want to do arithmetic in that same line. Therefore, we have to create another variable called k that is set to i + 1 and use k in our System.out line. That way, the first time, it will say Please enter weight #1: The second time, it will say Please enter weight #2: and so on. The number of the weight we’re asking for is one more than i; therefore, we set k to i + 1 before we get to the output statement so that it will stick the correct number into the output line at that point. Susan: How does k end up as #1? Steve: The first time, i is 0; therefore, when we set k to i + 1, the value of k is 1. The # comes from the end of the preceding part of the output statement. Let’s assume that i is 0, as it will be the first time through the loop. In that case, if we were to write System.out.print("Please type in weight #" + i + 1 + ": "); the compiler would think we wanted to do the following: ______________________________________________________________________________ 10. Okay, I’m very dubious. VARIABLES, BY THE NUMBERS 1. 2. 3. 4. 113 Start with the String "Please type in weight #". Convert the value of i to a String, producing the String value "0", and add this value to the end of the previous String, producing "Please type in weight #0". Convert the value 1 to a String, producing the String value "1", and add this value to the end of the previous String, producing "Please type in weight #01". Add the String value ": " to the end of the previous String, producing "Please type in weight #01: ". This happens because + has two meanings, one for Strings and one for numbers. If either of the two expressions being "added" is a String, the compiler assumes you want the String +, which means to append the expression after the + to the expression before the +. If this means converting one or the other of these expressions to a String from whatever type it might be, that’s what the compiler will do. Isn’t Java a nice simple language? Susan: That is dumb. Steve: I can’t say I’m terribly impressed by that design decision either, but if they didn’t have some way to chain output items together the language would be even clumsier than it is now. That’s why they broke their own rule against operator overloading in the case of Strings. Now let’s continue with the next step in the description of our for loop, the modification expression i ++. In our example, this will be executed five times. The first time, i will be 0, then 1, 2, 3, and finally 4. When the loop is executed for the fifth time, i will be incremented to 5; therefore, step 2 will end the loop by skipping to the next statement after the controlled block.11 A bit of terminology is useful here: Each time through the loop is called an iteration. Let’s hear Susan’s thoughts on this matter. Susan: When you say that "step 2 will end the loop by skipping to the next statement after the controlled block", does that mean it is now going on to the next for statement? So when i is no longer less than 5, the completion of the loop signals the next controlled block? Steve: In general, after all the iterations in a loop have been performed, execution proceeds to whatever statement follows the controlled block. ______________________________________________________________________________ 11. In case you’re wondering why the value of i at the end of this loop will be 5, the reason is that at the end of each pass through the loop, the modification expression (i ++) is executed before the continuation expression that determines whether the next execution will take place (i < 5). Thus, at the end of the fifth pass through the loop, i is incremented to 5 and then tested to see if it is still less than 5. Since it isn’t, the loop terminates at that point. Problem Algorithms Java Interpreter Hardware 114 Chapter 4: MORE BASICS In this case, the next statement is indeed a for statement, so that’s the next statement that is performed after the end of the current loop. The discussion of the for statement led to some more questions about loop control facilities and the use of parentheses. Susan: How do you know when to use ()? Is it only with if and for and while and else and stuff like that, whatever these statements are called? I mean they appear to be modifiers of some sort; is there a special name for them? Steve: The term loop control applies to statements that control loops that can execute controlled blocks a (possibly varying) number of times; these include for and while. The if and else statements are somewhat different, since their controlled blocks are executed either once or not at all. The () are needed in those cases to indicate where the controlling expression(s) end and the controlled block begins. You can also use () to control the order of evaluation of an arithmetic expression: The part of the expression inside parentheses is executed first, regardless of normal ordering rules. For example, 2*5+3 is 13, while 2*(5+3) is 16.12 Problem Algorithms Java Interpreter Hardware Susan: So if you just wrote while CurrentWeight > 0 with no (), then the compiler couldn’t read it? Steve: Correct. Susan: Actually it is beginning to look to me as I scan over a few figures that almost everything has a caption of some sort surrounding it. Everything either has a " " or () or {} or [ ] or <> around it. Is that how it is going to be? I am still not clear on the different uses of () and {}; does it depend on the control loop? Steve: The {} are used to mark the controlled block, while the () are used to mark the conditional expression(s) for the if, while, for, and the like. However, ( and ) also have other meanings in Java, which we’ll get to eventually. Susan: OK, I think I have it: {} define blocks and () define expressions. How am I to know when a new block starts? I mean if I were doing the writing, it would be like a new paragraph in English, right? So are there any rules for knowing when to stop one block and start another? ______________________________________________________________________________ 12. If you recall the problem that we had with printing an expression that had an arithmetic calculation in it (Figure 4.7), we could also have used () to cause the expression i + 1 to be executed before the String + operation, so that the line in question would have read System.out.print("Please type in weight #" + (i + 1) + ": ");. That would have eliminated the need for the extra variable k. VARIABLES, BY THE NUMBERS 115 Steve: It depends entirely on what you’ r e t r y i n g t o a c c o m p l i s h . T h e main purpose of a block is to make a group of statements act like one statement; therefore, for example, when you want to control a group of statements by one if or for, you group those statements into a block. Now that we’ve examined the for statement in excruciating detail, what about the block it controls? We’ve already discussed the first and second statements in the block, k = i + 1; and System.out.print("Please type in weight #" + k + ": ");. As we’ve already seen, these two statements combine to generate a message to the user of the program indicating what value we’re waiting for. When the second of these statements is executed during the first iteration of the loop, it will generate the output: Please type in weight #1: The same request, with a different value for the weight number, will show up each time the user hits ENTER, until five values have been accepted. The third and final statement in the controlled block, Weight[i] = RWVar.readInt(System.in); is a little different. Here, we’re waiting for the user to type a number in at the keyboard and storing it in a variable. Unlike our previous input statements, in this case the variable we’re using is different each time through the loop: To be exact, it’s the ith element of the Weight array. So, on the first iteration, the value the user types in will go into Weight[0]; the value accepted on the second iteration will go into Weight[1]; and so on, until on the fifth and last iteration, the typed-in value will be stored in Weight[4]. Here’s Susan’s take on this: Susan : N o w I u n d e r s t a n d w h y y o u u s e d t h e e x a m p l e o f i = i + 1 ; in Chapter 3; before, it didn’t make sense why you would do that silly thing. Anyway, now let me get this straight. To say that, in the context of this exercise, means you can keep adding 1 to the value of i? I am finding it hard to see where this works for the number 7, say, or anything above 5 for that matter. So, it just means you can have 4 +1 or + another 1, and so on? See where I am having trouble? Steve: Remember, an int variable such as i is just a name for a 4-byte area of RAM, which can hold any value between –2147483648 and 2147483647. Therefore, the statement i ++; means that we want to Problem Algorithms Java Interpreter Hardware 116 Chapter 4: MORE BASICS recalculate the contents of that area of RAM by adding 1 to its former contents. Susan: No, that is not the answer to my question. Yes, I know all that<G>. What I am saying is this: I assume that i ++; is the expression that handles any value over 4, right? Then let’s say that you have pumpkins that weigh 1, 2, 3, 4, and 5 pounds, consecutively. No problem, but what if the next pumpkin was not 6 but say 7 pounds? If at that point, the highest value for i was only 5 and you could only add 1 to it, how does that work? It just doesn’t yet have the base of 6 to add 1 to. Now do you understand what I am saying? Steve: I see the problem. We’re using the variable i to indicate which weight we’re talking about, not the weight itself. In other words, the first weight is Weight[0], the second is Weight[1], the third is Weight[2], the fourth is Weight[3], and the fifth is Weight[4]. The actual values of the weights are whatever the user of the program types in. For example, if the user types in 3 for the first weight, 9 for the second one, 6 for the third, 12 for the fourth, and 1 for the fifth, then the array will look like this: Problem Algorithms Java Interpreter Hardware Element Value Weight[0] Weight[1] Weight[2] Weight[3] Weight[4] 3 9 6 12 1 The value of i has to increase by only one each time because it indicates which element of the array Weight is to store the current value being typed in by the user. Does this clear up your confusion? Susan: I think so. Then it can have any whole number value 0 or higher (well, up to the size of an int); adding the 1 means you are permitting the addition of at least 1 to any existing value, thereby allowing it to increase. Is that it? S t e v e: No, I’m not permitting an addition; I ’ m p e r f o r m i n g i t . L e t ’ s suppose i is 0. In that case, Weight[i] means Weight[0], or the first element of the Weight array. When I add 1 to i, i becomes 1. Therefore, Weight[i] now means Weight[1]. The next execution of i ++; sets i to 2; therefore, Weight[i] now means Weight[2]. Any time an i is used in an expression, for example, Weight[i], i + j, or i + 1, you can replace the i by whatever the current value of i is. The only place where you can’t replace a variable such as i by its current value is when it is being modified, as in i ++ or the i in i = j + 1. In those cases, i means the address where the value of the variable i is stored. VARIABLES, BY THE NUMBERS 117 Susan: OK, then i is not the number of the value typed in by the user; it is the location of an element in the Weight array, and that is why it can increase by 1, because of the i ++? Steve: Correct, except that I would say "that is why it does increase by 1". This may just be terminology. Susan: But in this case it can increase no more than 4 because of the i < 5 thing? Steve: Correct. Susan: But it has to start with a 0 because of the i = 0 thing? Steve: Correct. Susan: When you say Weight[i] = RWVar.readInt(System.in); does that mean you are telling the computer to place that variable in the index? So this serves two functions, displaying the weight the user types in and associating it to the index? Steve: No, that statement tells the computer to place the value read in from the keyboard into element i of array Weight. Susan: Okay, but what do you mean by the ith element? So does Weight[i] mean you are directing the number that the user types in to a certain location in memory? Steve: Yes, to the element whose index is the current value of i. Susan: What I am confusing is what is being seen on the screen at the time that the user types in the input. So the user sees the number on the screen but then it isn’t displayed anywhere after that number is entered? Then the statement Weight[i] = RWVar.readInt(System.in); directs it to a location somewhere in memory with a group of other numbers that the user types in? Steve: Correct. Susan: So then Weight[i] = RWVar.readInt(System.in); means that the number the user is typing has to go into one of those locations but the only word that says what that location could be is Weight; it puts no limitations on the location in that Weight array other than when you defined the index variable as int i;. This means the index cannot be more than will fit into an int. Problem Algorithms Java Interpreter Hardware 118 Chapter 4: MORE BASICS Steve: Correct. The current value of i is what determines which element of Weight the user’s input goes into. Susan: I think I was not understanding this because I kept thinking that i was what the user typed in and we were defining its limitations. Instead we are telling it where to go. Steve: That’s right. Now that we have stored all of the weights, we want to find the three highest of the weights. We’ll use a sorting algorithm called a selection sort, which can be expressed in English as follows: 1. 2. 3. Repeat the following steps three times, once through for each weight that we want to select. Search through the list (i.e., the Weight array), keeping track of the highest weight seen so far in the list and the index of that highest weight. When we get to the end of the list, copy the highest weight we’ve found to the next element of another list (the "output list", which in this case is the array SortedWeight). Finally, set the highest weight we’ve found in the original list to 0, so we won’t select it as the highest value again on the next pass through the list. Problem 4. Algorithms Java Interpreter Hardware Let’s take a look at the portion of our Java program that implements this sort, in Figure 4.8. for (i = 0; i < 3; i ++) { HighestWeight = 0; HighestIndex = 0; for (k = 0; k < 5; k ++) { if (Weight[k] > HighestWeight) { HighestWeight = Weight[k]; HighestIndex = k; } } SortedWeight[i] = HighestWeight; Weight[HighestIndex] = 0; } Figure 4.8: Sorting the weights (from code\Vect1\Vect1.java) VARIABLES, BY THE NUMBERS 119 Now let’s look at the correspondence between the English description of the algorithm and the code. 1. Repeat the following steps once through for each prize: for (i = 0; i < 3; i ++) 2. 3. (During this process the variable i is the index into the SortedWeight array where we’re going to store the weight for the current prize we’re working on. While we ’ r e l o o k i n g f o r t h e highest weight, i is 0; for the second-highest weight, i is 1; finally, when we’re getting ready to award a third prize, i is 2.) Search through the input list. For each element of the list Weight, we check whether that element (Weight[k]) is greater than the highest weight seen so far in the list (HighestWeight). If that is the case, then we reset HighestWeight to the value of the current element (Weight[k]), and the index of the highest weight so far (HighestIndex) to the index of the current element (k). When we get to the end of the input list, HighestWeight is the highest weight in the list, and HighestIndex is the index of that element of the list that had the highest weight. Therefore, we can copy the highest weight to the current element of another list (the "output list"). As mentioned earlier, i is the index of the current element in the output list. Its value is the number of times we have been through the outer loop before; that is, the highest weight, which we will identify first, goes in position 0 of the output list, the next highest in position 1, and so on. SortedWeight[i] = HighestWeight; 4. Finally, set the highest weight in the input list to 0, so we won’t select it as the highest value again on the next pass through the list. Weight[HighestIndex] = 0; This statement is the reason that we have to keep track of the "highest index"; that is, the index of the highest weight. Otherwise, we wouldn’t know which element of the original Weight array we’ve used, and therefore wouldn’t be able to set it to 0 to prevent its being used again. Here’s Susan’s rendition of this algorithm: Problem Algorithms Java Interpreter Hardware 120 Chapter 4: MORE BASICS Susan: OK, let me repeat this back to you in English. The result of this program is that after scanning the list of user input weights, the weights are put in another list, which is an ordering list, named k. The program starts by finding the highest weight in the input list. It then takes it out, puts it in k, and replaces that value it took out with a 0, so it won’t be picked up again. Then it comes back to find the next highest weight and does the same thing all over again until nothing is left to order. Actually this is more than that one statement. But is this what you mean? That one statement is responsible for finding the highest weight in the user input list and placing it in k. Is this right? Steve: It’s almost exactly right. The only error is that the list that the weights are moved to is the SortedWeight array, rather than k. The variable k is used to keep track of which is the next entry to be put into the SortedWeight array. Problem Algorithms Java Interpreter Hardware Susan: OK. There was also something else I didn’t understand when tracing through the program. I did see at one point that i=3. Well, first I didn’t know how that could be because i is supposed to be < 3, but then I remembered that i ++ expression in the for loop, so I wondered if that is how this happened. I forgot where I was at that point, but I think it was after I had just completed entering 5 values and i was incrementing with each value. But see, it really should not have been more than 4 because if you start at 0 then that is where it should have ended up. Steve: The reason that i gets to be 3 after the end of the loop is that at the end of each pass through the loop, the modification expression (i ++) is executed before the continuation expression (i < 3). So at the end of the fifth pass through the loop, i is incremented to 3 and then tested to see if it is still less than 3. Since it isn’t, the loop terminates at that point. Susan: I get that. But I still have a question about the statement if Weight[k] > HighestWeight. Well, the first time through, this will definitely be true because we’ v e i n i t i a l i z e d H i g h e s t W e i g h t t o 0 , s i n c e a n y w e i g h t would be greater than 0. Is that right? Steve: Yes. Every time through the outer (i) loop, as we get to the top of the inner loop, the 0 that we’ve just put in HighestWeight should be replaced by the first element of Weight; that is, Weight[0], except of course if we’ve already replaced Weight[0] by 0 during a previous pass. It would also be possible to initialize HighestWeight to Weight[0] and then start the loop by setting k to 1 rather than 0. That would cause the inner (k) loop to be executed only four times per outer loop execution, rather than five, and therefore would be more efficient. Susan: Then HighestIndex=k; is the statement that sets the placement of the highest number to its rank? VARIABLES, BY THE NUMBERS 121 Steve: Right. Susan: Then I thought about this. It seems that the highest weight is set first, then the sorting takes place so it makes four passes (actually five) to stop the loop. Steve: The sorting is the whole process. Each pass through the outer loop locates one more element to be put into the SortedWeight array. Is that what you’re saying here? Susan: Then the statement Weight[HighestIndex] = 0; comes into play, replacing the highest number selected on that pass to 0. Steve: Correct. Susan: Oh, when k is going through the sorting process, why does i increment through each pass? It seems that k should be incrementing. Steve: Actually, k increments on each pass through the inner loop, or 15 times in all. It’s reset to 0 on each pass through the outer loop, so that we look at all of the elements again when we’re trying to find the highest remaining weight. On the other hand, i is incremented on each pass through the outer loop or three times in all, once for each "highest" weight that gets put into the SortedWeight array. Susan: OK, I get the idea with i, but what is the deal with k? I mean I see it was defined as an int, but what is it supposed to represent, and how did you know in advance that you were going to need it? Steve: It represents the position in the original list, as indicated in the description of the algorithm. Since that position is a number, we can use an int to represent it. Susan: I still don’t understand where k fits into this picture. What does it do? Steve: It’s the index in the "inner loop", which steps through the elements looking for the highest one that’s still there. We get one "highest" value every time through the "outer loop", so we have to execute that outer loop three times. Each time through the outer loop, we execute the inner loop five times, once for each entry in the input list. Susan: Too many terms again. Which is the "outer loop" and which is the "inner loop"? Problem Algorithms Java Interpreter Hardware 122 Chapter 4: MORE BASICS Steve: The outer loop executes once for each "highest" weight we’re locating. Each time we find one, we set it to 0 (at the end of the loop) so that it won’t be found again the next time through. Susan: OK, now I am confused with the statement if (Weight[k] > HighestWeight). This is what gets me: If I understand this right (and obviously I don’t) how could Weight[k] ever be greater than HighestWeight, since every possible value of k r e p r e s e n t s o n e o f t h e e l e m e n t s i n t h e Weight array, and HighestWeight is the highest weight in that array? For this reason I am having a hard time understanding the code for step 2, but not the concept. Steve: The value of HighestWeight at any time is equal to the highest weight that has been seen so far. At the beginning of each execution of the outer loop, HighestWeight is set to 0. Then, every time that the current w e i g h t (Weight[k]) is higher than the current value of H i g h e s t W e i g h t , w e reset HighestWeight to the value of the current weight. Susan: I still don’t understand this statement. Help. Problem Algorithms Java Interpreter Hardware Steve: Remember that HighestWeight is reset to 0 on each pass through the outer loop. Thus, this if statement checks whether the kth element of the Weight array exceeds the highest weight we’ve seen before in this pass. If that is true, obviously our "highest" weight isn’t really the highest, so we have to reset the highest weight to the value of the kth element; if the kth element isn’t the true highest weight, at least it’s higher than what we had before. Since we replace the "highest" weight value with the kth value any time that the kth value is higher than the current "highest" weight, at the end of the inner loop, the number remaining in HighestWeight will be the true highest weight left in Weight. This is essentially the same algorithm as we used to find the highest weight in the original version of this program, but now we apply it several times to find successively lower "highest" weights. Susan: OK, I understand now; i increments to show how many times it has looped through to find the highest number. You are doing a loop within a loop, really; it is not side by side, is it? Steve: Correct. S u s a n: So when you first enter your numbers, they are placed in an index called i, then they are going to be cycled through again, placing them in a corresponding index named k, looking for the top three numbers. To start out through each pass, you first set the highest weight to the first weight since you have preset the highest weight to 0. But to find the top three numbers, you have to look at each place or element in the index. At the end of each loop you sort out the highest number and VARIABLES, BY THE NUMBERS 123 then set that removed element to 0 so it won’t be selected again. You do this whole thing three times. Steve: That’s right, except for some terminology: Where you say "an index called i", you should say "an array called Weight", and where you say "an index called k", you should say "an array called SortedWeight". The variables i and k are used to step through the arrays, but they are not the arrays themselves. Susan: OK, then the index variables are just the working representation of what is going on in those arrays. But aren’t the numbers "assigned" an index? Let’s see; if you lined up your five numbers, you could refer to each number as to its placement in an array. Could you then have the column of weights in the middle of the two indexes of i and k to each side? Steve: If I understand your suggestion, it wouldn’t work, because k and i vary at different speeds. During the first pass of the outer loop, i is 0, whereas k varies from 0 to 5; on the second pass of the outer loop, i is 1, whereas k varies from 0 to 5 again, and the same for the third pass of the outer loop. The value of i is used to refer to an individual element of the SortedWeight array, the one that will receive the next "highest" weight we locate. The value of k is used to refer to an individual element of the Weight array, the one we’re examining to see if it’s higher than the current HighestWeight. Susan: This is what gets me; how do you know in advance that you are going to have to set HighestIndex to k? I see it in the program as it happens and I understand it then, but how would you know that the program wouldn’t run without doing that? Trial and error? Experience? Rule books? <G> Steve: Logic. Let’s look at the problem again. The sorting algorithm that we’re using here is called selection sort, because each time through the outer loop it selects one element out of the input array and moves it to the output array. To prevent our selecting the same weight (i.e., the highest one in the original input) every time through the outer loop, we have to clear each weight to 0 as we select it. But to do that, we have to keep track of which one we selected; that’s why we need to save HighestIndex. Being a glutton for punishment, Susan brought up the general problem of how to create an algorithm in the first place. Susan: Do they make instruction sheets with directions of paths to follow? How do you identify problems? I mean, don’t you encounter pretty much the same types of problems frequently in programming, and Problem Algorithms Java Interpreter Hardware 124 Chapter 4: MORE BASICS can’t they be identified some way so that if you knew a certain problem could be categorized as a Type C problem, let’s say, you would approach it with a Type C methodology to the solution? Does that make sense? Probably not. Steve: It does make sense, but for some reason such "handbooks" are rare. Actually, my earlier book Efficient C/C++ Programming, w a s designed to provide something like you’re suggesting, with solutions to common problems at the algorithmic level, although the examples were mostly in C and C++ (as the title indicates). There’s also a book called Design Patterns, by Gamma, Helm, Johnson, and Vlissides, that tries to provide tested solutions to common design problems at a much higher level. Details, Details Let’s work through the steps of the algorithm in more detail.13 The algorithm for reading in all of the weights (step 1) should be fairly self-explanatory, once you’re familiar with the syntax of the for statement; it causes the statements in its controlled block to be executed three times, with the index variable i varying from 0 to 2 in Problem Algorithms the process. The algorithm for making a list consisting of the three highest Java weights in descending order (step 2) is quite similar to the process we Interpreter w e n t t h rough to find the highest weight in our previous two Hardware programs; however, the reason for the HighestIndex variable may not be obvious. We need to keep track of which element of the original array (i.e., Weight) we have decided is the highest so far, so that this element won’t be selected as the highest weight on every pass through the Weight array. To prevent this error, step 4 sets each "highest" weight to a value that won’t be selected on a succeeding pass. Since we know there should be no 0 weights in the Weight array, we can set each selected element to 0 after it has been selected, to prevent its re-selection. Figure 4.9 shows a picture of the situation before the first pass through the data, with 0 in SortedWeight to indicate that those locations have been initialized to 0, as the Java language guarantees. ______________________________________________________________________________ 13. They’re in the section titled "You May Already Have Won". DETAILS, DETAILS Index 0 1 2 3 4 Contents of Weight 125 Contents of SortedWeight 5 2 11 3 7 0 0 0 Figure 4.9: Initial situation In Figure 4.9, the highest value is 11 in Weight[2]. After we’ve located it and copied its value to SortedWeight[0], we set Weight[2] to 0, yielding the situation in Figure 4.10. Index 0 1 2 3 4 Contents of Weight Contents of SortedWeight 5 2 0 3 7 11 0 0 Figure 4.10: After the first pass Now we’re ready for the second pass. This time, the highest value is the 7 in Weight[4]. After we copy the 7 to SortedWeight[1], we set Weight[4] to 0, leaving the situation in Figure 4.11. Index 0 1 2 3 4 Contents of Weight 5 2 0 3 0 Contents of SortedWeight 11 7 0 Figure 4.11: After the second pass On the third and final pass, we locate the 5 in Weight[0], copy it to SortedWeight[2], and set Weight[0] to 0. As you can see in Figure 4.12, S o r t e d W e i g h t now has the results we were looking for: the top three weights in descending order. Problem Algorithms Java Interpreter Hardware 126 Index 0 1 2 3 4 Chapter 4: MORE BASICS Contents of Weight 0 2 0 3 0 Contents of SortedWeight 11 7 5 Figure 4.12: Final situation By the way, it’s also possible to initialize a variable to a value other than the default one at the same time as you define it. For example, the statement int i = 12; defines an int variable called i and sets it to the value 12 at the same time. This is generally a good practice to follow when possible; if you initialize the variable when you define it, you don’t have to remember to write a separate statement to do the initialization. Of course, we can’t do this with variables that have to be reinitialized on every time through a loop, as is the case with HighestWeight and HighestIndex. Problem Algorithms Java Interpreter Hardware To Really Foul Things Up Requires a Computer This program will work all right as long as the user types in reasonable values. But what if all the values were typed in as negative values? In that case, the program wouldn’t work, because the initial value of HighestWeight, which is supposed to be lower than any of the typed-in values, would actually be higher than any of those values. The result would be that all of the highest weights would come out as 0. This erroneous result is an example of program failure. We should take some time here to discuss the idea of program failure, because its consequences can be quite serious. The first question, of course, is what it means to say that a program "fails". The simplest answer is that it doesn’t work correctly, but that isn’t very specific. As you can imagine, this notion was the topic of some discussion with Susan. TO REALLY FOUL THINGS UP REQUIRES A COMPUTER 127 Susan: What do you mean by a program failing? I know it means it won’t work, but what happens? Do you just get error messages, and it won’t do anything? Steve : I n g e n e r a l , a p r o g r a m " f a i l i n g " m e a n s t h a t i t d o e s s o m e t h i n g unexpected and erroneous. Exactly what happens depends on the particular situation; for example, because of some safety features in Java arrays, you’ll get an error message if you attempt to misuse an array by referring to a nonexistent element. In general, a program failure may or may not produce an error message. In the specific case that we’ve just seen, we’ll get erroneous output indicating that the top three weights are all 0. However, it’s entirely possible for a program to just "hang" (run endlessly), "crash" your system, produce an obviously ridiculous answer, or worst of all, provide a seemingly correct but actually erroneous result. The causes of program failure are legion. A few of the possibilities are these: 1. Problems isolated to our code. a. The original problem could have been stated incorrectly. b. We could have been using inappropriate algorithm(s). c. The algorithm(s) might have been implemented incorrectly. d. The input to the program might be outside the expected range. And so on. . . 2. Problems interacting with other programs. a. We might be misusing a function supplied by the system, like the System.out.println facility. b. T h e d o c u m e n t a t i o n f o r a s y s t e m f u n c t i o n m i g h t b e incorrect or incomplete. This is especially common in "guru"-oriented operating systems, where the users are supposed to know everything. c. A s y s t e m f u n c t i o n m i g h t b e u n r e l i a b l e . T h i s i s m o r e common than it should be. d. The compiler might be generating the wrong instructions. I’ve seen this on a few rare occasions. e. Another program in the system might be interfering with our program. This is quite common in some popular Problem Algorithms Java Interpreter Hardware 128 Chapter 4: MORE BASICS operating environments that allow several programs to be executing concurrently.14 And so on. . . With a simple program such as the ones we’re writing here, errors like the ones listed under problems with our code are more likely, as we have relatively little interaction with the rest of the system. As we start to use more sophisticated mechanisms in Java, we’re more likely to run into instances of interaction problems. To make the idea of program failure more concrete, let’s spend a little time discussing a horrible example of "input outside the expected range". This example is especially relevant as I write these words in April 1997, because we’re all going to be intimately familiar with its results any day now. Terror in the Year Zero Problem Algorithms Java Interpreter Hardware As I’ve mentioned in Chapter 2, RAM and disk storage were much more expensive in the past than they are today. For this reason, a very large number of programs written before the 1980s, and even some written since then, have employed a space-saving "shortcut" when dealing with dates. To be precise, these programs use a 2-digit number to represent the year portion of a date in the form "YYMMDD" (that is, two digits for the year, two for the month, and two for the day), which saves storage compared to storing the entire year as in the form "YYYYMMDD". I’m sure this seemed like (and may have even been) a good idea at the time, but it has turned out to be a very expensive way to save money; many of these programs are still in use, and none of them will work much longer, if indeed they haven’t already started to fail. How will these programs fail? There are many possible ways, but I can think of a few that are certain to occur. 1. 2. Interest calculations such as for credit card debt, which depend on the number of days since the last payment. Automated inventory systems that calculate the number of items to order based on criteria such as the average of the last 6 months’ orders. ______________________________________________________________________________ 14. Especially those whose names begin with "W". TERROR IN THE YEAR ZERO 3. 129 Programmed maintenance schedules for automobiles, commercial building control systems, and the like, that control the availability of the device based on how long it has been since the last maintenance was done. For example, many elevators are programmed to stop working as a safety measure if an excessive amount of time has elapsed since they were last maintained. To see how these calculations might be incorrect when the year 2000 rolls around, let’s take the first one of these as a more detailed example. Suppose that we have a program that calculates the interest due on a credit card in the following (somewhat oversimplified) way: 1. 2. 3. Calculate the "day number" of the previous payment by the following procedure:15 a. Multiply the year number by 365. b. Add the day of the month. c. Add the day number of the beginning of this month. This is the number of days in the year before the first of the month. This number is 0 for January, 31 for February, 59 for March, and so on. Calculate the day number of the current payment by the same procedure as above, substituting the correct values for the year number, day of the month, and day number of the beginning of the month. Subtract the day number of the previous payment from the day number of the current payment. The result is the number of days between the previous payment and the current payment. For example, if the last payment was on 991103 (that is, November 3rd, 1999), and the current payment was on 991202 (that is, December 2nd, 1999), we calculate the day number of the last payment as 99 * 365 (for the year part) + 3 (for the day of the month) + 304 (for the number of days in the year before the first of November), which gives us a day number of 36442. Then we calculate the day number of the current payment as 99 * 365 (for the year part) + 2 (for the day of the month) + 334 (for the number of days in the year before the first of December), or a day number of 36471. Subtracting the first of these numbers from the second, we ______________________________________________________________________________ 15. I know I’m disregarding leap year, but that would just be another complication that isn’t needed to explain the Year 2000 problem. Problem Algorithms Java Interpreter Hardware 130 Chapter 4: MORE BASICS determine that the number of days between these two payments is 29, which is correct. Now let’s look at the next billing cycle, where the previous payment date will be 991202. Let’s suppose that the next payment is on January 2, 2000, which will be represented as 000102. In that case, we calculate the day number of the last payment as 99 * 365 (for the year part) + 2 (for the day of the month) + 334 (for the number of days in the year before the first of December), which gives us a day number of 36471. Then we calculate the day number of the current payment as 0 * 365 (for the year part) + 2 (for the day of the month) + 0 (for the number of days in the year before the first of January), or a day number of 2. Subtracting the first of these numbers from the second, we determine that the number of days between these two payments is... -36469. Somehow that doesn’t seem right! If the credit card company (and their customers) are lucky, the bills this program generates will be obviously idiotic. If they’re not so lucky, they may appear to be correct but in fact will be wrong in some unpredictable way. Similar horrors will occur in the inventory control, automobile, and elevator maintenance examples. The people responsible for these defective programs can’t really claim that these problems are beyond their control; after all, the fact Problem Algorithms that the year 2000 was coming has been known for quite some time. Given this, surely the companies that are affected by this problem Java Interpreter will have everything fixed by January 1st, 2000. Won’t they? Hardware I’ll Think about It Tomorrow I’m afraid that’s not going to happen. The first reason is that, as I write this in 1997, many companies haven’t even started working on the problem! How did such a vital task get put off until it’s too late? That’s easy to explain. Pretend you are a reasonably competent manager16 working for Amalgamated Conglomerate. Here are some of the major characteristics of the project we need to do: 1. 2. 3. Its success is absolutely essential to the survival of the company. It is mind-numbingly boring, tedious work, which implies even higher turnover than usual in software projects. It is completely invisible to the customer, who has undoubtedly requested a number of new features that will have to be postponed to work on this project. ______________________________________________________________________________ 16. If that’s not an oxymoron. I’LL THINK ABOUT IT TOMORROW 4. 5. 131 The skills learned are completely worthless when the project is over.17 The deadline cannot be postponed by even one day. Now I want a show of hands: Who would like to lead this project? As I suspected, I’m not likely to get very many volunteers. But why should fixing these problems be so difficult? All we have to do is to change the date fields to use four digits rather than two for the year portion of the date, and change all of the code that refers to those date fields. How hard could that be? Much harder than it would be if modern software design rules had been applied. The problem here is that everyone wrote their own date calculation routines, and used these date fields throughout their programs, often without documenting how they worked or where they were used. Some of these programs are thirty years old, and the people who worked on them are long gone, if they’re even still alive. The only way to fix these date problems is to go over the code, line b y l i n e , s e a r c h i n g f o r a n y r e f e r e n c e t o d a t e s .1 8 O n c e e a c h s u c h reference is found, it has to be fixed. Unfortunately, the chance of introducing a new error when fixing an old one is not negligible; in fact, it can be quite high. This means that extremely thorough and careful testing is needed to try to ensure that new errors haven’t been introduced in this process. Okay, so it’s a lot of work. But these companies have had plenty of time to fix it, so why haven’t they done it by now? In the Long Run, We Are All Dead There are a number of excuses, but the truth of the matter is this: Although the company desperately needs these programs to be fixed, it’s not in any individual manager’ s i n t e r e s t t o b e i n c h a r g e o f t h e project. And since there isn’t any "company" to do it if the individual managers don’t want to, it doesn’t get done. In my opinion, this situation has exposed a serious flaw in the structure of corporations: Within a corporation, there is no market. This means that while the benefits of fixing this problem would have ______________________________________________________________________________ 17. At least until the year 9999, when we’ll have to convert to five-digit dates. 18. This is actually too optimistic, as it assumes that you have the source code to examine. Believe it or not, many companies are using programs whose source code has been lost; they have an even worse time trying to figure out what has to be fixed, or even what the program is doing right now. Problem Algorithms Java Interpreter Hardware 132 Chapter 4: MORE BASICS accrued to the entire company, the difficulties would have fallen on the individual managers who were running the projects. Therefore, the most sensible course of action from the point of view of the individual manager was to duck responsibility; with any luck, he or she would have retired or at least moved to another company before the day of reckoning arrived. Was there any way of preventing this outcome? Yes, but it would have required managers to act as entrepreneurs rather than employees. That is, if companies were organized as a number of autonomous groups who bought and sold one another’s products, then anyone who could fix bugs like the Year 2000 problem would have been able to make a good living doing so. Because of the laws of supply and demand, the fewer people who wanted to work on such projects, the more money each one would make. As a result, the rewards for fixing the problem would be commensurate with the difficulty and risk of taking on the project, so we wouldn’t be in the mess that we’re in today. Are there any benefits to be gained from this mess? I can think of some. Of course, one attractive prospect is the possible collapse of certain large universally hated bureaucratic organizations under the weight of massive program failure. If that occurs, it will be worth the Problem Algorithms estimated $600 billion price tag for fixing the problem in companies around the world. It ’s also within the realm of possibility that this Java Interpreter fiasco will encourage a serious reconsideration of the reward and penalty structure of whatever corporations are still in business after Hardware January 1st, 2000. As interesting and even vital as the Year 2000 problem may be, hasn’t it taken us a bit afield from our task of learning how to program? I don’t think so. If you’re going to write programs that anyone is going to depend on, you should have a good understanding of how serious program failure can be as well as some of the more common causes of such failure, even if they are outside the technical aspects of programming.19 Garbage in, Garbage Out In the meantime, there’s something else we should do if we want our example program to work as it should. As the old saying "Garbage in, garbage out" suggests, by far the best solution to handling ______________________________________________________________________________ 19. For a number of other perspectives on this Year 2000 problem, see the February ’96 issue of American Programmer magazine. GARBAGE IN, GARBAGE OUT 133 spurious input values (such as the negative weight values I mentioned previously) is to prevent them from being entered in the first place. What we want to do is to check each input value and warn the user if it’s invalid. Figure 4.13 illustrates a new input routine that looks like it should do the trick. for (i = 0; i < 5; i ++) { k = i + 1; System.out.print("Please type in weight #" + k + ": "); Weight[i] = RWVar.readInt(System.in); if (Weight[i] <= 0) { System.out.println("I’m sorry, " + Weight[i] + " is not a valid weight."); } } Figure 4.13: Garbage prevention, first attempt (from code\Vect2a\Vect2a.java) Most of this should be familiar; the only line that has a new construct in it is the if statement. The condition <= means "less than or equal to", which is reasonably intuitive. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Vect2a". Unfortunately, this program won’t work as we intended. The problem is what happens after the error message is displayed; namely, the loop continues at the top with the next weight, and we n e v e r c o r r e c t t h e e r r o n e o u s i n p u t . S u s a n d i d n’ t h a v e m u c h t r o u b l e figuring out exactly what that last statement meant. Susan: When you say that "we never correct the erroneous input", does that mean that it is added to the list and not ignored? Steve: Right. To fix this problem completely, we need to use the approach shown in the final version of this program (Figure 4.14). Problem Algorithms Java Interpreter Hardware 134 Chapter 4: MORE BASICS import WAJ.*; public class Vect3 { public static void main( String args[ ] ) { int[ ] Weight = new int[5]; int[ ] SortedWeight = new int[3]; int HighestWeight; int HighestIndex; int i; int k; System.out.println("I’m going to ask you to type in five weights, in pounds."); for (i = 0; i < 5; ) { k = i + 1; System.out.print("Please type in weight #" + k + ": "); Weight[i] = RWVar.readInt(System.in); if (Weight[i] <= 0) { System.out.println("I’m sorry, " + Weight[i] + " is not a valid weight."); } else i ++; } Problem Algorithms Java Interpreter Hardware for (i = 0; i < 3; i ++) { HighestIndex = 0; HighestWeight = 0; for (k = 0; k < 5; k ++) { if (Weight[k] > HighestWeight) { HighestWeight = Weight[k]; HighestIndex = k; } } SortedWeight[i] = HighestWeight; Weight[HighestIndex] = 0; } System.out.println("The highest weight was: " + SortedWeight[0]); System.out.println("The second highest weight was: " + SortedWeight[1]); System.out.println("The third highest weight was: " + SortedWeight[2]); } } Figure 4.14: Finding the top three weights using arrays (code\Vect3\Vect3.java) GARBAGE IN, GARBAGE OUT 135 To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Vect3". Now let’s look at the changes that we’ve made to the program from the last revision. The first change is that the first for loop has only two sections rather than three in its control definition (inside the ()). As you may recall, the first section specifies the initial condition of the index variable; in this case, we’re starting i out at 0, as is usual in C++ and Java. The second section indicates when we should continue executing the loop; here, it’s as long as i is less than 5. But the third section, which usually indicates what to do to the index variable, is missing. The reason for this is that we’re going to adjust the index variable manually in the loop, depending on what the user enters. In this case, if the user enters an invalid value (i.e., less than or equal to 0), we display an error message and leave i as it was, so that the next time through the loop, the value will go into the same element in the Weight array. When the user enters a valid value, the else clause increments i so that the next value will go into the next element in the array. This fixes the error in our previous version that left incorrect entries in the array. Now that we have beaten the pumpkin-weighing example to a pulp,20 let’s review the mass of information to which I’ve subjected you so far in this chapter. Review We started out by extending our pumpkin-weighing program to tell us the highest two weights rather than just the highest one. During this exercise, we learned the use of the else clause of an if statement. We also saw that making even an apparently simple change to a working program can introduce an error; in this case, we were copying the highest weight to the next-highest weight only when a new high weight was detected. This would produce an incorrect result if a value higher than the previous second highest but lower than the current highest weight were entered. Next we extended the program again, this time to handle any number of prizes to be given to the highest weight, second-highest ______________________________________________________________________________ 20. Pumpkin pie, anyone? Problem Algorithms Java Interpreter Hardware 136 Chapter 4: MORE BASICS weight, third-highest weight, and so on. This required a complete reorganization of the program; the new version used the selection sort algorithm to produce a list of as many of the highest weights as we need, in descending order. To do this, we had to use an array, or set of values with a common name, to store all of the weights as they were read in. When they had all been entered, we searched through them three times, once to find each of the top three elements. An array, just like a regular variable, has a name. However, unlike a regular variable, an array does not have a single value, but rather consists of a number of elements, each of which has a separate value. An element is referred to by a number, called an index, rather than by a unique name; each element has a different index. The lowest index is 0, and the highest index is 1 less than the number of elements in the array; for example, with a 10-element array, the legal indexes are 0 through 9. The ability to refer to an element by its index allows us to vary the element we are referring to in a statement by varying the index; we put this facility to good use in our implementation of the selection sort, which we’ll review shortly. We then added the for statement to our repertoire of loop control facilities. This statement provides more precise control than the while statement. Using for, we can specify a starting expression, a Problem Algorithms continuation expression, and a modification expression. The starting expression sets up the initial conditions for the loop. Before each Java Interpreter p o s s i b l e e x e c u t i o n o f t h e c o n t r o l l e d b l o c k , t h e c o n t i n u a t i o n expression is checked, and if it is true, the controlled block will be Hardware executed; otherwise, the for loop will terminate. Finally, the modification expression is executed after each execution of the controlled block. Most commonly, the starting expression sets the initial value of a variable, the continuation expression tests whether that variable is still in the range we are interested in, and the modification expression changes the value of the variable. For example, in the for statement for (i = 0; i < 5; i ++) the starting expression is i = 0, the continuation expression is i < 5, and the modification expression is i ++. Therefore, the block controlled by the for statement will be executed first with the variable i set to 0; at the end of the block, the variable i will be incremented by 1, and the loop will continue if i is still less than 5. Then we used the for statement and a couple of arrays to implement a selection sort. This algorithm goes through an "input list" of n elements once for each desired "result element". In our case, we want REVIEW 137 the top three elements of the sorted list, so the input list has to be scanned three times. On each time through, the algorithm picks the highest value remaining in the list and adds that to the end of a new "output list". Then it removes the found value from the input list. At the end of this process, the output list has all of the desired values from the input list, in descending order of size. While it’ s i m p o r t a n t t o i n s u r e t h a t o u r p r o g r a m s w o r k c o r r e c t l y when given correct input, it’s also important to make sure that they work correctly even when given improper input. So the next improvement we made to our pumpkin-weighing program was to tell the user when an invalid value had been entered and ask for a valid value in its place. This involved a for loop without a modification expression, since we wanted to increment the index variable i to point to the next element of the array only when the user typed in a valid entry; if an illegal value was typed in, we requested a legal value for the same element of the array. Exercises 1. If the program in Figure 4.15 is run, what will be displayed? import WAJ.*; public class Morbas01 { public static void main( String args[ ] ) { int[ ] x = new int[4]; int Result; int i; x[0] = 3; for (i = 1; i < 4; i ++) x[i] = x[i-1] * 2; Result = 0; for (i = 0; i < 4; i ++) Result = Result + x[i]; System.out.println(Result); } } Figure 4.15: Exercise 1 (code\Morbas01\Morbas01.java) Problem Algorithms Java Interpreter Hardware 138 2. 3. Chapter 4: MORE BASICS Write a program that asks the user to type in a weight, and display the weight on the screen. Modify the program from exercise 2 to ask the user to type as many weights as desired, stopping as soon as a 0 is entered. Add up all of the weights entered, and display the total on the screen at the end of the program. Answers to exercises can be found at the end of the chapter. Conclusion We’ve covered a lot of material in this chapter in our quest for better pumpkin weighing, ranging from sorting data into order based on numeric value through the anatomy of arrays. Next, we’ll take up some more of the language features you will need to write any significant Java programs. Answers to Exercises Problem Algorithms 1. The correct answer is 45. In case this isn’t obvious, consider the Java following: Interpreter a. The value of x[0] is set to 3. Hardware b. In the first for loop, the value of i starts out at 1. c. Therefore, the first execution of the assignment statement x[i] = x[i–1] * 2; is equivalent to x[1] = x[0] * 2;. This clearly sets x[1] to 6. d. T h e n e x t t i m e t h r o u g h t h e l o o p i i s 2 , s o t h a t s a m e assignment statement x[i] = x[i–1] * 2; is equivalent to x[2] = x[1] * 2;. This sets x[2] to 12. e. Finally, on the last pass through the loop, the value of i is 3, so that assignment statement x[i] = x[i–1] * 2; is equivalent to x[3] = x[2] * 2; This sets x[3] to 24. f. The second for loop just adds up the values of all the entries in the x array; this time, we remembered to initialize the total, Result, to 0, so the total is calculated and displayed correctly. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve ANSWERS TO EXERCISES 139 installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Morbas01". 2. Here’s Susan’s solution to this problem, in Figure 4.16: import WAJ.*; public class Morbas03 { public static void main( String args[ ] ) { int weight; System.out.println("Please write your weight here: "); weight = RWVar.readInt(System.in); System.out.println("I wish I only weighed " + weight + " pounds."); } } Figure 4.16: A weight program (code\Morbas03\Morbas03.java) Here are the questions that she came up with during the process: Susan: Would this work? Right now by just doing this it brought up several things that I have not thought about before. First, is the println really necessary here, or would the program work with just a print? Steve: You can use whichever you want; however, with print the user would type the answer to the question on the same line as the question; with println , t h e a n s w e r w o u l d b e t y p e d o n t h e n e x t l i n e , a s t h e p r i n t l n would cause the active screen position to move to the next line at the end of the question. Susan: OK, that is good, since I intended for the weight to be typed on a different line. Now I understand this much better. But I have another question: How does the program handle the ENTER? I don’t see where it comes into the programs you have written. It just seems that at the end of any pause an ENTER would be appropriate. So is the ENTER something that is part of the compiler that it just knows that by the way the code is written an ENTER will necessarily come next? Problem Algorithms Java Interpreter Hardware 140 Chapter 4: MORE BASICS Steve: The RWVar.readInt input mechanism lets you type until you hit an ENTER, then takes the result up to that point. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Morbas03". 3. This was an offshoot of the previous question, which occurred when Susan wondered whether the program in Figure 4.16 would terminate. Let’s start from that point in the conversation. Susan: Would this only run once? If so, how would you get it to repeat? Steve: We could use a while loop. Let’s suppose that we wanted to add up all the weights that were entered. Then the program might look like Figure 4.17. import WAJ.*; public class Morbas04 Problem Algorithms { public static void main( String args[ ] ) Java { Interpreter int weight; int total; Hardware System.out.print("Please type in your weight, typing 0 to end:"); weight = RWVar.readInt(System.in); total = weight; while (weight > 0) { System.out.print("Please type in your weight, typing 0 to end:"); weight = RWVar.readInt(System.in); total = total + weight; } System.out.println("The total is: " + total); } } Figure 4.17: The weight-totalling program (code\Morbas04\Morbas04.java) In case you were wondering, the reason we have to duplicate the statements to read in the weight is that we need an initial value for ANSWERS TO EXERCISES 141 the variable weight before we start the while loop so that the condition in the while will be calculated correctly. By the way, there’s another way to write the statement total = total + weight; that uses an operator analogous to ++, the increment operator: total += weight;. This new operator, +=, means "add what’s on the right to what’s on the left". The motivation for this shortcut, as you might imagine, is the same as that for ++: It requires less typing, is more likely to be correct, and is easier to compile to efficient code. Let’s see what Susan has to say about this program and the += notation. Susan: Darling, here we go again. Now why are you doing total = total + weight? What is that doing? Steve: It’s calculating the total by adding each weight to the previous total. Susan: Here’s something else that was very confusing. You say that += means "add what’s on the right to what’s on the left", but your example shows that it is the other way around. Unless this is supposed to be mirror imaged or something, I don’t get it. Steve: No, the example is correct. total += weight; is the same as total = total + weight;, so we’re adding the value on the right of the += (i.e., weight) to the variable on the left (i.e., total). Is that clearer now? Susan: OK, I think I’ve got it now. I guess if it were more like an equation, you would have to subtract total from the other side when you moved it. Why is it that the math recollection that I have, instead of helping me, just confuses me? Steve: Because, unfortunately, the = is the same symbol used to mean "is equal to" in mathematics. The = in Java means something completely different: "set the thing on the left to the value on the right". Susan: Ugh, this still seems backwards to me. Steve: .yhw wonk t‘nod I erus m‘I Susan: DARLING!! I am serious, I don’t like this a bit. Okay, this is the deal, say this: Add the old value of total and the value of weight on the right side, and put the result back into the total on the left side thereby modifying the total on the left side to a new value. Problem Algorithms Java Interpreter Hardware 142 Chapter 4: MORE BASICS THIS IS MY DECREE. DO NOT CHANGE. Steve: Yes, ma’am. Can I humbly restate your decree as follows: "Add the value on the right of the += (weight) to the old value of the variable on the left of the +=, (total) replacing the old value of total with the newly calculated value."? Susan: Okay, as long as you keep my decree as it is. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Morbas04". Problem Algorithms Java Interpreter Hardware Chapter 5 "You Know My Methods" Large Problems, Small Files? Java was intended to be suitable for writing large programs.1 Such programs are usually composed of many classes defined in a similar number of source-code files, as I mentioned in Chapter 3.2 In such a case, we must have some way for code in one sourcecode file to refer to code in another one. Similarly, we have to be able to specify where execution of our program should start; this latter requirement is taken care of by the Java rule that execution always starts at main. We’ll get back to this discussion as soon as we look at the definitions and objectives for this chapter. Definitions A source-code file is a file that contains source code for a program. Almost every part of every Java program starts out as a source-code file. A package is an organizing unit that combines a number of sourcecode and byte-code files of a generally useful nature. ______________________________________________________________________________ 1. Note that this does not mean that it is in fact suitable for this purpose. 2. I know I still haven’t told you exactly what a class is. For the moment, you can think of it as part of a larger program. If you’re patient, I suspect that you’ll learn more about classes than you may actually want to! 143 144 Chapter 5: "YOU KNOW MY METHODS" A c l a s s f i l e, which contains byte-code instructions, is the result of compiling a source-code file. A method is a section of code somewhat like a block, but its characteristics are different from those of a block. For one thing, you can’t substitute a method for a statement; also, a method has a name, whereas blocks are anonymous. This name enables one method to start execution of another one. A method call (or just "call" for short) causes execution to be transferred temporarily from the current method to the one named in the call. A called method is a method that starts execution as a result of a method call. A calling method is a method that suspends execution as a result of a method call. Problem Algorithms Java Interpreter Hardware A return statement is the mechanism used by a called method to return a value to the calling method, which picks up just where it left off. A method that doesn’t return a value doesn’t need a return statement; in that case, its execution will end after its last statement has been executed. Objectives for This Chapter By the end of this chapter, you should: 1. 2. 3. 4. 5. Understand how source code is organized. Understand how and when to use methods to reduce the amount of code you have to write. Understand how your source code is turned into a usable program. Understand how storage is assigned to different types of variables. Understand how methods can call one another. GOOD THINGS COME IN SMALL PACKAGES 145 Good Things Come in Small Packages As we’ve already seen, the computer can’t execute source code. Therefore, any source code we write has to be translated into bytecode instructions; the result of such translation is one or more class files. If we have a number of classes that work together, we can use a package to organize the source code for (and the use of) these classes. The idea of these different types of files led to the following discussion with Susan: Susan: So a class file is like some kind of interface between your code and the binary numbers of the machine? I am confused. Steve: The source code for each class is translated by the compiler into a class file. These class files can then be referred to as needed when the classes they define are actually used in a program. As I’ve mentioned before, a number of classes can be grouped into a logical unit called a package. We’ve been using some of the classes that are defined in packages that come with the Java language; for example, System.in and System.out are defined in a class called java.lang.System, which is part of a package called java.lang. Susan: So java.lang is a package? Is that a set of already written programs that you can refer to? Steve: Yes. Susan: The packages contain code segments that are generalized, and the other types of files contain code segments that are program specific? Steve: Right. Susan: Where are these packages? I am serious. Steve: They are in various places in the directory structure of your computer (or a computer on the Internet), based on their names. As this indicates, there are a number of packages, including ones written for more specialized purposes by different companies. Susan: So what is a "source file"? Is it a set of code written by the programmer? S t e v e : It’s a file containing part of a program in source code. Most significant programs consist of a number of files, rather than one big one, partly because it’s easier to find and edit one logical segment of a Problem Algorithms Java Interpreter Hardware 146 Chapter 5: "YOU KNOW MY METHODS" program in its own file than to locate it mixed in with a lot of other code, all in one big file. Susan: Okay then, so a source-code file is just a big logical segment? How is it delineated from the rest of the program? Is it named? How do you find it? Steve: Yes, it has a name, just like any other file. Susan: So a source file is like a package, only, as we have discussed, it is more specific than a package; it is for the program that you are working on? Steve: Right. Susan: Where are these files and how do they get there? Problem Algorithms Java Interpreter Hardware Steve: In the case of files you write, they go wherever you tell the J++ compiler to put them. For example, in the case of your "weight-writing" program, the code you wrote is in a source file in a directory called code\Morbas03. That source file is compiled to make a class file, which you can run by using the Java interpreter. Whenever your program refers to classes from the standard Java packages or to classes I’ve written, those classes will be made available to your program by the interpreter. Susan: So then the source file is a "mini-program" within a program that holds the source code to be later compiled? Steve: It contains part of the source code of a program, which needs to be compiled and combined with other previously compiled code before it can be used. I think that’s the same as what you’re saying. So far, a method sounds a lot like a block, but there are significant d i f f e r e n c e s b e t w e e n t h e s e t w o c o n c e p t s . L e t’ s t a k e a l o o k a t t h o s e differences. Functioning Normally There are basically two differences between a block and a method. First, a block can be used anywhere a statement can be used, but this is not true of a method. Second, a method has a name, which can be used to refer to it from another method. These differences are the result of the way in which we use a method: by mentioning its name FUNCTIONING NORMALLY 147 from another method. This is known as a method call, or just a call, for short. When we call a method, we usually have to provide it with input (for example, some values to be averaged) and it usually produces output which we use in further processing (for example, the average of the input values). Some methods, though, have only one or the other. For example, some pairs of methods consist of one storage method and one retrieval method; the first stores data for the second to retrieve later. In that case, the storage method may not give us anything back when we call it, and the retrieving method may not need any input from us. To see how and why we might use a method, we’ll take a look at a program having some duplicated code (Figure 5.1). I’d like you to look particularly at this line, AverageWeight = (FirstWeight + SecondWeight) / 2; and this one, AverageAge = (FirstAge + SecondAge) / 2; These two lines are awfully similar; the only difference between them is that one of them averages two weights and the other averages t w o a g e s . A l t h o u g h t h i s p a r t i c u l a r e x a m p l e d o e s n’ t t a k e t o o m u c h code to duplicate, it may not be too difficult for you to imagine the inefficiency and nuisance of having to copy and edit many lines of code every time we want to do exactly the same thing with different data. Instead of copying the code and editing it to change the name of the variables, we can write a method that averages whatever data we give it. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Nofunc". Problem Algorithms Java Interpreter Hardware 148 Chapter 5: "YOU KNOW MY METHODS" import WAJ.*; public class Nofunc { public static void main( String args[ ] ) { int FirstWeight; int SecondWeight; int FirstAge; int SecondAge; int AverageWeight; int AverageAge; System.out.print("Please type in the first weight: "); FirstWeight = RWVar.readInt(System.in); System.out.print("Please type in the second weight: "); SecondWeight = RWVar.readInt(System.in); AverageWeight = (FirstWeight + SecondWeight) / 2; System.out.print("Please type in the first age: "); FirstAge = RWVar.readInt(System.in); Problem Algorithms Java Interpreter Hardware System.out.print("Please type in the second age: "); SecondAge = RWVar.readInt(System.in); AverageAge = (FirstAge + SecondAge) / 2; System.out.println("The average weight was: " + AverageWeight); System.out.println("The average age was: " + AverageAge); } } Figure 5.1: A sample program with duplicated code (code\Nofunc\Nofunc.java) Figure 5.2 is a picture of a method call. FUNCTIONING NORMALLY 149 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ÚÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ² ³ ³ ³ static int Average(int First, int Second) ³ ³( 3 ) ³ ³ ³ ³ { ³ ³ ³ ³ ³ ³ int Result; ³ ³ ³ ³ ³ ³ Result = (First + Second) / 2; ³ ³ ³ ³ ³ ÚÄÅÄÄ return Result; ³ ³( 4 ) ³ ³ ³ ³ ³ ³ } ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ public static void main( String args[ ]) ³ ³( 1 ) ³ ³ ³ ³ ³ ³ { ³ ³ ³ ³ ³ ³ ³ ³ int FirstAge; ³ ³ ³ ³ ³ ³ ³ ³ int SecondAge; ³ ³ ³ ³ ³ ³ ³ ³ int AvgAge; ³ ³ ³ ³ ³ ³ ³ ³ FirstAge = 5; ³ ³ ³ ³ ³ ³ ³ ³ SecondAge = 9; ³ ³ ÀÄÅÄÄÄÄ¿ ³ ³ ³ ² ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÙ ³ AvgAge = Average(FirstAge,SecondAge); ³ (2) ³ ³ ³ System.out.println(AvgAge); ³ (5) ³ ³ ³ } ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 5.2: A method call The calling method (1) is main; the method call is at position (2). The called method is Average (3), and the return is at position (4); the returned value is stored in the variable AvgAge, as indicated by the = in the statement AvgAge = Average(FirstAge,SecondAge); and the calling method, main, resumes execution at line (5).3 ______________________________________________________________________________ 3. If you don’t provide a return statement, then the called method will just return to the calling method when it gets to its closing }. However, this is not legal for a method that is defined to return a value. This of course leads to the question of why we’d call a method that doesn’t return a value. One possibility is that the method exists only to produce output on the screen, rather than to return any results. The actions that a method performs other than returning a value are called side effects. Problem Algorithms Java Interpreter Hardware 150 Chapter 5: "YOU KNOW MY METHODS" By the way, it’s important to distinguish between returning a value from a method, which is optional, and returning control from a called method to the calling method, which always happens at the end of the called method (unless the program has terminated due to an error in the called method). In case this distinction isn’t clear, you may be interested to know that Susan had the same reaction. Susan: The sentence that reads, "By the way..." isn’t very clear to me, what exactly is it that you are saying? Steve: I’m saying that when a method is finished executing, it always returns to the calling method so that the calling method can continue executing. That is, when the called method terminates, the interpreter continues execution at the next instruction in the calling method. However, this doesn’t mean that a called method always has to return a value to the calling method when it finishes. Susan: Okay, I understand this now. Problem Algorithms Java Interpreter Hardware While we’re on the subject of the calling method, in case you’re wondering why we started the example at the beginning of main, it’s because every Java program starts executing at that point. When the main method calls another method, such as Average, then main is suspended until Average is finished. When Average finishes, main resumes where it left off. This isn’t limited to one "level" of calls. The same thing can happen if Average, for example, calls another method, let’s say Funca; Average will wait until Funca r e t u r n s b e f o r e c o n t i n u i n g . T h e n w h e n Average finishes, it will return to main, which will take up where it left off. This idea of calling and returning from methods led to the following discussion with Susan: Susan: So if you wanted to be really mean, you could get into someone’s work in progress and stick a return somewhere in the middle of it and it would end the program right there? Now that I am thinking about it, I am sure you could do a whole lot worse than that. Of course, I would never do such a thing, but what I am saying is that whatever you are doing when the program gets to the return statement, then it is the end? Next stop, C:\? S t e v e : I f y o u ’re in the main program, then a return statement means "back to the C:\ prompt". If you’re in a method other than main, it means "back to the method that called this method". In the case of a FUNCTIONING NORMALLY 151 method that returns a value, the expression in the return statement tells the compiler what value to use in place of the method call. For example, the statement AvgAge = Average(i,j); sets AvgAge to the result in the return statement of the method Average. As you can see by looking at that method, the returned value is the average of the two input values, so that is the value that AvgAge is set to by this statement. Susan: Let’s see if I have this right: The return statement has to match the type of the method statement. This is so confusing. Steve: Right, the return value has to match the int. That’s because a method has a return type, just like the type of a variable. In this case, int is the type of the Average method, and the value is filled in by the return statement. Susan: OK, then all this is saying is that the value that is produced is the same type as that declared at the beginning of a program. Since we declared the type of Average as an int, if the value produced were a letter or a picture of a cow, then you would get an error message? Steve: Well, actually a letter (i.e., a char) would be acceptable as an int, due to rules left over from C. Otherwise, you’re exactly correct. Susan: The picture helps with the calling confusion. But I don’t understand why int main is the calling method if the calling method suspends execution. How can you initiate a method if it starts out suspended? I am serious. Steve: Th e m a i n m e t h o d s t a r t s e x e c u t i o n a s t h e f i r s t m e t h o d i n y o u r program. Therefore, it isn’t suspended unless and until it calls another method. Above Average I think it’s time for a more detailed example of how we would use a method. Suppose we want to average sets of two numbers and we don’t want to write the averaging code more than once. The Average method just illustrated provides this service; its input is the two numbers we want to average, and its output is the average. Figure 5.3 shows the code for the method Average without all the lines and arrows. Problem Algorithms Java Interpreter Hardware 152 Chapter 5: "YOU KNOW MY METHODS" static int Average(int First, int Second) { int Result; Result = (First + Second) / 2; return Result; } Figure 5.3: A method to average two values A s h a d b e c o m e r o u t i n e , I c o u l d n’ t s n e a k t h i s i d e a ( o f w r i t i n g a method) past Susan without a discussion. Susan: When you say, "and we don’t want to write the averaging code more than once", are you just saying if you didn’t do the Average method thing then you would have to write this program twice? I mean, for example, would you have to write a program separately for weights and then another one from the beginning for ages? Steve: We wouldn’t have to write an entirely separate program; however, we would have to write the averaging code twice. One of the main purposes for writing a method is so that we don’t have to repeat code. Problem Algorithms Java Interpreter To analyze this piece of code, let’s start at the beginning. Every Hardware method starts with a method declaration, which tells the compiler some vital statistics of the method. The method declaration consists of three required parts: 1. 2. 3. a return type the method’s name an argument list There are also several optional parts, called qualifiers, which provide the compiler with more details about the method being declared. In the current case, we have one qualifier, the static declaration. We’ll take up the meaning of this qualifier in Chapter 7. For now, it’s enough to know that this qualifier is necessary whenever we want to write a simple function like Average, where we supply all the data necessary for the function to work. In the case of our Average method, the rest of the method declaration is int Average(int First, int Second). The return type is int, the name of the method is Average, and the argument list is (int First, int Second). Let’s take these one at a time. RETURN TO SENDER 153 Return to Sender The first part of the method declaration that we’ll discuss here is the return type, in this case int. This indicates that the method Average will provide a value of type int to the calling method when the Average method returns. Looking at the end of the method, you will see a statement that says return Result;. Checking back to the variable definition part of the method, we see that Result is indeed an int, so the value we’re returning is of the correct type. If that were not the case, the compiler would tell us that we had a discrepancy between the declared return type of our method and the type actually returned in the code. This is another example where the compiler helps us out with static type checking, as mentioned in Chapter 3; if we say we want to return an int a n d t h e n r e t u r n s o m e o t h e r i n c o m p a t i b l e t y p e s u c h a s a S t r i n g , w e ’ v e m a d e a m i s t a k e .4 I t ’ s m u c h e a s i e r f o r t h e compiler to catch this and warn us than it is for us to locate the error ourselves when the program doesn’t work correctly. Susan wanted to know more about the return type. Here’s the conversation that ensued: Susan: This return type thing — it will have to be the same type of value as the output is? Steve: For our purposes here, the answer is yes. As I’ve already mentioned, there are exceptions to this rule, but we won’t need to worry about them. Susan: Do you always use the word return when you write a method? Steve: Yes, if you’re writing a method that has a return value. Methods that don’t have return values (like main) don’t have to have an explicit return statement. When execution gets to the end of the method, it will return to the calling method, as though the statement return; had been executed. The method name (in this case, Average) follows the same rules as a variable name. This is not a coincidence, because both method names and variable names are identifiers, which is a fancy word for "user______________________________________________________________________________ 4. What do I mean by an incompatible type? Java has rules that, for example, allow us to return a char variable where an int is expected; the compiler will convert the char into either of those types for us automatically. This is convenient sometimes, but it reduces the chance s o f catching an error of this kind, and therefore is less safe than it could be. This practice is a legacy from C, which means that it probably won’t be changed for practical reasons, even though it is less than desirable theoretically. Problem Algorithms Java Interpreter Hardware 154 Chapter 5: "YOU KNOW MY METHODS" defined names". The rules for constructing an identifier are pretty simple, as specified in the Java Language Specification: An identifier is an unlimited-length sequence of Java letters and Java digits, the first of which must be a Java letter. An identifier cannot have the same spelling (Unicode character sequence) as a keyword, ... Two identifiers are the same only if they are identical, that is, have the same Unicode character for each letter or digit. In other words: 1. 2. 3. Problem Algorithms Java Interpreter Hardware Your identifiers can be as long as you wish; the compiler is required to distinguish between two identifiers, no matter how many identical characters they contain, as long as at least one character is different in the two names.5 They can be made of any combination of Java letters and Java digits, as long as the first character is a letter.6 For historical reasons, the underscore character _ and the dollar sign $ count as letters.7 The upper and lower case versions of the same character aren’t considered equal as far as names are concerned; that is, the variable xyz is a different variable from Xyz, whereas XYZ is yet another variable. Of course, y o u may get confused by having three variables with those names, but the compiler considers them all distinct. By the way, the reason that the first character of an identifier can’t be a digit is to make it easier for the compiler to figure out what’s a number and what isn’t. Another rule is that user-defined names cannot conflict with names defined by the Java language (keywords); some examples of keywords that we’ve already seen are if and int. Finally, we have the argument list. In this case, it contains two arguments, an i nt called First, which holds the first number that our ______________________________________________________________________________ 5. You don’t have to worry about wasting space in your program by using long identifiers. They go away when your program is compiled and are replaced by addresses of the variables or methods to which they refer. 6. What is a "Java letter"? This category includes all the English letters in both upper and lowercase (that is, a-z and A-Z). However, because Java is intended to be internationally useful, a Java letter is pretty much any character in any language that is used to make up words in that language. This means that you can use Greek letters, for example, in your identifiers, although that is much more convenient if you have a keyboard and display that can handle them. 7. You should avoid using the $ in your identifiers, as names using that character are sometimes generated by compiler writers and other language implementers. RETURN TO SENDER 155 Average method uses to calculate its result; the second argument (also an int) is called Second, which of course is the other number needed to calculate the average. In other cases, there might be several entries in the argument list, each of which provides some information to the called method. But what exactly is an argument? For the Sake of Argument The question of what is an argument is more subtle than it may appear. An argument is a value that is supplied by a method (the calling method) that wishes to use the services of another method (the called method). For example, the calling method might be our main method, and the called method might be our Average method, whereas the arguments are two int values to be averaged. Arguments are actually copies of values from the calling method; that is, the compiler will set the variable named in the argument list of the called method to the value supplied by the calling method. This process of making a copy of the calling method’s argument is referred to as call by value, and the resulting copy is called a value argument. Figure 5.4 is an example of this argument passing mechanism at work with only one argument. In this program, main sets x to 46 and then calls Birthday with x as the argument. When Birthday starts, a new variable called age is created, and set to 46, because that’s the value of x, the argument with which main called Birthday. Birthday adds 1 to its variable age, and then returns the new value of that variable to main. What value will be printed for the variable age by the line System.out.println("Your age was: " + x);? Answer: 46, because the variable age in Birthday was a copy of the argument from main, not the actual variable x named in the call to Birthday. On the other hand, the value of y in the main program will be 47, because that is the return value from Birthday. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\BirthdayTest". Problem Algorithms Java Interpreter Hardware 156 Chapter 5: "YOU KNOW MY METHODS" import WAJ.*; public class BirthdayTest { public static void main( String args[ ] ) { int x; int y; x = 46; y = Birthday(x); System.out.println("Your age was: " + x); System.out.println("Happy Birthday: your age is now " + y); } static int Birthday(int age) { age ++; return age; } } Problem Algorithms Figure 5.4: Argument passing with one argument Java (code\BirthdayTest\BirthdayTest.java) Interpreter Hardware As you might have guessed, the notion of copying the argument when a method is called occasioned an intense conversation with Susan. Susan: This is tough. I don’t get it at all. Does this mean the value of the int named x will then be copied to another location in the method named Birthday? Steve: Yes, the value in the int named x will be copied to another int called age before the execution of the first line in the method Birthday. This means that the original value of x in main won’t be affected by anything that Birthday does. Susan: Now for the really confusing part. I don’t understand when you say, "An argument like the one here (int age) is actually a copy of a value in the calling method". Now I have read this over and over and nothing helped. I thought I understood it for a second or two and then I would lose it; finally, I have decided that there is very little in this section that I do understand. Help. FOR THE SAKE OF ARGUMENT 157 Steve: When you write a method, the compiler inserts code at the beginning of the method to make a copy of the data that the calling method supplies. This copy of the data is what the called method actually refers to, not the original. Therefore, if you change the value of an argument, it doesn’t do anything to the original data in the calling method.8 Susan: I don’t understand why it is a copy of the calling method and not the called method. Steve: It’s not a copy of the calling method; it’s a copy of the value from the calling method, for the use of the called method. In the sample program, main sets x to 46 and then calls Birthday with x as the argument. When Birthday starts, a new variable called age is created, and set to 46, because that’s the value of x, the argument with which main called Birthday. Birthday adds 1 to its variable age, and returns the new value of age to main. What value will be printed for the variable age by the line System.out.println("Your age was: " + x);? Answer: 46, because the variable age in Birthday was a copy of the value of the argument from main, not the actual variable (x) specified in the call to Birthday. Does this explanation clarify this point? Susan: I still don’t understand the program. It doesn’t make any sense. If x = 46, then it will always be 46, no matter what is going on in the called method. So why call a method? You know what, I think my biggest problem is that I don’t understand the argument list. I think that is where I am hung up on this. Steve: The arguments to the method call (x, in the case of the method call Birthday(x)) are transferred to the value of the argument in the method itself (the int variable age, in the case of the method Birthday(int age)). Susan: In that case, why bother putting an x there, why not just put 46? Would it not do the same thing in the called method, since it is already set to 46? Steve: Yes, but what if you wanted to call this method from another place where the value was 97 rather than 46? The reason that the argument is a variable is so you can use whatever value you want. Susan: If we called Birthday with the value 46, then the 46 would be 46++, right? ______________________________________________________________________________ 8. This is the behavior when the variables being provided are ints, shorts, chars, or any of the other "primitive" types that are part of the Java language itself. As we’ll see later, things can get considerably more complicated when we use variables of other types. Problem Algorithms Java Interpreter Hardware 158 Chapter 5: "YOU KNOW MY METHODS" Steve: 46++ is a syntax error, because you can’t change the value of a literal constant. Only a variable can be modified. Susan: So if you want to state a literal value, do you always have to declare a variable first and then set a variable to that literal value? Steve: No, sometimes you can use a literal value directly without storing it in a variable. For example, System.out.print(15); or System.out.print("Hello, my name is Steve Heller"); What I was trying to say is that you can’t change a literal value. Thus, 15++; is not legal, because a literal value such as 15 represents itself, that is, the value 15. If you could write 15++;, what should it do? Change all occurrences of 15 to 16 in the program? Susan: I get that. Now how does age get initialized to the value of x? Problem Algorithms Java Interpreter Hardware Steve: The compiler does that when it starts the method, because you have stated in the method declaration that the argument to the method is called age, and you have called the method with an argument called x. So the compiler copies the value from x into age right before it starts executing the method. Susan: Oh, OK. That makes sense, because maybe later on you want to call the same method again and change only a little part of it, but you still need the original to be the same, so you can just change the copy instead of the original. Is that the purpose? Steve: The reason that the called method gets a copy of data rather than the original is so that the person writing the calling method knows that the original variable hasn’t been changed by calling a method. This makes it easier to create programs by combining your own methods with methods that have already been written. Is that what you meant? Susan: So is everything copied? I am getting confused again; are you going to talk a little more about copying in the book? Have I just not gotten there? Anyway, if you haven’t mentioned this more, I think you should; it explains hidden stuff. Steve: Don’t worry, we’re going to go into much more detail about how this works. GENERAL DELIVERY 159 General Delivery The same analysis that we have just applied to the B i r t h d a y method applies also to the Average method that we started out with; the a r g u m e n t s First and Second are copies of the values specified in the call to Average. Now that we have accounted for the Average method’ s i n p u t a n d output, we can examine how it does its work. First, we have a variable definition for Result, which will hold the value we will return to the calling method; namely, the average of the two input values. Then we calculate that average with the statement Result = (First + Second) / 2;. Once the average has been calculated, we’re ready to return it to the calling program, which is accomplished by the line return Result;. Finally, we reach the closing }, which tells the compiler that the method is done. Using a Method N o w t h a t w e h a v e s e e n h o w t o w r i t e t h e Average m e t h o d , l e t ’ s s e e how to use it to solve our original problem. The program in Figure 5.5 uses our Average method twice, once to average two weights, and once to average two ages. As before, calling a method requires specifying its name and its argument(s) and doing something with the return value, if any. In this case, we call Average with the arguments FirstWeight and SecondWeight, and store the result in AverageWeight. This is accomplished via the line AverageWeight = Average(FirstWeight, SecondWeight);. Later, we call Average with the arguments FirstAge and SecondAge, and store the result in AverageAge. We do this via the line AverageAge = Average(FirstAge, SecondAge);. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Func1". Problem Algorithms Java Interpreter Hardware 160 Chapter 5: "YOU KNOW MY METHODS" import WAJ.*; public class Func1 { public static void main( String args[ ] ) { int FirstWeight; int SecondWeight; int FirstAge; int SecondAge; int AverageWeight; int AverageAge; System.out.print("Please type in the first weight: "); FirstWeight = RWVar.readInt(System.in); System.out.print("Please type in the second weight: "); SecondWeight = RWVar.readInt(System.in); AverageWeight = Average(FirstWeight, SecondWeight); System.out.print("Please type in the first age: "); FirstAge = RWVar.readInt(System.in); Problem Algorithms Java Interpreter Hardware System.out.print("Please type in the second age: "); SecondAge = RWVar.readInt(System.in); AverageAge = Average(FirstAge, SecondAge); System.out.println("The average weight was: " + AverageWeight); System.out.println("The average age was: " + AverageAge); } static int Average(int First, int Second) { int Result; Result = (First + Second) / 2; return Result; } } Figure 5.5: Using the Average method (code\Func1\Func1.java) USING A METHOD 161 Why we would want a method to average two numbers wasn’t obvious to Susan at first. After some discussion, however, she agreed that it was valuable. Here’s the conversation that convinced her: Susan: In general, I just don’t understand why you even need to call the Average method in the first place; it looks like extra steps to me. It seems to me that all you need are your two input values, which end up just giving you the results right there for weight and age. I think that this is what bothers me the most. For example, when you get done with the set of weights, you should just have your results right then and there instead of calling the method of Average. Steve: But what is the result you want? You want the average of the weights. Where is that calculated? Susan: After you are done with that, then you already have written a set of ages so you can just use the result of that. It just seems like you are going in circles unnecessarily with this program. That is why I don’t understand it. Steve: Again, just because you have a set of ages doesn’t mean that you have the average age; some code somewhere has to calculate that average. Then our discussion returned to the topic of how the main program works. Susan: Oh, OK, so AverageWeight = Average(FirstWeight, SecondWeight); is the part that starts the Average method running? Steve: Right. The next topic we discussed was how to create a new program and get it to run. Susan: Now when you start out a new program, are all the new source files named with a java extension? Steve: Yes. Susan: So this code in Average is where the real averaging takes place, right? Is this the "Average command"? I thought Average meant to average, so what is the deal? Steve: The deal is that something has to do the averaging; rather than writing the same code every time we need to average another set of two Problem Algorithms Java Interpreter Hardware 162 Chapter 5: "YOU KNOW MY METHODS" numbers, we put that code in one place (the Average method) and call it whenever we need its assistance. I’ve updated the text to explain this motivation for using methods. Susan: So does that mean you could put the Average method anywhere you want? Then could it or any "submethod" be put anywhere you want because the main method would always be executed first? Or could you mess up the code if you put it in a really ridiculous place like inside an output or input statement, or could the compiler ignore something like that and go about business as usual? I guess because of the brackets it should ignore such a thing, but I am not sure. See, these are the things that we novices are obliged to ponder. Steve: You can’t start a method definition in the middle of another method. That’s called a nested method and it’s not allowed in Java. The rule can be stated approximately as, "You can start a method definition anywhere except in the middle of another definition." Susan: So then the "cue" for the Average method to begin is the word Average(weight) or Average(age), when the compiler sees that word it just begins that separate method to start its little calculation. Problem Algorithms Java Interpreter Hardware Steve: That’s right, except that it needs two arguments, not just one. Susan: And that method, since it was named Average, causes the averaging method to work. Is that how it goes? Steve: If I understand your question, it’s not the name that makes the Average method do the averaging; it’s the code that adds up the two values and divides by 2. We could replace all the references to Average with the word Glorp and the compiler wouldn’t care; however, a future programmer trying to read the program probably wouldn’t be amused by that name. Susan: Oh, so there is nothing magical about the word Average; I thought it might trigger a method of averaging. Well, that sounds reasonable; it’s more for us humans than the computer. And then that brings up another question, along the same line of thinking. After the Average method has done its thing, how does the program go from return Result; to the next output statement that asks for the ages? What triggers that change? I am not seeing this in the code. Steve: The return keyword tells the compiler to hand back control to the method that called the one where the return is, as indicated in Figure 5.2. USING A METHOD 163 This discussion didn’t slake her thirst for knowledge about how to write a program. Here is how we continued: Susan: Can I mix ints with Strings? Steve: Mixing ints with Strings is a dubious proposition, sort of like asking a desk to make you lunch; could you be more specific about what you’re trying to do? Susan: What if you wanted to add a numerical value to your program: You have to put in an int, right? So if you added an int, what else would you have to do to make it work? Or would you have to start over with another main method after the first part and then declare new variables? I tried that too, and the compiler did not like that either. Very inflexible it is. I will tell you after one more try what I am doing. This will keep you in suspense. Steve: It depends on what you’re trying to do with the int. It’s usually best to have a specific problem in mind that you’re trying to solve by writing a program. Then you can see how to use these facilities (ints, Strings, arrays, etc.) to solve your problem; if not, you can ask me how they would fit into the solution. As for your second suggestion, you are allowed to have more than one main method in a particular program, but only one can be the "active" main at any given time. The interpreter determines which one is active based on the name of the program you’re running and where it is in the directory structure. Susan: I am not really trying to solve anything, I just want to have the user type in more info and that info is a number — wait!! That is it; in that case it will be like a character and it doesn’t need an int, right? That’s right. I can still use a String. We are not crunching any numbers with this. Steve: As long as you don’t try to do any calculations, you can read the data into a String, even data that looks like a number; of course, that data entry method is pretty loose, since if the user types "abc" as an age, the program will accept it. Susan: Can you define a String without a word but with just a wildcard type of variable like when we use i in ints? In other words, does it matter what we call a variable? Steve: A variable is always a "wildcard", whether it’s an int or a String. For example, an int variable always has a name, such as i, or index, or whatever makes sense to you, and a value, which is a number such as 14 or 99. A String variable also has a name, such as FirstName, or street, or Problem Algorithms Java Interpreter Hardware 164 Chapter 5: "YOU KNOW MY METHODS" whatever makes sense to you, and a value, which consists of characters rather than a number, such as "Susan" or "Wesley". A Convincing Argument As you can see, using a method isn’t very difficult. We have to provide it with the material to work on (its input arguments) and can store its return value in a variable for further processing (or use it directly, if we wish). B u t t h e r e ’s a little more at work with arguments than meets the eye. How does the variable FirstWeight, for example, get transformed into the variable First that we used when writing the method? This explanation requires us to to look at some more underlying software technology: To be precise, we’re going to spend some time examining the infrastructure that makes computers usable for programmers. A First-class Solution Problem Algorithms Java Interpreter Hardware We’ve already seen that the function of a compiler is to convert our human-readable Java program into byte codes that can be interpreted by the Java interpreter. In addition, the compiler doesn’t actually turn a whole program into one byte-code file; instead, it translates each source file into one or more class files. These files contain the instructions that correspond to the source-code statements you’ve written, but not the "infrastructure" needed to allow them to be interpreted. We’ll see what that infrastructure does for us shortly. Why does the Java compiler produce all these different class files rather than one file that contains the byte-codes for the whole program? Because, as I’ve mentioned before, Java was intended to be useful in writing large programs. Such programs can consist of hundreds or even thousands of source-code files, each containing hundreds or thousands of lines of code. Once each source file is compiled, the class files resulting from that compilation can be referred to by other programs. When one source file is changed, only those files that have been affected have to be recompiled.9 To make such a system work, it’s necessary to set up conventions as to which parts will be executed first, where data needed by more ______________________________________________________________________________ 9. The alert reader may wonder why I said, "those files that have been affected" rather than, "those files that have been changed". The reason is that even if we don’t change a particular file, we may have to recompile it if it uses a class that has been changed. A FIRST-class SOLUTION 165 than one file will be stored, and so on. Also, a lot of operations aren’t supplied as part of the language itself but are very handy in writing p r o g r a m s , s u c h a s t h e I / O m e t h o d s t h a t w e’ v e a l r e a d y s e e n . T h e s e make up the infrastructure needed to execute Java programs. Susan found this explanation to be helpful. Susan: This is beginning to come into focus. So you write your source code, and it has a middle man called a class file so it can be used by other programs. Close? Steve: No, exactly right! Operating Systematically As is often the case in programming, this infrastructure is divided into several layers, the higher ones depending on the lower ones for more fundamental services. The lowest level of the infrastructure is supplied by the operating system, a program that deals with the actual hardware of your computer. By far the most common operating system for Intel CPUs, as this is written, is MS-DOS (which is also the basis for Windows 95), followed by OS/2 and Windows NT. All of these provide some of the same facilities. For example, you are accustomed to dealing with files and directories when using application programs such as word processors and spreadsheets; however, the disk drive in your computer doesn’t know anything about files or directories. As we have seen in Chapter 2, all it can do is store and retrieve fixed-size pieces of data called sectors, given an absolute address on the disk described by a platter, track number, and sector number. Files are a creation of the operating system, which keeps track of which parts of which files are stored where on the disk.10 A modern operating system provides many more facilities than just keeping track of file storage. For example, it arranges for code and data to be stored in separate areas of RAM with different access rights, so that code can’t be accidentally overwritten by a runaway program; that is, one that writes outside the memory areas it is ______________________________________________________________________________ 10. You might say that files are "virtual"; that is, they’re a figment of the operating system’s imagination. Nonetheless, they are quite useful. This reminds me of the story about the man who went to a doctor, complaining that his brother had thought he was a hen for many years. The doctor asked why the family hadn’t tried to help the brother before, and the man replied, "We needed the eggs". Problem Algorithms Java Interpreter Hardware 166 Chapter 5: "YOU KNOW MY METHODS" supposed to use. This is a valuable service, as errors of this kind are quite difficult to find and can cause havoc when they occur. That’s the good news. The bad news is that MS-DOS was created before the widespread availability of reasonably priced CPUs with memory protection facilities and therefore doesn’t take advantage of these facilities, although they are present in Windows 3.1 and Windows 95, which run "on top of" MS-DOS. Running under plain MS-DOS, it’s entirely possible for a runaway program written in most languages to destroy anything else in memory. Theoretically, we should all be running "real" operating systems by the time you read this; so far, though, the rumors of the demise of MS-DOS have been greatly exaggerated. This notion intrigued Susan. Here’s how that conversation went: Susan: What is a runaway program? Steve: One that is writing in areas that it shouldn’t, thus destroying data or programs outside its assigned memory areas. Problem Algorithms Java Interpreter Hardware Susan: How would an operating system actually separate code from data areas? Would it be a physical thing? Steve: What makes this possible are certain hardware mechanisms built into all modern CPUs, so that certain areas of memory can be assigned to specific programs, for use in predefined ways. When these mechanisms are used, a program can’t write (or read, in some cases) outside its assigned area. This prevents one program from interfering with another. One of the advantages of Java, however, is that Java programs are run under control of a Java interpreter, which means that they can be prevented from running wild. In fact, such safety is a primary goal of the Java language, and there are quite a few error checks made by the interpreter to ensure that your program doesn’t affect anything that it isn’t supposed to. As this suggests, the Java interpreter is another layer of the infrastructure needed to run a Java program. Its basic operation is fairly simple: It uses each of the byte codes that result from compiling a Java program to determine which piece of code to execute on the real CPU. In other words, it is a "virtual CPU" that behaves exactly like a real CPU that uses Java byte codes as its machine language.11 ______________________________________________________________________________ 11. In fact, it’s quite possible to build such a CPU; you may see them on the market in the relatively near future if they aren’t already available by the time you read this. OPERATING SYSTEMATICALLY 167 The next level of the infrastructure is supplied by the aforementioned packages, which contain standardized segments of code that can perform I/O, mathematical functions, and other commonly used operations. So far, we have used the java.io and java.lang packages to do screen output in our example programs and when using Strings.12 Don’t Call Me, I’ll Call You Now let’s get back to our averaging example. In Figure 5.5, there are two calls to the method Average: The first one is used to average two weights and the other to average two ages. One point I didn’t stress was exactly how the Average method "knew" which call was which; that is, how did Average return to the right place after each time it was called? The answer is fairly simple: The interpreter notifies the called method of the address of the next byte-code instruction that should be executed after the called method is finished (the return address), and the called method stores that return address in its piece of the stack. As you might have guessed, Susan and I went over this in gory detail. Here’s the play by play: Susan: Yes, I think this is what has confused me in the past about methods. I never fully understood the mechanism by which one method calls another. I still don’t. But I guess it is by the position of the next address in a stack? Steve: The stack is used to pass arguments and get return values from methods, but its most important use is to keep track of the return address where the calling method is supposed to continue after the called method is done. Susan: This is how I am visualizing the use of the stack pointer. In one of my other books, it showed how the clock worked in the CPU and it seemed to cycle by pointing in different directions as to what was to happen next in a program. So it was sort of a pointer. Is this how this pointer works? So let me get this straight. All CPUs have some kind of stack pointer, but they are used only for calling methods? Exactly where is the instruction call? It sounds to me like it is in the hardware, and I am having a very difficult time understanding how a piece of hardware can have an instruction. ______________________________________________________________________________ 12. Another package that we’ve used is the WAJ package, which supplies the ReadInt and ReadString methods. Problem Algorithms Java Interpreter Hardware 168 Chapter 5: "YOU KNOW MY METHODS" Steve: All of the instructions executed in a Java program are executed by the interpreter. When a method call is performed, in particular, the interpreter does a number of things, of which two are relevant here. 1. It saves the address of the next instruction (the contents of the Java program counter) on the stack.13 2. It changes the Java program counter to point to the first byte-code instruction of the called method. The return byte-code instruction (or a variant of this instruction) is used to return to the calling method. It does this by the following steps: 1. It retrieves the saved value of the program counter from the stack. 2. It sets the Java program counter back to that value. The result of this is that execution of the program continues with the next byte-code instruction in the calling method. Problem Algorithms Java Interpreter Hardware Susan: Are you saying that, rather than the pointer of a stack actually pointing to the top of the physical stack, wherever it points to by definition will be the top of the stack, even if it really doesn’t look like it? Steve: Absolutely correct. It’s time to go over the material we’ve covered in this chapter. Review First, we added the fundamental programming concept of the method. A method is a piece of code that can "stand alone"; it can be compiled separately from other methods, and provides some service that we can use via a mechanism known as a method call. The method that makes the call is known as the calling method, and the one it calls is known as the called method. Before we can call a method, we need to know what input values it needs and what it returns. This information is provided by a method declaration at the beginning of each method. This includes an argument list, which ______________________________________________________________________________ 13. The Java program counter is a variable in the Java interpreter that keeps track of the address of the next instruction to be executed, just as the dedicated program counter register does in a real CPU. REVIEW 169 specifies input values that the called method uses (if any), and a return type, which specifies the type of the value that it produces when it’s finished (if any). When we call a method, it executes until it reaches the end of its code or reaches a return statement, whichever comes first. When either of these events happens, the program continues execution in the calling method immediately after the place where the method call occurred. Ordinarily, as in our example, an argument to a method is actually a copy of the variable in the calling program, so that the called method can’t modify the "real" value in the caller. This way of passing an argument is known as call by value.14 We also saw that method and variable names can be of any length, consisting of upper or lower case Java letters (or both), Java digits, and the special characters underscore (_) and dollar sign ($). To make it easier for the compiler to distinguish numbers from variable names, the first character can’t be a digit. Also, a variable name can’t be the same as a keyword, or name defined by the language; examples of keywords we’ve seen so far include if, for, and int. After finishing the construction of our Average method, we saw how to use it by making a method call. Conclusion We’ve covered a lot of material in this chapter, ranging from the anatomy of methods through a lot more information on what’s going on "underneath the covers" of even a fairly simple Java program. Next we’ll see how to write a realistic, although simplified, application program using some more advanced concepts in Java. ______________________________________________________________________________ 14. As we’ll see in Chapter 6, argument passing is considerably more complicated when we’re dealing with variables other than the primitive types that are built into the Java language. Problem Algorithms Java Interpreter Hardware Chapter 6 Taking Inventory A class Act Now we have enough of the fundamentals of programming under our belts to look at one of the more powerful features of Java: the ability to create user-defined data types. What are these, and why do we care? In Java, the available data types are mostly divided into two groups: primitive (i.e., defined in the language itself) and user defined (i.e., defined by the programmer).1 The primitive types that we’ve been using are char, short, and int, all of which have been inherited from C.2 In this chapter, we’ll see how to give the compiler enough information to create our own data types. Let’s start out with some definitions and objectives. Definitions A primitive type (sometimes called a native type) is a data type that is built into the Java language and cannot be replaced or modified by the Java programmer. ______________________________________________________________________________ 1. There are a couple of exceptions to this division: Strings act partially like a primitive type and partially like a user-defined type, and arrays can be used with both primitive and user-defined types, but can’t themselves be redefined by the user. 2. There are actually several other primitive Java types that we haven’t used: byte, long, float, double, and boolean. We’ll use float to keep track of numbers that have fractional parts in some examples in this and later chapters. The boolean type is useful for keeping track of a true/false condition, as we’ll see later in this chapter. The others are covered to some extent in Appendix A. 171 172 Chapter 6: TAKING INVENTORY A class is a user-defined type. A class definition tells the compiler what facilities the class supplies and how to implement those facilities. A class definition is found in a source-code file, which has the extension .java. An object is a programming construct that represents a specific item of user-defined data that we wish to keep track of in a program. Note that an object is similar to a variable in that it represents an item of data. However, unlike a variable, we cannot use an object directly but must refer to it via a reference. A reference is a variable that refers to an object and thus allows us to use that object. A field is a variable that is part of the definition of a class. O b j e c t - o r i e n t e d p r o g r a m m i n g is the organization of programs as collections of objects, rather than as collections of methods operating on variables of primitive data types. Problem Algorithms A class interface is the set of operations that objects of that class can perform, as contrasted with the implementation of those operations. Java Interpreter Encapsulation is the concept of hiding the details of a class inside Hardware the implementation of that class rather than exposing them in the interface. This is one of the primary organizing principles that characterize object-oriented programming. Objectives for This Chapter By the end of this chapter, you should: 1. 2. 3. Understand what a user-defined type (a class) is, and how it is defined. Understand how variables of some simple classes are created, destroyed, and copied. Understand how and why access to the internals of a class is controlled. PAY SOME ATTENTION TO THE MAN BEHIND THE CURTAIN 173 Pay Some Attention to the Man behind the Curtain Susan had a revealing question here. Susan: I think I need to find out something here. I am getting the impression that what is "primitive" is C and what is "user-defined" is Java. Is that right? And if so, why? Steve: Close, but not exactly right. Java inherited much of its syntax from C++ (the successor to C), but user-defined types aren’t supported as well in Java as in C++. As to why, the answer is pretty simple: The reason that Java was invented in the first place was to create a simpler language by keeping only the necessary features from C++ and adding new features that the designers thought would be useful but didn’t exist in C++. As to whether they picked the right features in the first category, I’m not convinced. In any event, Java is the way it is (for now, anyway), and we’ll have to make the best of it. In Java, it is not possible to create user-defined data types that behave in the same way as primitive types, so it is important to distinguish the characteristics of these two kinds of data types. Native variables in Java can be defined, initialized, assigned values, passed as arguments and return values, and compared to other values of the same type. However, most of these facilities don’ t w o r k t h e s a m e way for user-defined data types in Java. To explain this difference, we’re going to have to go into detail about how we use objects (that is, data items of a user-defined type). We can never use objects directly; instead, we have to refer to them via references. What is a reference? We can think of it as a "secretary" for an object. Whenever we want to do anything with an object, we ask a reference to pass the message along to it. This may seem a distinction without a difference; after all, why should we care whether we access an object directly or through a reference? Unfortunately, we do have to be concerned with this apparently arcane topic, because there are some very important differences between the way references are treated by the compiler and the way primitive variables are treated. For example, while the == operator can be applied to references, it doesn’t tell you whether the objects that the references refer to have the same value; instead, it tells you whether the references refer to the same object. To compare the values of two objects, you have to write a function (usually called equals) for this purpose. Problem Algorithms Java Interpreter Hardware 174 Chapter 6: TAKING INVENTORY We’ll see soon enough how these differences affect the way that we use references in our programs. First, though, let’s start by defining a simple class. Second-class Citizens In Java, a user-defined data item is called an object. Each object has a type, just as a primitive variable does. For example, if we define a class called StockItem (as we will do in this chapter), then an object can be of type StockItem, just as a primitive variable can be of type int, char, or one of the other primitive types. However, an additional step is required with user-defined types. Since the compiler has no intrinsic knowledge of these types, we have to tell it exactly what they are and how they work. We do this by defining a class, which specifies both the data contained in the user-defined variable and what operations can be performed on these data. Here’s how Susan reacted upon her first encounter with this idea: Susan: I can tell that there is only one thing that I think that I understand Problem about this. That is, that Java is not a language. You have to make it up as Algorithms you go along. Java Interpreter That may be overdoing it a bit, but there is a grain of truth in her observation: Java is more of a "language kit" than it is a language. Hardware What do I mean by this? I mean that to use Java in the most effective way, it is necessary to make up our own data types. So far in this book, we have been using data types that were previously defined, either by the compiler and language (primitive types, e.g., short , i n t , c h a r ) o r b y packages (class types, e.g., String). Now we ’ r e g o i n g t o a c t u a l l y m a k e u p o u r o w n types; the difference between using preexisting types and making up new types is analogous to the difference between using a program and writing a program, but carried to the next higher level. In the event that you find this notion hard to understand, you’re not alone; neither did Susan. Susan: This is an outrage! I didn’t understand one other word as I was far beyond anything that could even be described as shock. I think I did faint. I may as well have been in a coma. SECOND-class CITIZENS 175 Interestingly enough, she did in fact understand this idea of making up our own data types, so perhaps she was overestimating the degree of her shock. Before we get back to the technical explanation of how we create new data types, I’m sure one more question is burning in your mind: Why should we do this? What’s wrong with the primitive types like char and int? The answer is simple: We make up types so that we can match the language to the needs of the problem we’re trying to solve. For example, suppose we want to write a program to do inventory control for a small business like a grocery store. Such a program needs objects representing items in the store, which have prices, names, and so on. We’d need to define each of these types of objects so that it can display the behavior appropriate to the thing it represents. The availability of objects that have relevance to the problem being solved makes it much easier to write (and read) a program to handle inventory than if everything has to be made of ints and chars. I suspect that the advantages of making up one’s own data types may still not be apparent to you, so let me make an analogy with natural languages. Making up new data types in Java is in some ways quite similar to making up new words in English, for example. You might think that if everyone made up new words, the result would be chaos. Actually, this is correct, with the very important exception of technical jargon and other vocabularies that are shared by people who have more in common than simply being speakers of English. For example, physicians have their own "language" in the form of medical terminology. Of course, a cynical observer might conclude that the reason for such specialized vocabulary is to befuddle or impress the naive listener, and of course it can be used for that purpose. However, there is also a much more significant and valid reason: to make it possible for experts in a field to communicate with one another quickly and precisely. The same is true of creating our own data types; they enable us to write programs that are more understandable to those who are conversant with the problems being solved. It’s much easier to talk to a store owner about inventory objects than about ints and chars! Here’s the discussion that Susan and I had on this topic: Susan: Why should we have user-defined data types? Steve: So that you can match the language to the needs of the problem you’re trying to solve. For example, if you were writing a nurse’s station program in Java, you would want to have objects that represented nurses, doctors, patients, various sorts of equipment, and so on. Each of Problem Algorithms Java Interpreter Hardware 176 Chapter 6: TAKING INVENTORY these objects would display the behavior appropriate to the thing or person it was representing. Susan: Why do you need that? What if each individual who spoke English made up a unique version of English (well, it is user defined, right?), how could we communicate? This is garbage. Steve: We need user-defined types for the same reason that specialists need jargon in their technical fields. For example, why do you healthcare professionals need words like tachycardia? Why don’t you just say "a fast heartbeat" in simple English? Hey, that’s not a bad way to explain this: adding classes is like adding specialized vocabulary to English. I don ’ t r e m e m b e r e v e r s e e i n g t h a t explanation before; what do you think of it? Susan: Huh? Then you are saying that, by defining a class of objects, they can take on more realistic qualities than just abstract notions? That is, if I wanted to define nurse in a program, then I would do it with a class named nurse and then I can define in that program the activities and functions that the nurse objects would be doing. Is this how you keep everything straight, and not mix them up with other objects? Problem Algorithms Java Interpreter Hardware Steve: Yes, that’s one of the main benefits of object-oriented programming. You might be surprised how hard it can be to teach an old-line C programmer the importance of this point. Susan: So is this what object-oriented programming is? I have heard of it, but never knew what it meant. Could it also be described as userdefined programming? I guess there are advantages to teaching a novice; you don’t have to undo old ideas to make way for newer ones. So anything that is user defined is a class? That is, primitive variables are not classes? Steve: Every user-defined type is a class; data items of a class type are called objects. Variables of primitive types are not objects in the strict sense. Susan: OK, so if I want to make up something, then what I make up is called a class as opposed to the other type of stuff that isn’t made up by the user and is really Java; that is called primitive. That is intuitive, thank you. Then the class is made up of data items? And what about primitive variables; are they objects? I guess just the variables of the class are called objects, because I just made an effort to move and read your definition for object. So primitive variables are not objects, they are just variables. Am I am talking in circles again? SECOND-class CITIZENS 177 Steve: No, you’re not; you’re making perfect sense. The only point you have missed is that there are methods in the objects, as well as data items. We’ll get into that shortly. Susan: So Steve, tell me this: What have I been doing up to this point? How does this new stuff compare to the old stuff and which one is it that the programmer really uses? (Let’s see, do I want curtain 1 or 3; which one holds the prize?) I just want to get a little sense of direction here; I don’t think that is a whole lot to ask, do you? Steve: What you’ve been doing up to this point is using classes (String, RWVar) as well as primitive types like int and char. This new stuff shows how to create classes rather than just using them.3 Assuming that I’ve sold you on the advantages of making up our own data types, let’s see how we can actually do it. Each data type is represented by a class, whose definition is contained in a file with the extension .java. This class definition tells the compiler (and the class user) both what the class does and how the objects of that class actually perform the specified functions. Let’s take a look at a stepby-step description of how to create and use a class. 1. 2. 3. 4. 5. Write the class definition, which will be stored in a file with the extension .java; in our example, the first one of these will be stored in the file item1.java. This definition is the code that tells the compiler how to perform the operations that objects of the class support. Write the program that uses objects in the class to do some work; the first such program we’ll write will be itemtst1.java. C o m p i l e t h e class implementation definition to produce a c l a s s file (item1.class). This makes the class available for use by the user program. Compile the user program to produce a class file (itemtst1.class). Run the user program. Taking Stock Now let’s start on our first class definition, which is designed to help solve the problem of maintaining inventory in a small grocery store. We need to keep track of all the items that we carry, so we’re going to define a class called StockItem. The StockItem class, like other classes, ______________________________________________________________________________ 3. In case you were wondering, you can’t create new primitive types. Problem Algorithms Java Interpreter Hardware 178 Chapter 6: TAKING INVENTORY is composed of a number of methods and fields. To make this more concrete, think of something like Lego blocks, which you can put together to make parts that can in turn be used to build bigger structures. The smallest Legos are the primitive types, and the bigger, composite ones are class types. F o r t h e c o m p i l e r t o b e a b l e t o d e f i n e a n o b j e c t c o r r e c t l y , w e’ l l have to tell it the names and types of the fields that will be used to store the information about each StockItem; this enables the compiler to allocate memory for a StockItem. So how do we identify these fields? By considering what fields each StockItem object will need to keep track of its corresponding item in the stock of the store. After some thought, I’ve come up with the following list of fields: 1. 2. 3. 4. 5. Problem Algorithms Java Interpreter Hardware the name of the item (m_Name) the number in stock (m_InStock) the distributor that we purchase it from (m_Distributor) the price we charge (m_Price) the item number, or UPC (m_UPC) What I mean by an item is actually something like "chunky chicken soup, 16 oz.", rather than a specific physical object like a particular can of soup. In other words, every can of soup with the same item number is considered equivalent to every other can of soup with the same item number. Therefore, any given item can be described by the above data. For the item number, we’ll use the Universal Product Code (UPC), which is printed as a bar code on almost every product other than fresh produce; it’s a 10-digit number, which we’ll represent as a String for convenience. Susan took me to task about the notion of a StockItem object versus a specific physical object like a particular can of soup. It didn’t take too long to clear this one up. Susan : W h e n y o u s a y , " r a t h e r t h a n a s p e c i f i c p h y s i c a l o b j e c t " , h o w much more specific can you get than "chunky chicken soup, 16 oz."? Steve : E a c h c a n o f c h u n k y c h i c k e n s o u p i s a t l e a s t s l i g h t l y d i f f e r e n t from every other one; at the very least, they are in different places. Let’s recap what we know about a StockItem so far. We need a field in the StockItem class definition for each value in the above description: the name of the item (m_Name), its price (m_Price), the number of TAKING STOCK 179 items in stock (m_InStock), the name of the distributor (m_Distributor), and the UPC (m_UPC) of the item. Of course, merely storing this data isn’t very useful unless we can do something with it. Therefore, objects of the StockItem class also need to be able to perform several operations on their data; we’ll start by giving them the ability to display their contents. Figure 6.1 illustrates a very simple way that this class might be used. import WAJ.*; public class Itemtst1 { public static void main( String args[ ] ) { StockItem item1; StockItem item2; item1 = new StockItem(); item2 = new StockItem("Chunky Chicken",32,129, "Bob’s Distribution","123456789"); item1.Display(); item2.Display(); } } Figure 6.1: The initial sample program for the StockItem class (code\Itemtst1\Itemtst1.java) This program defines a S t o c k I t e m n a m e d i t e m 1 , a s s i g n s i t a v a l u e , and displays that value on the screen via a method called Display, and finally terminates normally. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Itemtst1". By the time we’re done with this chapter, you’ll understand exactly how all the operations in this program (and several other example programs) are performed by the StockItem class. First, we’ll need some more definitions to clarify the terms that we’ll need for the discussion. Problem Algorithms Java Interpreter Hardware 180 Chapter 6: TAKING INVENTORY More Definitions A constructor is a method that creates new variables of the class type. All constructors have the same name as the class for which they are constructors; therefore, the constructors for StockItem variables also have the name StockItem. A default constructor is a constructor that is used when no initial value is specified for an object. Because it is a constructor, it has the same name as the class; since it is used when no initial value is specified, it has no arguments. Thus, StockItem() is the default constructor for the StockItem class. . is the "object member access" operator. It separates an object name, on its left, from the member variable or method on its right. Stock in Trade Before we can implement the methods for our StockItem class, we have to define what a StockItem is in more detail than my previous Problem sketch.4 Let’s start with the simplified version of the definition for Algorithms that class in Figure 6.2, which includes the specification of the default Java constructor, the display method, and another constructor that is Interpreter specific to the StockItem class. Hardware Your first reaction is probably something like, "What a bunch of malarkey!" Let’s take it a little at a time, and you’ l l s e e t h a t t h i s seeming gibberish actually has its own rhyme and reason. First we have the line public class StockItem. T h i s t e l l s t h e c o m p i l e r t h a t w h a t follows is the definition of a class, which as we have already seen is a specification of the operations that can be performed on objects of a given user-defined type; in this case, the type is StockItem. So that the compiler knows where this description begins and ends, it is enclosed in {}, just like any other block of information that is to be treated as one item. But what about the public part? This is a new type of declaration called an access specifier, which tells the compiler the "security classification" of the item following it. This particular access specifier, public, means that any method, whether or not it is defined in this class, can use the item to which it is prefixed; in this case, it ______________________________________________________________________________ 4. By the way, in using a functional class such as StockItem to illustrate these concepts, I’m violating a venerable tradition in programming tutorials. Normally, example classes represent zoo animals, or shapes, or something equally useful in common programming situations. STOCK IN TRADE 181 means that the StockItem class is accessible to any program that wants to use it. Problem Algorithms Java Interpreter Hardware 182 Chapter 6: TAKING INVENTORY import WAJ.*; public class StockItem { private int m_InStock; private int m_Price; private String m_Name; private String m_Distributor; private String m_UPC; StockItem() { m_InStock = 0; m_Price = 0; m_Name = ""; m_Distributor = ""; m_UPC = ""; } StockItem(String Name, int InStock,int Price, String Distributor, String UPC) { m_InStock = InStock; m_Price = Price; m_Name = Name; m_Distributor = Distributor; m_UPC = UPC; } Problem Algorithms Java Interpreter void Display() { Hardware System.out.print("Name: "); System.out.println(m_Name); System.out.print("Number in stock: "); System.out.println(m_InStock); System.out.print("Price: "); System.out.println(m_Price); System.out.print("Distributor: "); System.out.println(m_Distributor); System.out.print("UPC: "); System.out.println(m_UPC); System.out.println(); } void SetName(String Name) { m_Name = Name; } } Figure 6.2: The initial class definition for the StockItem class (code\Itemtst1\StockItem.java) STOCK IN TRADE 183 Since the StockItem class is public, we can use StockItem objects anywhere in our programs.5 You may be wondering why everything isn’t public; why should we prevent ourselves (or users of our classes) from using everything in the classes? It’s not just hardheartedness; it’s actually a way of improving the reliability and flexibility of our software, as I’ll explain later. As you might imagine, this notion of access specifiers didn’t get past Susan without a battle. Here’s the blow-by-blow account. Susan: So is public a word that is used often or is it just something you made up for this example? Steve: It’s a keyword of the Java language, which has intrinsic meaning to the compiler. In this context, it means "any method, inside or outside this class, can access the following item." Because it is a keyword, you can’t have a variable named public, just as you can’t have one named if. Susan: These access specifiers: What are they, anyway? Are they always used in classes? Steve: Yes. Susan: Why aren’t they needed for primitive variables? Steve: Because you can’t define access specifiers for primitive types; their internals are predefined in the compiler and aren’t accessible to the programmer. Susan: What does internals mean? Do you mean stuff that is done by the compiler rather than stuff that can be done by the programmer? Steve: Yes, in the case of primitive data types. In the case of class types, internals means the details of implementation of the type rather than what it does for the user. Susan: You know, I understand what you are saying about internals; that is, I know what the words mean, but I just can’t picture what you are doing when you say implementation. I don’ t s e e w h a t i s a c t u a l l y happening at this point. Steve: The implementation of a class is the code that is responsible for actually doing the things that the objects of the class are supposed to do. ______________________________________________________________________________ 5. By the way, public is a keyword in Java; that is, it is defined in the language. This means that you cannot have a method or variable called public. Problem Algorithms Java Interpreter Hardware 184 Chapter 6: TAKING INVENTORY All of the code in the i t e m 1 . j a v a file is part of the implementation of StockItem. Susan: So on these fields, that m_ stuff, do you just do that to differentiate them from a primitive variable? If so, why would there be a confusion, since you have already told the compiler you are defining a class? Therefore, all that is in that class should already be understood to be in the class rather than the primitive language. I don’t like to look at that m_ stuff; it’s too cryptic. Steve: It’s true that the compiler can tell whether a variable is a field or a local variable. However, it can still be useful to give a different name to a field so that the programmer can tell which is which. Remember, a field looks like an argument or a local variable in a class implementation, although you don’t have to declare it as you would an argument or a local variable. Now we’re up to the line that says StockItem(). This is the declaration for a method called a constructor, which tells the compiler how to create an object; as we have already seen, there’s no way for it to know this otherwise. This particular constructor is the default constructor for the StockItem class. It’s called the "default" Problem constructor because it is used when no initial value is specified by the Algorithms user; the empty parentheses after the name of the method indicate the Java lack of arguments to the method. The name of the method is the clue Interpreter that it’s a constructor; the name of a constructor is always the same Hardware as the name of the class for which it’s a constructor, to make it easier for the compiler to identify constructors among all of the possible methods in a class. T h i s i d e a o f h a v i n g f i e l d s a n d m e t h o d s " i n s i d e " o b j e c t s w a s n’t intuitively obvious to Susan. Susan: Now where you talk about mixing a String and an int in the same method, can this not be done in the native language? Steve: It’s not in the same method but in the same object. We are creating a user-defined object that can be used more or less the way we use a primitive variable. Susan: OK, so you have a class StockItem. And it has a method called StockItem. But a StockItem is an object, so in this respect a method can be inside an object? Steve: Actually, every method is inside a class. In this particular case, a StockItem is an object that is composed of a number of methods and fields (variables). STOCK IN TRADE 185 Susan: OK, I think I am seeing the big picture now. But you know that this seems like such a departure from what I thought was going on before, where we used primitive types in methods rather than the other way around. Like when I wrote my little program, it would have ints in it but they would be in the method main. So this is a complete turnabout from the way I used to think about them. This is hard. Steve: Yes, that is a difficult transition to make. Interestingly enough, experience isn’t necessarily an advantage here; you haven’t had as much trouble with it as some professional programmers who have a lot more experience in writing methods as "stand-alone" things with no intrinsic ties to data structures. However, it is one of the essentials in objectoriented programming; in Java, all methods live "inside" classes, and do the bidding of objects of those classes, rather than being wild and free. Why do we need to write our own default constructor? Well, although we have already specified the fields used by the class, that isn’t necessarily enough information for the compiler to know how to initialize the objects of the class correctly. The compiler will make assumptions as to the correct values of the fields of an object. In the case of a StockItem, these assumptions actually will be correct; however, it’s generally considered better form for us to initialize the fields of our object ourselves anyway, just so the next programmer knows that we didn’t forget. Figure 6.3 shows what the code to our first default constructor looks like. StockItem() { m_InStock = 0; m_Price = 0; m_Name = ""; m_Distributor = ""; m_UPC = ""; } Figure 6.3: The default constructor for the StockItem class (from code\Itemtst1\Item1.java) Actually, this method isn’t all that different from a "regular" method. The main difference is that it is used to create a new object rather than to do something for, or with, an existing object. Susan had an objection to my cavalier use of the empty String "" in the default constructor. Problem Algorithms Java Interpreter Hardware 186 Chapter 6: TAKING INVENTORY Susan: Excuse me, but what kind of value is " "? Do you know how annoying it is to keep working with nothing? Steve: It’s not " ", but "". The former has a space between the quotes, and the latter does not; the former is a one-character String consisting of one space, whereas the latter is a zero-character String. Susan: OK, so the "" is an empty String, but could you please explain how this works? Steve: The "" means that we have a String with no data in it. Susan: OK, so this is only setting the Strings in the default constructor to a value that the compiler can understand so you don’t get an error message, although there is no real data. We’re trying to fool the compiler, right? Steve: Close, but not quite. Basically, we want to make sure that we know the state of the Strings in a default StockItem. Susan: Yes, I remember. So this is just the way to initialize a String when you don’t know what real value it will end up having? Problem Algorithms Java Interpreter Hardware Steve: Right. Scoped Out Now let’s get back to the fields of StockItem. One important characteristic of any variable is its scope: The scope of a variable is the part of the program in which it can be accessed. Until now, we’ve been dealing with l o c a l variables, which are defined and accessed only inside a particular method. Now we have to deal with another scope called class scope, which applies to all fields of a class.6 Fields have class scope, which means that each object of a given class has its own set of fields. In the case of StockItem, this set of fields consists of m_InStock, m_Price, m_Name, m_Distributor, and m_UPC. Methods of a class can access fields of that class without defining them, as though they were arguments or local variables of those methods. In addition to scope, each field has another attribute we have already encountered: an access specifier. The access specifier is used to control access by methods of other classes to that field or method. ______________________________________________________________________________ 6. Actually, I’m describing "normal" fields here. There is another kind that we won’t be covering. SCOPED OUT 187 If you look back at Figure 6.2, you’ll see that the keyword private precedes the definition of each of the fields in the StockItem class. The keyword private is an access specifier, like p u b l i c ; h o w e v e r , w h e r e a public access specifier allows any method to access the item following it, a private access specifier allows only methods of that class to access the item that follows it. Although scope rules and access specifiers are similar in some w a y s , i n t h a t t h e y a f f e c t w h e r e a v a r i a b l e c a n b e u s e d , t h e y a r e n’ t exactly the same. Scope defines where a variable exists and therefore where it is visible, whereas access specifiers control where a variable (or method) is accessible. That is, if you write a program that tries to read or modify a private variable from outside the class i m p l e m e n t a t i o n , t h e c o m p i l e r k n o w s w h a t y o u’re trying to do but won’t let you do it. On the other hand, if you try to access a local variable from a method where it isn’t defined, the compiler just tells you it has never heard of that variable, which indeed it hasn’t in that context. For example, let’s suppose that the local variable x defined in method abc has no existence in any other method; in that case, if you try to access a variable named x in another method, say def, where it hasn’t been defined, you’ll get an error message from the compiler telling you that there is no variable x in method def. However, if there is a private field called x defined in class ghi, and you try to access that field from a method of another class, the compiler will tell you that you’re trying to do something illegal. It knows which x you mean, but it won’t let you access it because you don’t have permission. Susan had some more questions about access specifiers, including this new one, private. Susan: It seems to me that the access specifiers act more like scope than anything. Are they about the same? Steve: Yes, the difference between public and private is somewhat analogous to the difference between local variables and class fields, but an access specifier controls what methods can access the field, whereas the difference between local variables and class fields affects where a variable is stored and when it is initialized. However, because fields are defined inside classes, they can’t be exactly like local variables; a field must always live inside a single occurrence of an object of its class. Susan: Are they necessary for every class? Steve: Pretty much. The default access specifier for a class is one commonly referred to as "package access", but for some reason there’s Problem Algorithms Java Interpreter Hardware 188 Chapter 6: TAKING INVENTORY no keyword to specify it. In any event, this default specifier grants access to all classes in the same package. Of course, this also means that if you don’t use explicit access specifiers, then everything declared in that class will be accessible to every other class in that package. As you’ll see, it’s usually better to make fields private, so the private specifier is almost always needed in a class. Susan: So does the scope of a variable refer to where it is, or where you can see it? Steve: Both. The scope of a variable is that part of the program in which it exists. It is visible, although perhaps not accessible, depending on its access specifier, in that part of the program. In the case of a class field, its scope is the same as the scope of the object of which it is a part: It comes into existence when that object is created, and becomes inaccessible when that object becomes inaccessible (because the last reference to it has ceased to exist). In the case of a local variable, its scope is from the point of its declaration to the end of the block in which it was declared. Susan: OK, that makes sense now. Are there any other kinds of access specifiers or are these the only ones? Problem Algorithms Java Interpreter Hardware Steve: Actually, there’s another one we’ll be using later. Susan: Wonderful; I can’t wait. Steve: Don’t worry, they won’t hurt (at least, not very much). Susan also wanted some more details about this new class scope. Susan: How about explaining the difference between a class scope and a public access specifier? Steve: Fields declared in a class always have class scope; that means that they live as long as the object that contains them. The access specifier determines who can access these variables, but does not affect their lifetimes. Of course, the constructor StockItem(), by virtue of being a method of the StockItem class, has access to all the fields of that class, so the p r i v a t e access specifier doesn’t apply to it. We’ll see later how that access specifier comes into play. Now that we know what kind of variables the StockItem() method deals with, namely, its fields, its behavior isn’t very mysterious: It SCOPED OUT 189 simply initializes the fields to 0 or "", whichever is appropriate to their types. But why does it need to do this? Because the fields of the StockItem class are the "raw material" the StockItem methods use to implement the behavior that we want a StockItem to display. Susan wasn’t buying all this malarkey about fields without a fight. Here’s how it went: S u s a n: What do m_InStock a n d m _ P r i c e a n d t h e o t h e r s a c t u a l l y d o ? I t seems we are missing a verb here. Steve: They don’t do anything by themselves. They are the fields used to store the count and price of the goods described by a StockItem object, respectively. In the default constructor, they are both set to 0, indicating a StockItem with no content. Any StockItem object that is created without a value is set to this empty state. That is, we don’t have to worry about the initial state of a StockItem that we create without a specified value, because the default constructor ensures that such a StockItem is set to a known value when it is created. And because the fields are private, the user of the class can’t access them directly, but has to use methods of StockItem to do anything with or to them. Susan: Okay, that makes more sense now. The fields of a class are set, used, and changed by the methods of that class in the course of implementing the behaviors that the StockItem class definition promises. So much for the "high-altitude" description of what a class does. Now let’s get back to the details that make it work, starting with the line where the StockItem() method is used in the test program in Figure 6.1, StockItem item1 = new StockItem();. Remember that the basic idea of constructing a class is to add data types to the language that aren’t available "out of the box". One of the functions that we have to help the compiler with is initialization; a main purpose for the StockItem() constructor is to initialize objects of the StockItem type that have no values explicitly assigned to them. That ’s why it’s called a default constructor. Susan didn’t immediately cotton to the idea of calling a default constructor when creating an object. Susan: Sure, defining an object is simple if you don’t lose your mind defining the classes first. Problem Algorithms Java Interpreter Hardware 190 Chapter 6: TAKING INVENTORY Steve: It is simple for the application programmer (the user of the class). We’re doing the hard part so he or she can just use the objects without having to worry about any of this stuff. Susan: Huh? Isn’t the "user of the class" always the same as the "writer of the class"? Steve: Not necessarily. You’ve been using Strings for some time now without having to be concerned about how they work. This is not unusual. Susan: Yeah, but if you are a programmer you will be a class writer, not just a user. Steve: That’s usually the case, but certainly not with respect to all the classes. You may very well write your own application-specific classes but use the ones from the standard packages for all of the low-level stuff like Strings, I/O, and so on. You should generally write a default constructor for every class you define, to guarantee the state of any "default constructed" object. If you define some other constructor but don’t define a default Problem constructor, programs using your class won’t be able to create objects Algorithms without specifying their values. Java So why did I say "generally", rather than "always"? Because there Interpreter are some times when you don’t want to allow an object to be created Hardware unless the "real" data for it is available. In that case, all you have to do is to define some other constructor but avoid defining a default constructor. This will cause a compiler error in any user code that tries to create an object of that class without specifying any initial values. Susan thought that the idea of having to define a default constructor for each class was a bit off the wall. Susan: When you say that "you should define one of these (default constructors) for every class you define...", my question is how? What are you talking about? I thought a default meant just that, it was a default; you don’t have to do anything with it; it is set to a preassigned value. Steve: It’s true that the class user doesn’t have to do anything with the default constructors. However, the class writer (that’s us, in this case) often has to define a default constructor so that when the class user defines an object without providing an initial value, the new object has a reasonable state for an "empty" object. SHOP TILL YOU DROP 191 Shop till You Drop Now let’s continue with our analysis of the StockItem class (Figure 6.2). Before we can do anything with an inventory record, we have to enter the inventory data. This means that we need another constructor that actually sets the values into the object. We also need some way to display the data for a StockItem on the screen, which means writing a Display method. The next method in that figure is the constructor that creates an object with actual data. Figure 6.4 shows the code for that constructor. StockItem(String Name, int InStock, int Price, String Distributor, String UPC) { m_InStock = InStock; m_Price = Price; m_Name = Name; m_Distributor = Distributor; m_UPC = UPC; } Figure 6.4: Another constructor for the StockItem class (from code\Itemtst1\Item1.java) Let’s start the analysis with the method’s header: StockItem(String Name, short InStock, short Price, String Distributor, String UPC). We can tell that this method is a constructor because its name, StockItem, is the same as the name of the class. If you’re a C programmer, you may be surprised to see two methods that have the same name, differing only in the types of their arguments. This is not legal in C, but it is in Java; it’s called method overloading, and as you’ll see, it’s a very handy facility that isn’t limited to constructors. The combination of the method name and argument types is called the signature of a method; two methods that have the same name but differ in the type of at least one argument are distinct methods.7 In the case of the default constructor, there are no arguments, so that constructor is used where no initial data are specified for the object. The statement item1 = StockItem(); fits that description, so the default constructor is used. However, in the next statement of the sample program (Figure 6.1), we have the expression: ______________________________________________________________________________ 7. Note that the names of the arguments are not part of the signature, so you can’t have two methods that differ only in the names of their arguments. Problem Algorithms Java Interpreter Hardware 192 Chapter 6: TAKING INVENTORY StockItem("Chunky Chicken",32,129,"Bob’s Distribution","123456789"); This is clearly a call to a constructor, because the name of the method is the name of a class, StockItem. Therefore, the compiler looks for a constructor that can handle the set of arguments in this call, and finds: StockItem(String Name, int InStock, int Price, String Distributor, String UPC) The first, fourth, and fifth arguments to the constructor are Strings, whereas the second and third are ints. Since these types all match those specified in the expression in the sample program, the compiler can translate that expression into a call to this constructor. As you can see, nothing about this constructor is terribly complex; it merely sets the fields of the object being constructed to the values of the corresponding arguments to the method. But why do we need more than one constructor? Susan had that same question, and I had some answers for her. Susan: How many constructors do you need to say the same thing? Problem Algorithms Java Interpreter Hardware Steve: They don’t say exactly the same thing. It’s true that every constructor in the StockItem class makes a StockItem; however, each argument list varies. The default constructor makes an empty StockItem and therefore doesn’t need any arguments, whereas the constructor StockItem(String Name, int InStock, int Price, String Distributor, String UPC) makes a StockItem with the values specified by the Name, InStock, Price, Distributor, and UPC arguments in the constructor call. Susan: Are you saying that in defining a class you can have two methods that have the same name, but they are different in only their arguments and that makes them unique? Steve: Exactly. This is the language feature called method overloading. Susan: So StockItem item1; is the default constructor in case you need something that can create uninitialized objects? Steve: Not quite; the default constructor for the StockItem class is StockItem(), which doesn’t need any arguments, because it constructs an empty StockItem. The line item1 = StockItem(); causes the default constructor to be called to create an empty StockItem. SHOP TILL YOU DROP 193 Susan: And the line item2 = StockItem("Chunky Chicken",32,129,"Bob’s Distribution","123456789"); is a constructor that finally gets around to telling us what we are trying to accomplish here? Steve: No, that line causes a StockItem with the specified contents to be created, by calling the constructor StockItem(String Name, int InStock, int Price, String Distributor, String UPC);, and assigns that StockItem to the variable named item2. Susan: So are you saying that for every new StockItem you have to have a new constructor for it? Steve: No, there’s one constructor for each way that we can construct a StockItem. One for situations where we don’t have any initial data (the default constructor), and one for those where we are supplying the data for a StockItem. There could be other ones too, but those are all we have right now. Once that expression has been translated, the compiler assigns the result of the expression to the StockItem object called item2, as requested in the whole statement: item2 = StockItem("Chunky Chicken",32,129,"Bob’s Distribution","123456789"); The last two statements is the test program (Figure 6.1) are item1.Display(); and item2.Display(), which display the values of item1 and item2 on the screen, respectively. Figure 6.5 shows the code for the Display method. void Display() { System.out.print("Name: "); System.out.println(m_Name); System.out.print("Number in stock: "); System.out.println(m_InStock); System.out.print("Price: "); System.out.println(m_Price); System.out.print("Distributor: "); System.out.println(m_Distributor); System.out.print("UPC: "); System.out.println(m_UPC); } Figure 6.5: Display method for the StockItem class (from code\Itemtst1\StockItem1.java) Problem Algorithms Java Interpreter Hardware 194 Chapter 6: TAKING INVENTORY This is also not very complicated; it just uses System.out.print and System.out.println to display each of the parts of the StockItem object on the screen, along with some identifying information that makes it easier to figure out what the values represent. Susan wanted to know how we could use System.out.print (and System.out.println) without defining a special version of these methods for this class. Susan: Hey, how come you don ’ t h a v e t o d e f i n e System.out.print and System.out.println as class methods? Does the compiler just use the native System.out.print? And that works OK? Steve: We’re using System.out.print and System.out.println only for types that already have it defined, which includes all of the primitive types, as well as the String class. To display a StockItem, we have to write our own method, which is what we’re doing here. Susan: Then please explain to me why System.out.print is being used in Figure 6.5, which is for the StockItem class. Problem Algorithms Java Interpreter Hardware Steve: It’s being used for Strings and ints, not objects of the StockItem class. The fact that the Strings and ints are inside the StockItem class is irrelevant in this context; they’re still Strings and ints, and therefore can be displayed by System.out.print. Susan: So the stuff you get out of the standard packages is only for the use of class types? Not primitive? Steve: The java.io library is designed to be able to handle both primitive types and class types; however, the latter use requires the class writer to do some extra work, which we won’t be tackling. That should clear up most of the potential problems with the meaning of this Display method. However, it does contain one construct that we’ve seen before without explanation: void. This is the return type of the Display method, as might be apparent from its position immediately before the class name StockItem. But its meaning might not be as obvious, so let me clear up the mystery: as a return type indicator, void means simply that this method doesn’t supply a return value at all. Y o u w o n’ t b e s u r p r i s e d t o l e a r n t h a t S u s a n h a d a f e w q u e s t i o n s about this idea of methods that return no value. Susan: How can a method not return a value? Then what is the point? SHOP TILL YOU DROP 195 Steve: The point of calling a method that returns no value is that it causes something to happen. The Display method is one example; it causes the value of a StockItem object to be displayed on the screen. Another example is a "storage method"; calling such a method can cause it to modify the value of some piece of data it is maintaining, so when you call the corresponding "retrieval method", you’ll get back the value the "storage method" put away. Such lasting effects of a method call (other than returning a value) are called side effects. Susan: But even a side effect is a change, so then it does do something after all, right? Steve: Sure, it does something; every method should do something, or you wouldn’t write (or call) it. However, some methods don’t return any value to the calling program, in which case we specify their return type as void. That takes care of the public entries in the c l a s s d e f i n i t i o n . N o w what about the private entries? As I mentioned before in the discussion of how a class is defined, the access specifier private means that only methods of the class can access the item after that specifier. It’s almost always a good idea to mark all the fields in a class as private, for two reasons. 1. 2. If we know that only methods of a class can change the values of member data, then we know where to look if the values of the data are incorrect. This can be extremely useful when debugging a program. Marking fields as private simplifies the task of changing or deleting those fields should that become necessary. You see, if the fields are public, then we have no idea what methods in what programs are relying on their values. That means that changing or deleting these fields can cause havoc anywhere in the system. Allowing access only by methods of the class where the fields are defined means that we can make changes freely as long as all of the methods are kept up to date. Both of these advantages of keeping fields private can be summed up in the term encapsulation, which means "hiding the details inside the class rather than exposing them to users of the class". This is one of the primary organizing principles that characterizes objectoriented programming. Susan wasn’t buying this encapsulation idea without a struggle. Problem Algorithms Java Interpreter Hardware 196 Chapter 6: TAKING INVENTORY Susan: I don’t get why you say calling something private is the same as encapsulation. Calling it private is the access specifier. Steve: Yes, private is the access specifier. However, when we make all the fields private, we are effectively encapsulating the details of how the class is implemented within the class. That is, the user cannot access those fields directly, but must go through our class methods to get at them, as though they were inside a guarded building. Susan: So then could you say that something is encapsulated every time you are calling something private? Steve: Not unless all the fields are private. Susan: What if you wanted to leave one field public and the rest private, then it would not be encapsulated? Steve: Right. If there’s any way for the user to get at the internals of the class, you can’t consider the object encapsulated. There’s only one more point about the fields in the StockItem class that needs clarification; surely the price of an object in the store Problem should be in dollars and cents, and yet we have only an int to Algorithms represent it. As you know by now, an int can hold only a whole Java number. What’s going on here? Interpreter Only that I’ve decided to store the price in cents rather than dollars Hardware and cents. That is, when someone types in a price, I’ll assume that it’s in cents, so "246" would mean 246 cents, or $2.46. This would, of course, not be acceptable in a real program, but for now it’s OK. This allows prices up to about $20,000,000.00 (as well as negative numbers for things like coupons or rebates), which should be acceptable for almost anything except a large corporation or government agency. Later we’ll see how to use another kind of variable that can hold numbers with fractional parts. For now, though, let’s stick with the int. There’s one more method that we need to cover in this class: SetName(String Name). Figure 6.6 shows the code for this method. void SetName(String Name) { m_Name = Name; } Figure 6.6: The SetName method of the StockItem class (from code\Itemtst1\Item1.java) SHOP TILL YOU DROP 197 This is about as simple as a method can get. All it does is change the name of an existing StockItem to whatever the Name argument specifies. However, as simple as it is, it is essential in the discussion of one of the major stumbling blocks in learning (and using) Java: the fact that we can never deal with an object directly but only through a reference to that object. References Required I alluded to this characteristic of Java briefly near the beginning of this chapter, and now it’s time to delve into it in excruciating detail. Let’s start with a few pictures. Let’s say that Figure 6.7 represents the situation after a StockItem c a l l e d i t e m 1 h a s b e e n s e t t o i t s i n i t i a l value (leaving out some of the fields that don’t matter here). Name ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿³ ³ m_Name ³³"Chunky Chicken" ³³±ÄÄÙ ³ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ³ ³ÚÄÄÄÄÄÄÄÄ¿ ³ m_InStock ³³ 32 ³ ³ ³ÀÄÄÄÄÄÄÄÄÙ ³ ³ÚÄÄÄÄÄÄÄÄ¿ ³ m_Price ³³ 129 ³ ³ ³ÀÄÄÄÄÄÄÄÄÙ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ item1 Figure 6.7: item1 and its object That seems fine. Figure 6.8 shows the situation after the statement item2 = item1; has been executed. Problem Algorithms Java Interpreter Hardware 198 Chapter 6: TAKING INVENTORY Name item1 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿³ ³ m_Name ³³"Chunky Chicken" ³³±ÄÄ´ ³ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ³ ³ ³ÚÄÄÄÄÄÄÄÄ¿ ³ ³ m_InStock ³³ 32 ³ ³ ³ ³ÀÄÄÄÄÄÄÄÄÙ ³ ³ ³ÚÄÄÄÄÄÄÄÄ¿ ³ ³ m_Price ³³ 129 ³ ³ ³ ³ÀÄÄÄÄÄÄÄÄÙ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ item2 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 6.8: item1, item2, and their object That’s certainly a simple way to make those two variables have the same value. But what would happen if we were to execute the statement item1.SetName("Sliced Beef");? Figure 6.9 shows the situation at that point. Problem Name Algorithms item1 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Java ³ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿³ ³ Interpreter m_Name ³³"Sliced Beef" ³³±ÄÄ´ Hardware ³ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ³ ³ ³ÚÄÄÄÄÄÄÄÄ¿ ³ ³ m_InStock ³³ 32 ³ ³ ³ ³ÀÄÄÄÄÄÄÄÄÙ ³ ³ ³ÚÄÄÄÄÄÄÄÄ¿ ³ ³ m_Price ³³ 129 ³ ³ ³ ³ÀÄÄÄÄÄÄÄÄÙ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ item2 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 6.9: item1, item2, and their object As you can see, since these two variables, item1 and item2, share an m_Name field, changing that field changes the value of both variables. To understand this characteristic of Java in detail, let’s look at a program that illustrates it (Figure 6.10). REFERENCES REQUIRED 199 import WAJ.*; public class ReferenceTest1 { public static void main( String args[ ] ) { StockItem item1; StockItem item2; int x; int y; x = 3; y = x; System.out.println("The value of x is: "); System.out.println(x); System.out.println(); System.out.println("The value of y is: "); System.out.println(y); System.out.println(); x = 2; System.out.println("The value of x has been changed to 2"); System.out.println(); System.out.println("The value of x is: "); System.out.println(x); System.out.println(); System.out.println("The value of y is: "); System.out.println(y); System.out.println(); System.out.println(); item1 = new StockItem("Chunky Chicken",32,129, "Bob’s Distribution","123456789"); System.out.println("The value of item1 is: "); item1.Display(); item2 = item1; System.out.println("The value of item2 is: "); item2.Display(); item1.SetName("Sliced Beef"); System.out.println("The name of item1 has been changed to Sliced Beef"); System.out.println(); System.out.println("The value of item1 is: "); item1.Display(); System.out.println("The value of item2 is: "); item2.Display(); } } Figure 6.10: A sample program for references (code\Reference1\ReferenceTest1.java) Problem Algorithms Java Interpreter Hardware 200 Chapter 6: TAKING INVENTORY If you run that program, you’ll discover that its output is as shown in Figure 6.11. The value of x is: 3 The value of y is: 3 The value of x has been changed to 2 The value of x is: 2 The value of y is: 3 Problem Algorithms Java Interpreter Hardware The value of item1 is: Name: Chunky Chicken Number in stock: 32 Price: 129 Distributor: Bob’s Distribution UPC: 123456789 The value of item2 is: Name: Chunky Chicken Number in stock: 32 Price: 129 Distributor: Bob’s Distribution UPC: 123456789 The name of item1 has been changed to Sliced Beef The value of item1 is: Name: Sliced Beef Number in stock: 32 Price: 129 Distributor: Bob’s Distribution UPC: 123456789 The value of item2 is: Name: Sliced Beef Number in stock: 32 Price: 129 Distributor: Bob’s Distribution UPC: 123456789 Figure 6.11: Output of sample program for references (code\Reference1\Reftest1.out) REFERENCES REQUIRED 201 Do you see anything odd about that output? I certainly do! Let’s start from the beginning. We assign the value 3 to an int variable called x. Then we set another int variable called y to the value that x has. When we display the values of these variables, both of them have the value 3, which is certainly reasonable. Then we change the value of x to 2. The last step of this part of the program displays the values of x and y again, and as you would expect, x is 2 and y is still 3. The second part of the program performs a seemingly similar operation with a couple of StockItems. First, we give item1 a value, the m_Name field of which is "Chunky Chicken". Then we display that StockItem, and the name is indeed "Chunky Chicken". Then we assign item2 the value of item1 by the statement item2 = item1;. Then we display the value of item2, which naturally enough shows its name as "Chunky Chicken". The next statement is the one that uses SetName to change the name in item1 to "Sliced Beef", which as we have seen is quite a simple operation. Then we display the value of item1, which of course now has the name "Sliced Beef". So far, so good. Now we come to the tricky part (to put it mildly): When we display the value of item2, it also has the name "Sliced Beef"! This is not the way the primitive variables behaved in the earlier part of this program, and is not obviously correct. Is there a bug in this program? Referred Pain No, that’s the way Java works. The problem is that item1 and item2, like all other variables of user-defined types, aren’t really objects; they are references to objects. That is, they are just middlemen that pass along all of our method calls and field references to the "real" objects, which are forever inaccessible to us in any other manner. Okay, so they’re not the real objects; why does that matter? Because the meaning of "item2 = item1;" is "make item2 refer to the same object as item1". In other words, rather than item1 and item2 each having their own object with its own fields, they share one object that has one set of fields. That’s why we get the output that we do from that test program; when we change "item1", we’re really changing the object it refers to, which is the same object that item2 refers to. When we display "item1" and "item2", what we’re really displaying is the same object through two different references. Of course, there are situations in which this behavior is undesirable, and where we must be able to separate such "Siamese Problem Algorithms Java Interpreter Hardware 202 Chapter 6: TAKING INVENTORY twins". We can do this by writing a method called clone that will actually create a new object with the same value as an existing object. Then we can assign that new object to our other reference and thus avoid the problem of sharing data when we don’t want to. Luckily, in this book we will be able to avoid this complication, as we will not encounter any circumstances in which sharing an object in this way is erroneous. However, there are plenty of circumstances in the real world where sharing data is a problem. Unfortunately, in those cases the clone method doesn’t really solve the problem fully, because we can still use = by accident when we should use clone and end up with improperly shared data. This is called aliasing, and it is a common source of serious, difficult-to-find errors in large programs where it’s almost impossible to keep track of all of the places that a given object is being referred to. I consider this a major flaw in the design of Java, because it is an inescapable fact of life that programmers make errors. Java was intended to be safer than many other existing languages; that is, it should be harder to make mistakes in Java than in other languages. The ease with which aliasing errors can be generated in Java is inconsistent with that goal. Problem At the moment, however, there’s nothing that can be done about it, Algorithms so let’s move on to the next part of our discussion of references and Java Interpreter objects: how we actually create an object to assign to a reference. Hardware Objection Sustained What is the purpose of the statement item1 = new StockItem("Chunky Chicken",32,129,"Bob’s Distribution","123456789");? It uses a Java facility called new to mark off a segment of memory that can be used to hold the fields that make up a StockItem object, as well as some supporting information that connects that object with its methods. As soon as that memory area has been assigned, the StockItem constructor is called to initialize the newly created StockItem object to the appropriate values; this object is then assigned to the StockItem reference item1. Once a particular area of memory has been assigned to an object, it won’t be used for any other purpose until we’re done with it. Of course, this leads to the question of how the Java interpreter knows when we’re done using the object; do we have to use another facility called something like old to tell Java it can reuse that memory? OBJECTION SUSTAINED 203 In many other languages, we would indeed need to do that, but in Java it’s handled automatically by a process known as garbage collection. Whenever most of the memory available for objects is in use, the g a r b a g e c o l l e c t o r combs through memory to find objects that no longer have any references. Any such objects are "garbage" and the memory they occupy can therefore be "collected" (made available for reuse). Garbage collection was included in the Java language because it is regrettably common for programmers in other languages (such as C and C++) to forget to tell the system when they are done with a piece of memory, so that it can be reused. This error is called a memory leak, and it’s a particularly insidious kind of error, because the program seems to work correctly unless it is run for a long enough time that it uses up all the available memory and stops working. N o w l e t ’ s g o b a c k a n d l o o k a t a s t a t e m e n t t h a t w e’ve skipped: Stockitem item1; . This statement doesn ’ t c r e a t e a StockItem object; as we’ve just seen, to do that we have to write a statement that uses new to allocate memory for that object.Instead, it creates a reference to a StockItem, which can be made to refer to an actual StockItem when we have one to refer to. In the meantime, it is an uninitialized variable, that is, one that hasn’t been assigned a value. If we try to do anything with such a variable other than assign it a value, our program won’t compile, just as it won’t compile if we have any other variables that haven’t been assigned values. We’re ready to continue with the next phase of our StockItem project. We’ve covered all of the methods and fields of the StockItem class, but you may be wondering how we can use this class to keep track of all of the items in the store. Surely we aren’t going to have a separately named StockItem variable for each one. Array of Hope This is another application for our old friend the array; specifically, we need an array of StockItems to store the data for all the StockItems in the store. In a real application we would need to be able to vary the number of elements in the array, unlike our previous use of arrays. After all, the number of items in a store can vary from time to time. However, in our example program we’ll ignore this complication and just use an array that can hold 100 StockItems. Even with this limitation, we will have to keep track of the number of items that are in use, so that we can store each new StockItem in its own array element and maintain a count of how many there are in the array at Problem Algorithms Java Interpreter Hardware 204 Chapter 6: TAKING INVENTORY the moment. Finally, we need something to read the data for each StockItem from the inventory file where it’s stored when we’re not running the program. Susan had some questions about these details of the test program. Susan: In the paragraph where you are talking about the number of items, I am a little confused. That is, do you mean the number of different products that the store carries or the quantity of an individual item available in the store at any given time? Steve: The number of different products, which is the same as the number of StockItems. Remember, each StockItem represents any number of objects of that exact description. Susan: So what you’re referring to is basically all the inventory in the store at any given period of time? Steve: Exactly. Problem Algorithms Java Interpreter Hardware Susan: What do you mean by "need something to read the data" and "where it’s stored when we’re not running the program." I don’t know what you are talking about; I don’t know where that place would be. Steve: Well, where is the data when we’re not running the program? The disk. Therefore, we have to be able to read the information for the StockItems from the disk when we start the program. Susan: Okay, that makes sense now. Steve: I’m glad to hear it. Figure 6.12 is a program that shows the code necessary to read the data for the StockItem array into memory when the program starts up. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Itemtst2". ARRAY OF HOPE 205 import WAJ.*; import java.io.*; public class Itemtst2 { static final int MaxItems = 100; public static void main( String args[ ] ) { FileInputStream ShopInfo; try { ShopInfo = new FileInputStream("shop2.in"); } catch (FileNotFoundException e) { System.out.println("Can’t open file \"shop2.in\""); return; } int i; int InventoryCount; boolean Result; StockItem[ ] AllItems; AllItems = new StockItem[MaxItems]; for (i = 0; i < MaxItems; i ++) { AllItems[i] = new StockItem(); Result = AllItems[i].Read(ShopInfo); if (Result == false) break; } InventoryCount = i; for (i = 0; i < InventoryCount; i ++) { AllItems[i].Display(); } } } Figure 6.12: Reading and displaying an array of StockItems (code\Itemtst2\Itemtst2.java) Problem Algorithms Java Interpreter Hardware 206 Chapter 6: TAKING INVENTORY This program has a number of new features that need examination. First, the statement static final int MaxItems = 100; is new. This defines a constant: a data item that is like a variable except that its value can’t change. Rather than get into the details of the static final keywords, which aren’t particularly relevant here, you can think of that as a phrase meaning "we’re defining a constant". We will use this MaxItems constant rather than the number "100" to specify the number of items in an array of StockItems; this is better programming practice because the next programmer to come along will be able to see that it’s not just a coincidence that the number of array elements is the same as the number of times we want to execute a loop. Also, if that number ever needs to change, we would only have to change it in one place (the definition of MaxItems) rather than in several places in the program. This makes maintenance easier and less error prone. The next new feature of this program is the addition of the java.io.* package to the list of packages we’re using, which is required if we want to read data from a file (or write data to a file, for that matter).8 The way we do this is to create a FileInputStream reference and assign it an object that is connected to a file. In this case, we create the FileInputStream reference called ShopInfo in the statement FileInputStream ShopInfo; , and we’re going to connect it to the file named shop2.in when Problem we create the FileInputStream object it refers to. Algorithms B e f o r e we can create that object, though, we have to stop and Java c o n s i d e r s o me possible problems when dealing with file input and Interpreter output. Hardware An Exceptional Opportunity Whenever we try to use a file, we may encounter an error. For example, if we try to open a file to read from it, it may not exist, or we may not have permission to open it. If we try to read from it, it may not have any more data in it, or there may be some kind of operating system error in the process (bad sectors, etc.). If we try to open a file to write to it, there may not be any more space on the disk, or we may not have permission to create it.9 ______________________________________________________________________________ 8. I haven’t defined the meaning of the * in a package name before, so this is a good time to do it: It means "I want to use everything in the package that it’s affixed to, not just part of it". In this case, the name java.io.* tells the compiler that we want to be able to use everything in the java.io package rather than just some of its contents. This is similar to the use of * when asking for a directory listing, where it serves as a "wild card" that will match anything. 9. Of course, other operations can have errors of this sort as well; for example, when we try to create an object, we may run out of memory. For now, though, let’s stick with I/O errors. AN EXCEPTIONAL OPPORTUNITY 207 Different programming languages provide different mechanisms for programmers to handle these problems and other similar ones. In C, for example, when you request an operation that can fail, you’re supposed to check the return value to see whether it is okay. However, this approach has a number of drawbacks. First, it’s very easy to write your program without such checks on the theory that you’ l l a d d t h e m l a t e r , " w h e n y o u h a v e t i m e " ; n e e d l e s s t o s a y , t h a t happy eventuality rarely comes to pass. Second, having lots of error checks in your program can quickly transform a simple program into a complicated, hard-to-follow mess. Third, if you’re using the return value to indicate success or failure, you can’t also use it to convey the result of the operation. I’m sure there are other drawbacks to this method, but those should be enough to convince a reasonable person that a better way would be desirable! In Java, there is a better way, called exception handling. An exception is an unexpected event that occurs during the execution of a program, or in other words, an error. The idea behind exception handling can be expressed by the old saying: "Hope for the best, but prepare for the worst." In other words, rather than trying to handle every possible error condition in the normal flow of a program, we assume that everything will work all right. If it doesn’t, we have a fallback plan to handle the error. Of course, this new approach to error handling gives us a new set of concepts and constructs to deal with. Let’s start by examining the next non-blank line of our test program (6.12), which consists of the keyword try. The controlled block of a try statement is called, reasonably enough, a try block. Better Read than Dead There’s only one statement in this try block, ShopInfo = new FileInputStream("shop2.in");. This statement creates a FileInputStream object, attaches it to the file shop2.in, and makes this object the current object for the ShopInfo reference. Actually, that description has left out something important. What it should say is that this statement attempts to create that object and attach it to the file shop2.in. What happens if that file doesn’t exist? In that case, the next part of the exception-handling mechanism comes into play, in the form of the catch statement that immediately follows the controlled block of the try. While a try statement doesn’t have any arguments, a catch statement has one, which specifies the Problem Algorithms Java Interpreter Hardware 208 Chapter 6: TAKING INVENTORY type of exception that it can catch.10 This catch statement handles an exception of the FileNotFoundException type. As is usual with method arguments, this argument also has a name. In this case, we won’t actually be using the exception object, but if we wanted to, we could refer to it by the name e, since that’s what we called it in the argument list of the catch. Instead of using the contents of the exception object, our catch block just displays an error message telling us about the error and then executes a return statement. Since we are already in the main method, this return will cause the program to stop running. Susan had some questions about exception handling. S u s a n : A b o u t c a t c h. If you get an error, then the program just ends? Otherwise, this is just ignored? Steve: If you get an exception and there is no catch block that catches that type of exception in the current method, then the error propagates to the calling method, if there is one. If the current method is main, then the program ends. However, if there is an appropriate catch block in the current method, then it catches the exception and can decide what to do about it. Usually this is to display an error message and quit, but in some cases it can fix the problem that caused the exception so that processing can continue. Problem Algorithms Java Interpreter W e ’ l l g e t b a c k t o e x c e p t i o n s v e r y s h o r t l y , b u t f o r n o w l e t ’ s continue with our program (Figure 6.12) under the assumption that Hardware the file shop2.in exists and our attempt to open it is successful. In that case, we’ll continue by creating three variables named i, InventoryCount, and Result, which we will use in keeping track of the number of elements read and whether we have reached the end of the file.11 Next, we create an array called AllItems to hold the references to all the StockItem objects that will hold the real information about the items in the store, and fill those StockItems in with information read from the file. ______________________________________________________________________________ 10. Actually, any catch statement can catch more than one type of exception, as is explained in Chapter 8, but that’s a complication we can overlook for the moment. 11. This is an example of the fact that we can create variables wherever we want to, not just at the beginning of a method as we have been doing so far. There are two schools of thought on where to create variables if you have a choice: one says that it’s easier to find the variables if they’re always at the top of the method, and the other says that it’s easier to find them if they are created shortly before they are used. Of course, in some cases you might not have enough information to know that you will need a particular variable until after you have executed some code in the method; obviously in such cases you won’t be able to create the variables at the beginning of the method. BETTER READ THAN DEAD 209 To make that last step more intelligible, I think some diagrams showing how an array works in more detail might be in order. Figure 6.13 is the first installment, showing what the AllItems array might look like when it is first created by the statement StockItem[ ] AllItems;. Address Name 12300000 StockItem[ ] AllItems ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 6.13: An uninitialized array of StockItem references The box that will hold the value of the AllItems variable is blank to indicate that the AllItems array doesn’t refer to anything at the moment. You see, an array in Java is actually a reference, just as a userdefined variable is. That is, just as we must use a reference to access a user-defined variable, we must use a reference to access a set of elements. Until we assign an actual set of elements to an array, it has no value and therefore cannot be used. Therefore, the next step is to use the statement AllItems = new StockItem[MaxItems]; to assign the AllItems array a set of 100 elements, each of which is a reference to a StockItem; the elements of this array will refer to the StockItems we will read in from the file. Figure 6.14 illustrates what the AllItems array might look like at that point, omitting the 95 elements between element 3 and element 98, inclusive; after all, I’m not being paid by the page! Address Name 12300000 StockItem[ ] AllItems 13130000 AllItems[0] 13130004 AllItems[1] 13130008 AllItems[2] ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ 13130000 ÃÄ¿ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÚÄÄÄÄÙ ² ÚÄÄÄÄÄÄÄÄÄÄÄÁÄÄ¿ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ . . . 13130188 AllItems[98] 1313018C AllItems[99] ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 6.14: An array of uninitialized StockItem references Problem Algorithms Java Interpreter Hardware 210 Chapter 6: TAKING INVENTORY As was the case with the array itself, the blank boxes indicate that we haven’t initialized the array elements yet, so they don’t refer to anything. Now we’re up to the beginning of the for loop in Figure 6.12. As usual in Java, the first index that we will use is 0. We want to continue the loop until we have read either 100 items or all of the data in the file, whichever comes first. We handle the former condition in the for loop header by stopping when i is not less than 100; the latter condition will be handled in the loop, as we’ll see in a moment. The next statement is AllItems[i] = new StockItem();. We have to do this to create the object for the ith reference in the AllItems array before we can do anything with that object. Remember that merely creating the array of references doesn’t mean that those references refer to anything, and in fact they don’t refer to anything until we make them do so. But what would happen if we forgot to assign an object to each reference in the AllItems array before we use it? Nothing Ventured, Nothing Gained Problem Algorithms Java Interpreter Hardware If we did forget to assign an object to each reference before trying to use it, we would get an exception. If we didn’t handle that exception ourselves, then we would get the default error message, which looks like this: ERROR: java.lang.NullPointerException telling us that we have tried to use a null reference.12 However, we won’t get that error here because we’ve remembered to initialize our references before using them. After the first execution of this loop, therefore, the array might look something like Figure 6.15 (omitting most of the fields in the StockItem to keep the diagram relatively simple). Of course, the reason that the name and number of items in stock are "" and 0, respectively, is that the default constructor StockItem(), ______________________________________________________________________________ 12. I n c a s e y o u ’ r e w o n d e r i n g w h y t h e e r r o r m e s s a g e s a y s " N u l l P o i n t e r " r a t h e r t h a n "NullReference", I’m not sure, but I can guess. A Java reference is very similar to a C pointer, and the Java designers probably had named this exception before they changed the term "pointer" to "reference". Of course, this doesn’t answer the question of why they would change the term in the first place, but I think that was a marketing decision made so that Sun could claim that "Java has no pointers". NOTHING VENTURED, NOTHING GAINED 211 which we ’ v e u s e d t o c r e a t e t h e S t o c k I t e m , s e t s t h o s e f i e l d s t o t h o s e values. Address Name 12300000 StockItem[ ] AllItems 13130000 AllItems[0] 13130004 AllItems[1] 13130008 AllItems[2] 13130188 AllItems[98] 1313018C AllItems[99] 14120010 m_Name 14120020 m_InStock ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ 13130000 ÃÄ¿ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÚÄÄÄÄÙ ² ÚÄÄÄÄÄÄÄÄÄÄÄÁÄÄ¿ ³ 14120010 ÃÄÄÄÄ¿ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ . . . ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ "" ñÄÄÄÙ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 0 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 6.15: An array of StockItem references, with the first reference initialized Now we’re up to the statement Result = AllItems[i].Read(ShopInfo);. This statement calls the method Read (shown in Figure 6.17), which I’ve added to the StockItem class. This method reads data from a FileInputStream, in this case ShopInfo, into its StockItem object, in this case AllItems[i]. After that statement has been executed, the AllItems array might look like Figure 6.16. As you can see, the first StockItem in the AllItems array has now been set up properly. Of course, the same process has to be applied to the rest of the StockItems in the AllItems array before it can be used in the rest of the program. Problem Algorithms Java Interpreter Hardware 212 Chapter 6: TAKING INVENTORY Address Name 12300000 StockItem[ ] AllItems 13130000 AllItems[0] 13130004 AllItems[1] 13130008 AllItems[2] 13130188 AllItems[98] 1313018C AllItems[99] 14120010 m_Name ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ 13130000 ÃÄ¿ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÚÄÄÄÄÙ ² ÚÄÄÄÄÄÄÄÄÄÄÄÁÄÄ¿ ³ 14120010 ÃÄÄÄÄ¿ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ . . . ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³"Chunky Beef" ñÄÄÄÙ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 25 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ 14120020 m_InStock Problem Algorithms Java Figure 6.16: An array of StockItem references, with the first StockItem set up Interpreter for use Hardware This whole process was anything but obvious to Susan. Susan: How does just adding the package java.io.* enable you to read data in from a file? Steve: The package java.io.* contains the FileInputStream class. Susan: Where did the FileInputStream class come from? Steve: It’s one of the standard classes that comes with the compiler. Susan: Your only reference to this is just "The way we do this is to create a FileInputStream object that is ‘attached’ to a file when the object is constructed". If this is just something that you wrote to aid this program that we don’t have to worry about at this time, then please mention this. Steve: I didn’t write it, but we don’t have to worry about it. I’ll explain it to the minimum extent necessary. NOTHING VENTURED, NOTHING GAINED 213 Susan: Where did java.io come from and how did it get there? Who defined it and when was it written? And how did you know it was there? Steve: It is defined in the Java language. Since we’re using Visual J++, a Microsoft compiler/interpreter, I imagine it was written by programmers at Microsoft. As to how I knew it was there, I looked it up in a book. If you want to do any serious programming in Java, you’ll need a reference book. I recommend Java in a Nutshell, by David Flanagan, published by O’Reilly & Associates. Although it won’t teach you Java, it does have a list of every class and method that was defined in the Java language at the time the book was published. Figure 6.17 is the implementation of the new Read method. boolean Read(FileInputStream s) { DataInputStream dis = new DataInputStream(s); String tempInStock; String tempPrice; try { m_Name = dis.readLine(); tempInStock = dis.readLine(); tempPrice = dis.readLine(); m_Distributor = dis.readLine(); m_UPC = dis.readLine(); } catch (IOException e) { System.out.println(e); return false; } if (m_UPC == null) return false; m_InStock = Integer.parseInt(tempInStock); m_Price = Integer.parseInt(tempPrice); return true; } Figure 6.17: The Read method for the StockItem class (from code\Itemtst2\StockItem.java) Problem Algorithms Java Interpreter Hardware 214 Chapter 6: TAKING INVENTORY This is the first time we’ve used the boolean type. Expressions and variables of that type are limited to the two values true and false. We’ve been using the terms true and false to refer to the result of a logical expression such as if (x < y); similarly, a boolean variable or method return value can be either true or false. In this case, Read needs to inform its caller of whether it has read data from the file (true) or has failed in that attempt (false); therefore, a boolean return value is a good choice. Susan wasn’t initially convinced of the advantages of using boolean. Susan: How does boolean work? How does it differ from int, where you could just compare two numbers and if they don’t match you get a false and if they do match you get a true? Steve: I don’t understand your question. Susan: Yes, you do. Why is boolean any better than if and else? Problem Algorithms Java Interpreter Hardware Steve: That’s like asking, "Why is a bank better than a supermarket?" They aren’t in competition. The if and else keywords allow us to determine what will be executed based on some condition; a boolean variable is used to remember whether some condition is true or false. Susan: No, what I mean is why should we use boolean rather than int for a return value? We could make 0 mean false and 1 mean true. Steve : T h a t i s i n f a c t t h e w a y t h a t C d o e s i t , b u t C + + h a s a d d e d a n equivalent bool data type. The reason for this addition is to allow the compiler to tell you when you’ve made a mistake by trying to return a value other than true or false. If you used an int return type, you could return 6 instead of either 0 or 1, and the compiler wouldn’t be able to tell that it was an error, whereas with boolean it can. Susan: Okay, I think I have that now. Steve: Good. Now let’s continue with the statement DataInputStream dis = new DataInputStream(s);. The purpose of this statement is to allow us to read data from the FileInputStream called s, which is the argument that our caller passed us for that purpose. As you may recall, that FileInputStream was connected to a file called shop2.in, which means that when we read data from s, we are really reading it from shop2.in. NOTHING VENTURED, NOTHING GAINED 215 However, in order to read data from a FileInputStream, we have to use it to create a DataInputStream, as we do in the current statement. The discerning reader will notice I haven’t explained exactly what a DataInputStream is and how it differs from a FileInputStream. Unfortunately, I can’t do that, because it involves yet another facet of Java that we won’t be able to cover in this book. Thus, you’ll just have to take my word for it that we have to create a DataInputStream from a FileInputStream before we can read data from the file connected to the FileInputStream. Even aside from this detail, this Read method is pretty complicated. Why is this? I Take Exception to That Statement The main reason is that reading from a file can cause a type of exception called a checked exception, which means one that the compiler insists that you deal with when writing your methods. There are two ways you can satisfy this requirement. First, by stating in the header of your method that it may throw this type of exception, and second, by handling it via the try/catch mechanism. The purpose of this requirement is to make sure that the user of a method knows exactly what checked exceptions can result from calling that method; if it doesn’t declare any, it won’t throw any. However, there are (at least) two things I haven’t explained: Why are some exceptions checked, and what other kinds are there? A checked exception is one that the design of Java assumes can only happen at certain reasonably predictable places in your program; therefore, you are required to let the callers of your methods know about the possibility of its being thrown if you don’t handle it yourself. An example of a condition that causes checked exceptions is trying to open a nonexistent file for reading. On the other hand, an unchecked exception is one that the design of Java assumes can happen virtually anywhere in your program, so it wouldn’t make sense to force you to specify that your methods can throw such an exception. Examples of conditions that cause unchecked exceptions are attempting to use a null reference, trying to access an element of an array that is past the end of the array, and the like. We can handle unchecked exceptions in our methods if we wish, but we don’t have to; whether or not we handle them, we don’t have Problem Algorithms Java Interpreter Hardware 216 Chapter 6: TAKING INVENTORY to declare that we will throw them, because any method is permitted to throw them.13 As you might expect, Susan wanted to explore the logical consequences of exception handling in general, and the notion of two classes of exceptions in particular. Susan: Why do you need exception handling in Read? Steve: Because the readLine method can throw checked exceptions. In Java, the compiler requires that any methods that call other methods that can throw checked exceptions either handle those exceptions or declare that they can also throw those exceptions. Susan: Why bother with unchecked exceptions then? Steve: You only have to deal with them if you want to give better error messages than the default ones that happen if you ignore them. Susan: Why are these custom error messages better than default error messages? Problem Algorithms Java Interpreter Hardware S t e v e: Because when you write your own error messages, you know more about the situation where the error occurred than the person who wrote the package containing the code where the error really occurred. After all, the package designer doesn’t know how you’re using the code in the package. Susan: What is dis? I am serious. Steve: It’s short for DataInputStream, because that’s the type of variable that it is. Susan: Is reading variables in from dis what the whole try block does? Steve: Yes, that’s what this try block does. Of course, other try blocks do other things. Susan: So then there is no general statement that you can make about all try blocks? S t e v e: The general statement might be "if you’re worried about the possibility of an exception in a section of code, you can use a try block to ______________________________________________________________________________ 13. If you find the distinction between these categories far from obvious, you’re not alone. I consider the division into checked and unchecked exceptions fairly arbitrary; you just have to look up which is which, or let the compiler tell you when you’ve called a method that might throw a checked exception. I TAKE EXCEPTION TO THAT STATEMENT 217 handle it." While just about every section of code could throw an exception, you can ignore that possibility in many cases. However, some sections of code can throw a checked exception; in that case, you have to decide whether you want to handle that exception or pass it back to the calling method. If you want to handle the exception yourself, you have to use a try block. However, that isn’t the only time that you would use a try block: in general, you would also use it for code that you’ r e c o n c e r n e d m i g h t throw an unchecked exception, where you want to handle such an exception yourself rather than relying on the system’s default exception handling. The easiest thing to do, at least when you’re starting out, is to ignore exceptions when you’re writing your code. If you don’t handle a checked exception, the compiler will give you an error telling you that you have to do something about it, and you can deal with it then. In the case of the Read method, IOException is a checked exception, which means we have to do something about it in our method. In this case, we want to handle it ourselves, so we use a try block to surround the statements that try to read from the file, because they might throw an IOException. If there is a problem, then the catch block displays an error message and returns false (meaning there is no more data to be read). Note that in this case we’re using the default error message that comes with the exception, in the statement System.out.println(e);. This is a handy feature of the exceptions that come with Java: they all have error messages in them, and will display those error messages via println. That means that we don’t have to write our own error messages for these exceptions as long as we’re content with the default ones. If we preferred to have our callers handle this exception rather than handling it ourselves, we could change the header of our Read method to indicate that it can throw an IOException; that would make the header boolean Read(FileInputStream s) throws IOException. In that case, we could write our method without worrying about the possibility that such an exception could occur; of course, the users of our method might not be happy at our foisting the exception handling off on them! Assuming that we don’t have any exceptions while reading data, each statement inside the try block will read one line from the file. The statements that read data for String fields (i.e., m_Name, m_Distributor, and m_UPC) can simply store data read from the file directly into the corresponding variable. However, this won’t work for our int fields; instead, they have to be converted from Strings to numeric values, which we’ll do at the end of the routine. Problem Algorithms Java Interpreter Hardware 218 Chapter 6: TAKING INVENTORY As you probably have guessed, Susan had some questions about this whole concept. Susan: How does Read make Shopinfo go get data? Steve: Well, the argument s is a reference to the FileInputStream object provided by the caller; in this case, the FileInputStream object to which s refers is Shopinfo. That FileInputStream is connected to the file shop2.in. Susan: How come this Read method is a boolean type? I would think it would return data being read from a file. Steve: You would think so, wouldn’t you? I love it when you’re logical. However, it actually reads data from a file into the object for which it was called, then tells its caller whether that read was successful. Therefore, its appropriate return type is boolean. Susan: So the DataInputStream object is a transfer mechanism? That is, DataInputStream s; would read data from a file named s? Problem Algorithms Java Interpreter Hardware Steve: Yes, it’s a transfer mechanism. However, DataInputStream s; would create a DataInputStream called s that was not connected to any file. In this case, we’re creating the DataInputStream called dis and specifying the FileInputStream that it actually uses to go get the data. Susan: OK. A DataInputStream just reads data from a file. It doesn’t care which file until you specify it, either when you create it or later? Steve: Right. Susan: What does this mean without System.in? Is it just the same thing, only you can’t call it System.in because System.in is for primitive use and this is a class? Steve: The s takes the place of System.in, because we want to read from s, not System.in. Susan: Am I right that null is used if you can’t read a whole item? Steve: Close, but not quite. If we try to read something from a file when there isn’t anything left, we will get a null rather than whatever we were expecting. In that case. we return false. There is another point that we haven’t examined yet, though: how this routine determines that it’s finished reading from the input file. With keyboard input, we process each line separately when it’s typed I TAKE EXCEPTION TO THAT STATEMENT 219 in, but that won’t do the job with a file, where we want to read all the items in until we get to the end of the file. We handle this detail by checking whether the value for m_UPC is null, that is, a null reference. This is what the readLine method of DataInputStream returns when there is nothing left in the file.14 When this occurs, we simply return false to inform our caller that we were unsuccessful in reading the StockItem that they wanted us to get. Once we have read all of the data for the current StockItem, we have to convert the m_InStock and m_Price values from their String form as read from the file into int values so we can calculate with them. This is the task of the next two lines, m_InStock = Integer.parseInt(tempInStock); and the corresponding line for m_Price. The Integer.parseInt method is a handy feature that is provided by the standard package Java.lang to allow us to determine the numeric value of a String that is supposed to represent a number.15 Reading between the Lines Now that we’re done with the Read method, let’s get back to our main program (Figure 6.12). At this point, we have to check whether our most recent attempt to read a StockItem from shop2.in was successful. We use the return value from Read to do this. If Read was able to read all the data for a StockItem, the answer will be true; otherwise, it will be false. How do we use this information? We use it to decide whether to execute a break statement. This is a loop control device that interrupts processing of a loop. The flow of control passes to the next statement after the end of the controlled block of the for statement.16 The loop will terminate in one of two ways. Either 100 records have been read, in which case i will be 100; or the end of the file is reached, in which case i is the number of records that have been read successfully. ______________________________________________________________________________ 14. In case you were wondering why we don’t get an exception when we try to read data that isn’t in the file, so am I. 15. By the way, if the String in fact does not represent a valid numeric value, we’ll get a BadNumberFormat exception. The reason we don’t have to catch or declare this exception in our method is that it is an unchecked exception. We aren’t handling this exception here or in the main program; therefore, if it occurs, our program will terminate with an "unhandled exception" error. 16. The break statement can also terminate execution of a while loop, as well as some other types of control mechanisms that we won’t get to. Problem Algorithms Java Interpreter Hardware 220 Chapter 6: TAKING INVENTORY Susan had some questions about the implementation of this program. Susan: Where did break come from? Steve: It’s another keyword like for; it means to terminate the loop that is in progress. Susan: I do not understand what is actually happening with the program at this time. When is break implemented? Is it just to end the reading of the entire file? Steve: We have to stop reading data when there is no more data in the file. The break statement allows us to terminate the loop when that occurs. Susan: What do you mean that the loop will terminate either by 100 records being read or when the end of the file is reached? Isn’t that the same thing? Problem Algorithms Java Interpreter Hardware Steve: It’s the same thing only if there are exactly 100 records in the file. Susan: So you mean when there are no more records to be read? So that the loop won’t continue on till the end with nothing to do? Steve: Exactly. Susan: So does i just represent the number of records in the file? Steve: Actually, it’s the number of records that we’ve read. Susan: Well, the program sounded like there were indeed 100 records in the file. However, I see that in practice that might change, and why you would therefore need to have a break. Steve: You obviously understand this. Whether there are 100 records in the file or fewer than that number, obviously the number of items in the array is equal to the current value of i. Or is it? DON’T FENCE ME IN 221 Don’t Fence Me In Let’s examine this a bit more closely. It’s actually quite easy to make a mistake when writing code that counts items, believing that you have one more or one less than the actual number of items. In fact, this error is common enough to have a couple of widely known nicknames: an off-by-one error, also known as a fence post error. The former name should be fairly evident, but the latter name may require some explanation. First, let’s try it as a "word problem". If you have to put up a fence 100 feet long, and each section of the fence is 10 feet long, how many sections of fence do you need? Obviously, the answer is 10. Now how many fence posts do you need? 11. The confusion caused by counting fence posts when you should be counting segments of the fence (and vice versa) is the cause of a fence post error. That’s fine as a general rule, but what about this specific example? Well, let’s start out by supposing that we have an empty file, so the sequence of events in the upper loop is as follows: 1. 2. 3. 4. 5. Set i to 0. Is i less than 100? If not, exit. If so, continue. Use the Read method to try to read a record into the ith element of the AllItems array. Check the return value from Read to find out whether we’ve tried to read past the end of the file. If so, execute the break statement to exit the loop. The answer to the question in step 4 is that in fact nothing was read, so we do execute the break and leave the loop. The value of i is clearly 0 here, because we never went back to the top of the loop; since we haven’t read any records, setting InventoryCount to i works in this case. Now let’s try the same thing, but this time assuming that there is one record in the file. Here’s the sequence of events: 1. 2. 3. 4. 5. Set i to 0. Is i less than 100? If not, exit. If so, continue. Use the Read method to try to read a record into the ith element of the AllItems array. Check the return value from Read to find out whether we’ve tried to read past the end of the file. If so, execute the break statement to exit the loop. In this case, we haven’t run off the end of the file, so we go back to the top of the loop, and continue as follows: Problem Algorithms Java Interpreter Hardware 222 Chapter 6: TAKING INVENTORY 6. 7. 8. Increment i to 1. Is i less than 100? If not, exit. If so, continue. Use the Read method to try to read a record into the AllItems array. 9. Check the return value from Read to find out whether we’ve tried to read past the end of the file. 10. If so, execute the break statement to exit the loop. The second time through, we do execute the break. Since i is 1, and the number of elements read was also 1, it’s correct to set the count of elements to i. It should be pretty clear that this same logic applies to all the possible numbers of elements up to 99. But what if we have 100 elements in the file? Relax, I’m not going to go through these steps 100 times, but I think we should start out from the situation that would exist after reading 99 elements, and see if we get the right answer in this case too. After the 99th element has been read, i will be 99; we know this from our previous analysis that indicates that whenever we start executing the statements in the controlled block of the loop, i is always equal to the number of elements previously read. So here’s the 100th iteration of the loop: Problem Algorithms 1. Java Interpreter 2. Hardware 3. 4. 5. Use the Read method to try to read a record into the AllItems array. Check the return value from Read to find out whether we’ve tried to read past the end of the file. If so, execute the break statement to exit the loop. Otherwise, increment i to 100. Is i less than 100? If not, exit. If so, continue. Since i is not less than 100, we exit. At this point, we’ve read 100 records and i is 100, so these two numbers are still the same. Therefore, we can conclude that setting InventoryCount equal to i when the loop is finished is correct; we have no fence post error here. Susan wasn’t sure why I was hammering this fence post thing into the ground. Susan: Why are you always saying that "it’s correct to set the count of elements to i"? Steve: Because I’m showing how to tell whether or not we have a fence post error. That requires a lot of analysis. DON’T FENCE ME IN 223 A c t u a l l y , t h i s w h o l e p r o c e d u r e w e’ v e j u s t b e e n t h r o u g h r e m i n d s me of the professor who claimed that some point he was making was obvious. This was questioned by a student, so the professor spent ten minutes absorbed in calculation and finally emerged triumphantly with the news that it was indeed obvious. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Itemtst2". You’ll see that it indeed prints out each StockItem object read from the file. Can I Help You? Of course, this isn’t all we want to do with the items in the store’s inventory. Since we have a working means of reading and displaying the items, let’s see what else we might want to do with them. Here are a few possible transactions at the grocery store: 1. 2. 3. George comes in and buys 3 bags of marshmallows. We have to adjust the inventory for the sale. Sam wants to know the price of a can of string beans. Judy comes in looking for chunky chicken soup; there’s none on the shelf where it should be, so we have to check the inventory to see if we’re supposed to have any. All of these scenarios require the ability to find a StockItem object given some information about it. Let’s start with the first example, which we might state as a programming task in the following manner: "Given the UPC from the bag of marshmallows, and the number of bags purchased, adjust the inventory by subtracting the number purchased from the previous quantity on hand." Here’s a further breakdown of the steps needed to do this: 1. 2. 3. Take the UPC from the item. Look through the inventory list to find an item with that UPC. When you find it, subtract the number purchased from its m_InStock. Figure 6.18 is a program that looks as though it should solve this problem. Does it? Problem Algorithms Java Interpreter Hardware 224 Chapter 6: TAKING INVENTORY import WAJ.*; import java.io.*; public class Itemtst3 { static final int MaxItems = 100; public static void main( String args[ ] ) { FileInputStream ShopInfo; StockItem[ ] AllItems; AllItems = new StockItem[MaxItems]; Problem Algorithms Java Interpreter Hardware int i; int InventoryCount; int OldInventory; int NewInventory; String PurchaseUPC; String ItemName; int PurchaseCount; boolean Found; boolean Result; try { ShopInfo = new FileInputStream("shop2.in"); } catch (IOException e) { System.out.println("Can’t open file \"shop2.in\""); return; } for (i = 0; i < MaxItems; i ++) { AllItems[i] = new StockItem(); Result = AllItems[i].Read(ShopInfo); if (Result == false) break; } Figure 6.18: Trying to update inventory (code\Itemtst3\Itemtst3.java) CAN I HELP YOU? 225 InventoryCount = i; System.out.print("What is the UPC of the item? "); PurchaseUPC = RWVar.readString(System.in); Found = false; for (i = 0; i < InventoryCount; i ++) { if (PurchaseUPC == AllItems[i].m_UPC) { Found = true; break; } } if (Found == true) { OldInventory = AllItems[i].m_InStock; ItemName = AllItems[i].m_Name; System.out.print("There are currently " + OldInventory); System.out.println(" units of " + ItemName + " in stock."); System.out.print("How many items were sold? "); PurchaseCount = RWVar.readInt(System.in); AllItems[i].m_InStock -= PurchaseCount; System.out.println("The inventory has been updated."); NewInventory = AllItems[i].m_InStock; System.out.print("There are now " + NewInventory); System.out.println(" units of " + ItemName + " in stock."); } else System.out.println("Can’t find that item. Please check UPC"); } } Figure 6.18 continued There’s nothing really new here except for the use of a boolean variable, and the -= operator that the program uses to adjust the inventory; -= is just like +=, except that it subtracts the right-hand value from the left-hand variable, whereas += adds. We’ve already seen that the boolean type can be useful as a return value from a function that simply needs to tell its user whether it succeeded or failed. Much the same is true of boolean variables such Problem Algorithms Java Interpreter Hardware 226 Chapter 6: TAKING INVENTORY as Found in 6.17: its purpose is to record whether or not we have been successful in finding the StockItem that we’re looking for. Since the only two possible answers to that question are true and false, a boolean variable is ideal for the task. However, we’ll have to wait a bit to analyze the use of Found, because if you compile this program, you’ll find that it is not valid. The problem is the lines: if (PurchaseUPC == AllItems[i].m_UPC), OldInventory = AllItems[i].m_InStock;, ItemName = AllItems[i].m_Name;, AllItems[i].m_InStock -= PurchaseCount;, and NewInventory = AllItems[i].m_InStock; The first of these lines could be translated into English as follows: "If the input UPC is the same as the value of the m_UPC member variable of the object stored in the ith element of the AllItems vector, then..." Problem Algorithms Java Interpreter Hardware while the second of these lines could be translated as: "Set the value of the previous inventory to the value of the m_InStock member variable of the object stored in the ith element of the AllItems vector". The rest of the lines can be translated in similar fashion. While all of these lines are quite understandable to the compiler, they are also illegal, because they are trying to access private member variables of the StockItem class, namely m_UPC, m_InStock and m_Name, from function main. Since main is not a member function of StockItem, this is not allowed. You’ll get a number of error messages from the compiler, which should look something like this: E:\whosj\code\Itemtst3\Itemtst3.java(46,34) : error J0147: Cannot access member ’m_UPC’ in class ’StockItem’ from ’void main(String[ ] args)’ E:\whosj\code\Itemtst3\Itemtst3.java(55,30) : error J0147: Cannot access member ’m_InStock’ in class ’StockItem’ from ’void main(String[ ] args)’ E:\whosj\code\Itemtst3\Itemtst3.java(56,26) : error J0147: Cannot access member ’m_Name’ in class ’StockItem’ from ’void main(String[ ] args)’ E:\whosj\code\Itemtst3\Itemtst3.java(63,15) : error J0147: Cannot access member ’m_InStock’ in class ’StockItem’ from ’void main(String[ ] args)’ E:\whosj\code\Itemtst3\Itemtst3.java(66,30) : error J0147: Cannot access member ’m_InStock’ in class ’StockItem’ from ’void main(String[ ] args)’ private private private private private CAN I HELP YOU? 227 Does this mean that we can’t accomplish our goal of updating the inventory? Not at all. It merely means that we have to do things "by the book" rather than going in directly and reading or changing fields t h a t b e l o n g t o t h e StockItem class. Of course, we could theoretically "solve" this access problem by simply making these fields public rather than private. However, this would allow anyone to mess around with the internal variables in our StockItem objects, which would defeat one of the main purposes of using classes in the first place: that the users of a class can ignore the internal workings of its objects and merely use them according to the class interface. The implementation of the class is our responsibility, not theirs. As it happens, we can easily solve our access problem without exposing the implementation of our class to the user. All we have to do is to add a couple of new methods called CheckUPC and DeductSaleFromInventory to the StockItem class; the first of these allows us to check whether a given UPC belongs to a given StockItem, and the second allows us to adjust the inventory level of an item. Susan had another suggestion as to how to solve this problem, as well as a question about why I hadn’t anticipated it in the first place. Susan: How do CheckUPC and DeductSaleFromInventory h e l p k e e p f r o m exposing the implementation of our class to the user? Steve: Because the user has to call one of our methods to get the information rather than reaching into the fields to read or modify the data. Thus, we can change our implementation by, for example, preventing a sale of more items than the number in stock. Susan: I don’t understand how this works. Is it that you are putting the variables in the methods and this way the user doesn’t get to use them? Steve: As long as the variables are listed as private, the user can’t use them in his program. Instead, if the user wants to do something with an object, he has to call a method of that object. Susan: So then all these new methods do is to act as a go-between linking the StockItem class and the inventory update program to compare data that is privately held in the StockItem class? Steve: Yes, the new methods are designed to make the private data available in a safe manner. I think that’s the same as what you’re saying. Susan: If you wanted to change the program, why didn’t you just do it in the first place instead of breaking it down in parts like this? Problem Algorithms Java Interpreter Hardware 228 Chapter 6: TAKING INVENTORY Steve: Because that’s not the way it actually happens in real life. Susan: Do you think it is less confusing to do that, and also does this act as an example of how you can modify a program as you see the need to do it? Steve: Right on both counts. Figure 6.19 shows the new, improved class definition. The declarations of the two new methods CheckUPC and DeductSaleFromInventory should be pretty easy to figure out: CheckUPC takes the UPC that we want to find and compares it to the UPC in its StockItem, then returns true if they match and false if they don’t. Here’s another good use for the boolean data type; the only possible results of the CheckUPC method are that the UPC in the StockItem matches the one we’ve supplied (in which case we return true) or it doesn’t match (in which case we return false). DeductSaleFromInventory takes the number of items sold and subtracts it from the previous inventory. But where did those other two methods GetInventory and GetName come from? Problem Algorithms Java Interpreter Hardware CAN I HELP YOU? 229 import WAJ.*; import java.io.*; class StockItem { private int m_InStock; private int m_Price; private String m_Name; private String m_Distributor; private String m_UPC; StockItem() { m_InStock = 0; m_Price = 0; m_Name = ""; m_Distributor = ""; m_UPC = ""; } StockItem(String Name, int InStock, int Price, String Distributor, String UPC) { m_InStock = InStock; m_Price = Price; m_Name = Name; m_Distributor = Distributor; m_UPC = UPC; } void Display() { System.out.println("Name: " + m_Name); System.out.println("Number in stock: " + m_InStock); System.out.println("Price: " + m_Price); System.out.println("Distributor: " + m_Distributor); System.out.println("UPC: " + m_UPC); System.out.println(); } boolean Read(InputStream s) { DataInputStream dis = new DataInputStream(s); String tempInStock; String tempPrice; try { m_Name = dis.readLine(); tempInStock = dis.readLine(); Figure 6.19: An enhanced StockItem class (code\Itemtst4\Stockitem.java) Problem Algorithms Java Interpreter Hardware 230 Chapter 6: TAKING INVENTORY tempPrice = dis.readLine(); m_Distributor = dis.readLine(); m_UPC = dis.readLine(); } catch (IOException e) { System.out.println("Problem reading from file \"shop2.in\""); return false; } if (m_UPC == null) return false; try { m_InStock = Integer.parseInt(tempInStock); m_Price = Integer.parseInt(tempPrice); } catch (NumberFormatException e) { System.out.println("Bad number format in file \"shop2.in\""); return false; } return true; Problem } Algorithms Java boolean CheckUPC(String ItemUPC) Interpreter { if (m_UPC.equals(ItemUPC)) Hardware return true; return false; } void DeductSaleFromInventory(int QuantitySold) { m_InStock -= QuantitySold; } int GetInventory() { return m_InStock; } String GetName() { return m_Name; } } Figure 6.19 continued THE CUSTOMER IS ALWAYS RIGHT 231 The Customer Is Always Right I added those methods because I noticed that the "itemtst" program wasn’t very user friendly. Originally it followed these steps: 1. 2. 3. 4. 5. 6. Ask for the UPC. Ask for the number of items purchased. Search through the list to see whether the UPC is legitimate. If so, adjust the inventory. If not, give an error message. Exit. What’s wrong with this picture? Well, for one thing, why should the program make me type in the number of items sold if the UPC is no good? Also, it never told me the new inventory or even what the name of the item was. It may have known these things, but it never bothered to inform me. So I changed the program to work as follows: 1. 2. 3. 4. 5. Ask for the UPC. Search through the list to see whether the UPC was legitimate. If not, give an error message and exit. If the UPC was OK, then a. Display the name of the item and the number in stock. b. Ask for the number of items purchased. c. Adjust the inventory. d. Display a message with the name of the item and number of remaining units in inventory. Exit. To do this, I needed those two new methods GetInventory and GetName, so as you’ve seen I added them to the class definition. Let’s go over these new methods (Figure 6.19). Most of the code in these methods is pretty simple, but there’s one little twist in CheckUPC that I want to make sure you understand: how we compare String variables. As I’ve already mentioned, we can’t just use the normal == that we use with ints and the other primitive types to compare two Strings. Instead, we have to use a method called equals. Why is this, and what happens if we use == anyway? According to the authors of The Java Programming Language, one of whom was the originator of the language, "Using equality operators on String objects does not work as expected. Given String objects str1 and str2, str1==str2 t e s t s w h e t h e r str1 and str2 refer to the same String object. It does not test whether they have the same Problem Algorithms Java Interpreter Hardware 232 Chapter 6: TAKING INVENTORY c o n t e n t s . C o n t e n t e q u a l i t y i s t e s t e d u s i n g String.equals, described in Chapter 8." (p. 116, emphasis in the original) That explains what happens if we use == by accident. However, it doesn’ t e x p l a i n w h y t h e S t r i n g c l a s s w o r k s t h a t w a y . T h e a n s w e r i s that this is another example of the fact that in Java objects of class type do not act like primitive types; instead, we must always refer to objects via references to those objects. Thus, when we compare str1 and str2 in the quoted example, we are really comparing the references str1 and str2, not the objects they refer to. Comparing references tells us only whether they refer to the same objects, which isn’t what we want to know. Now that I’ve explained why == doesn’t work correctly when w e ’re comparing objects of a class type (including String), let ’ s g e t back to our example, which is getting to be enough like a real program that I’m going to start using the term application program (or equivalently, application) to refer to it sometimes. As is generally true of Java programs, the responsibility for doing the user’s work is divided up into a main program (or application program) and a set of more general classes (sometimes called infrastructure classes) used by the application. In this case, itemtst4.java is the main program, or application program, whereas the other file (StockItem.java) is the Problem Algorithms infrastructure. Figure 6.20 shows the new, improved version of our application, which updates the inventory and actually tells the user Java Interpreter what it’s doing. Hardware import WAJ.*; import java.io.*; public class Itemtst4 { static final int MaxItems = 100; public static void main( String args[ ] ) { FileInputStream ShopInfo; StockItem[ ] AllItems; AllItems = new StockItem[MaxItems]; int i; int InventoryCount; int OldInventory; Figure 6.20: Updating StockItem inventory (code\Itemtst4\Itemtst4.java) THE CUSTOMER IS ALWAYS RIGHT 233 int NewInventory; String PurchaseUPC; String ItemName; int PurchaseCount; boolean Found; boolean Result; try { ShopInfo = new FileInputStream("shop2.in"); } catch (IOException e) { System.out.println("Can’t open file \"shop2.in\""); return; } for (i = 0; i < MaxItems; i ++) { AllItems[i] = new StockItem(); Result = AllItems[i].Read(ShopInfo); if (Result == false) break; } InventoryCount = i; System.out.print("What is the UPC of the item? "); PurchaseUPC = RWVar.readString(System.in); Found = false; for (i = 0; i < InventoryCount; i ++) { if (AllItems[i].CheckUPC(PurchaseUPC) == true) { Found = true; break; } } if (Found == true) { OldInventory = AllItems[i].GetInventory(); ItemName = AllItems[i].GetName(); Figure 6.20 continued Problem Algorithms Java Interpreter Hardware 234 Chapter 6: TAKING INVENTORY System.out.print("There are currently " + OldInventory); System.out.println(" units of " + ItemName + " in stock."); System.out.print("How many items were sold? "); PurchaseCount = RWVar.readInt(System.in); AllItems[i].DeductSaleFromInventory(PurchaseCount); System.out.println("The inventory has been updated."); NewInventory = AllItems[i].GetInventory(); System.out.print("There are now " + NewInventory); System.out.println(" units of " + ItemName + " in stock."); } else System.out.println("Can’t find that item. Please check UPC"); } } Figure 6.20 continued This code should be pretty easy to follow; it simply implements the first item purchase scenario I outlined in the section titled "Can I Help You?". Problem To use the debugger for this program, follow the instructions in the Algorithms section titled "Using the debugger" in the file "\readme.txt" on the Java CD in the back of the book. These instructions assume that you’ve Interpreter i n s t a l l e d t h e e x a m p l e s o n d r i v e C : , s o t h a t t h e l o c a t i o n o f t h i s Hardware program is "c:\whosj\code\Itemtst4". When the program asks for a UPC, you can use 7904886261, which is the (made-up) UPC for "antihistamines". When the program asks you for a transaction code, type S for "sale" or P for "price check", and then hit ENTER. Next Customer, Please? Now let’s consider what might be needed to handle some of the other possibilities, starting with the second scenario in that same list. To refresh your memory, here it is again: "Sam wants to know the price of a can of string beans". How would this be expressed as a programming task? Perhaps in this way: "Given a UPC, look up the price of the item in the inventory". Here is a set of steps to solve this problem: NEXT CUSTOMER, PLEASE? 1. 2. 3. 4. 5. 235 Ask for the UPC. Search through the list to see whether the UPC is legitimate. If not, give an error message and exit. If the UPC is OK, then display the name and price of the item. Exit. Have you noticed that this solution is very similar to the solution to the first problem? For example, the search for an item with a given UPC is exactly the same. It seems wasteful to duplicate code rather than using the same code again, and in fact we’ve seen how to avoid code duplication by using a method. This is a good idea, except that the search method can’t be a method of StockItem, because we don’t have the right StockItem yet; if w e d i d , w e w o u l d n’t need to search for it. Therefore, we have to create a new class that contains a field that is an array of StockItems and write the search routine as a method of this new class; the new method would look through its array to find the StockItem we want. Then we can use the methods of StockItem to do the rest of the work. Susan had some questions about having to create a new class to find a StockItem. Susan: I am not sure if I truly understand the problem as to why you can’t search StockItem as a method. Steve: A method of StockItem always accesses a particular StockItem.17 However, our problem is that we don’t know which StockItem we want; therefore, a method in the StockItem class, which necessarily applies to a particular StockItem, won’t solve our problem. Susan: I don’t understand, "because we don’t have the right StockItem yet". Yes we do, it is "string beans". Steve: Which StockItem is that? Is it the first element of AllItems, or the third, or the 37th? Or is it even in the AllItems array at all? Until we can determine which one it is (or if we even have it), we can’t use it. Figure 6.21 shows the class definition for this new class, called Inventory. ______________________________________________________________________________ 17. This is not strictly true. In fact, as we’ll see in the next chapter, it is possible to have a method called a static method , w h i c h d o e s n o t r e f e r t o a n y p a r t i c u l a r o b j e c t o f a class. However, such methods aren’t applicable here, so I’ll just ignore their existence for the time being. Problem Algorithms Java Interpreter Hardware 236 Chapter 6: TAKING INVENTORY import WAJ.*; import java.io.*; class Inventory { static final int MaxItems = 100; private int m_StockCount; private StockItem[ ] m_Stock; Inventory() { m_Stock = new StockItem[MaxItems]; m_StockCount = 0; } int LoadInventory(FileInputStream s) { int i; boolean Result; Problem Algorithms Java Interpreter Hardware for (i = 0; i < MaxItems; i ++) { m_Stock[i] = new StockItem(); Result = m_Stock[i].Read(s); if (Result == false) break; } m_StockCount = i; return m_StockCount; } StockItem FindItem(String UPC) { int i; boolean Found = false; for (i = 0; i < m_StockCount; i ++) { if (m_Stock[i].CheckUPC(UPC) == true) { Found = true; break; } } if (Found == true) return m_Stock[i]; return null; } } Figure 6.21: Inventory class (code\Itemtst5\Inventory.java) NEXT CUSTOMER, PLEASE? 237 Here’s the next installment of that discussion of searching for a StockItem. Susan: Ugh. I know we have gone over this but I don’t understand why it doesn’t know it is there if it has a UPC. Steve : W h a t a r e t h e r e f e r e n t s o f t h e t h r e e o c c u r r e n c e s o f " i t " i n t h a t sentence? That might help me answer, because I can’t find my secret decoder ring. Susan: Why StockItem can’t search string beans even though it has a UPC. Steve: The StockItem class cannot have a method that looks for a StockItem, because to call a StockItem method, you need a StockItem variable. Once you have that variable, you don’t need to look for it. Let’s say we have an array of StockItems called AllItems. In that case, to call a StockItem method, you would have to pick one of the StockItems in that array to call it on. Once you have the StockItem you’re looking for, you don’t need to search for it. However, you can have a class such as Inventory, each of whose objects contains an array of StockItems. Then that class can have a method that searches for a particular StockItem in the array of StockItems in a given Inventory object. Susan: Oh, then so far that is all our program is able to do? It is unable to locate one item of all possible items and display it just from the UPC code? In fact that is what we are trying to accomplish, right? Steve: Exactly. Susan also wasn’t sure what the purpose of the LoadInventory method was. Susan: What does the code int LoadInventory (FileInputStream s) do? Does it just give you an object named LoadInventory that reads a file that has an argument named s? I don’t get this. Steve: That’s quite close. The line you’re referring to is the declaration of a method named LoadInventory, which takes a reference to a FileInputStream. The implementation of the method, as you’ll see shortly, reads StockItem records from the file connected to the FileInputStream. Once that was cleared up, she had some questions about the way the FindItem method works, including its interface. Problem Algorithms Java Interpreter Hardware 238 Chapter 6: TAKING INVENTORY Susan: Is the argument UPC to the FindItem method a String because it is returning the name of a stock item? Steve: That’s the input to the FindItem method, not its output; therefore, it’s not "returning" anything. Or did I misunderstand your question? Susan: Let’s see if I even know what I was asking here. OK, how about this: I wanted to know why UPC was a String and not an int, since a UPC is usually a number. In this case, it will be returning a name of a "found item", so that is why it is a String, right? Steve: No, it’s because the UPC is ten digits, which is too many to fit in an int. There actually is another type of number in Java, a long, that is big enough. However, we’re not using the UPC in calculations anyway, so we might as well store it as a String. Susan: Oh, OK. So a String is useful for storing numbers that are somewhat lengthy as long as you don’t calculate with those numbers. They are nothing more than "numerical words"? Steve: Exactly. Problem Algorithms Java Interpreter Hardware Most of this should be fairly self-explanatory by this point. We start out with the default constructor which makes an empty Inventory. Figure 6.22 has the implementation for the default constructor. Inventory() { m_Stock = new StockItem[100]; m_StockCount = 0; } Figure 6.22: Default constructor for Inventory class (from code\Itemtst5\Inventory.java) There’s nothing complex here; we’re initializing the m_Stock variable to a newly constructed array of 100 StockItems and the number of active StockItems to 0. The latter, of course, is because we haven’t yet read any data in from the file. Then we have a couple of handy methods. The first is LoadInventory, which will take data from a FileInputStream and store it in its Inventory object, just as we did directly in our application itemtst4.java. Susan had a question about this. Susan: How did you know that you were going to need to use a FileInputStream again? NEXT CUSTOMER, PLEASE? 239 Steve: Because we’re reading data from a file into an array of StockItems, and reading data from a file is what FileInputStreams are for. Figure 6.23 shows the implementation of LoadInventory. int LoadInventory(FileInputStream s) { int i; boolean Result; for (i = 0; i < 100; i ++) { m_Stock[i] = new StockItem(); Result = m_Stock[i].Read(s); if (Result == false) break; } m_StockCount = i; return m_StockCount; } Figure 6.23: The LoadInventory method for the Inventory class (from code\Itemtst5\Inventory.java) Now we’re up to the FindItem method (Figure 6.24). Its declaration, StockItem FindItem(String UPC), is pretty simple: It takes an argument of type String which contains the UPC that we’re looking for. Its implementation is pretty simple too: It will search the Inventory object for the StockItem that has that UPC and return a reference to that StockItem, which can then be interrogated to find the price or whatever other information we need. However, there’s a serious design issue here: What should this method return if the UPC doesn’t match the UPC in any of the StockItem entries in the Inventory object? The application program has to be able to determine whether or not the UPC is found. In the original program this was no problem, because the main program maintained that information itself. But in this case, the method FindItem has to communicate success or failure to the caller somehow. Of course, we could use a return value of true or false to indicate whether the UPC is found, but we’re already using the return value to return the StockItem to the calling method. Problem Algorithms Java Interpreter Hardware 240 Chapter 6: TAKING INVENTORY Nobody’s Home There’s one more possibility. We can return a null reference, which will indicate that we couldn’t find the StockItem we were looking for. I like this solution, because when the method terminates, the application program has to test something anyway to see if the desired StockItem was found; why not test whether the returned reference is a null reference? This solution also has the advantage of avoiding changes to our implementation of StockItem: We can test whether a reference is null by using == to compare it to null with the expression if (FoundItem == true). Let’s hear from Susan on the topic of method return values. Susan: This is something I have not thought about before: When you call a method where does the return value go? Steve: Wherever you put it. If you say x = sum(weight);, then the return value goes into x. If you just say sum(weight);, then it is discarded. Susan: Why is it discarded? Problem Algorithms Java Interpreter Hardware Steve: If you don’t use it, the compiler assumes you aren’t interested in it. Susan: So the return value can be used in only one place? Steve: Yes, unless you save it in a variable, in which case you can use it however you like. Figure 6.24 shows the implementation of FindItem, which uses CheckUPC to check whether the requested UPC is the one in the current item and returns a null reference if the desired UPC isn’t found in the inventory list. Now that we’ve solved the problem of finding an object given its UPC in a more general way, we can also directly apply this same lookup method to the previous problem of updating the inventory. In that case, after we get a reference to the correct StockItem and update its inventory via DeductSaleFromInventory, we’re done; since FoundItem is a reference to the StockItem object that we’re interested in, we have actually updated that object by calling DeductSaleFromInventory. NOBODY’S HOME 241 StockItem FindItem(String UPC) { int i; boolean Found = false; for (i = 0; i < m_StockCount; i ++) { if (m_Stock[i].CheckUPC(UPC) == true) { Found = true; break; } } if (Found == true) return m_Stock[i]; return null; } Figure 6.24: FindItem method for Inventory class (from code\Itemtst5\invent1.java) Here’s my interchange with Susan on the implementation of this method: S u s a n : A b o u t t h e f i r s t if s t a t e m e n t , i f ( m _ S t o c k [ i ] . C h e c k U P C ( U P C ) = = t r u e ) : Does that mean if you find the UPC you are looking for, then the program breaks and you don’t need to continue looking? In that case, what does the statement Found = true; do? It looks as if you are setting Found to the value true. Steve: That’s right. If we’ v e f o u n d t h e i t e m w e ’re looking for, then Found will have been set to true, so we’ll return the real item; otherwise, we’ l l r e t u r n a n u l l S t o c k I t e m t o i n d i c a t e t h a t w e c o u l d n’t find the one requested. Susan: Also, I am confused about why we are working on the actual object. I thought that was the opposite, that everything is a reference to some object in Java. Steve: Yes, everything (other than a primitive variable) is a reference to a n o b j e c t i n J a v a . T h a t’ s p r e c i s e l y w h y , w h e n w e a r e w o r k i n g o n a StockItem from the AllItems array, we are actually working on the original object, not on a copy. Because a StockItem variable is just a pointer to a StockItem object, copying a StockItem variable results in another StockItem variable that points to the same StockItem object that the first StockItem Problem Algorithms Java Interpreter Hardware 242 Chapter 6: TAKING INVENTORY variable pointed to. Thus, when we access the StockItem object through the newly created StockItem variable, we are actually accessing the same StockItem object that the original StockItem variable referred to. This means that when we have modified the StockItem by, for example, reducing the number of items in its stock, we have actually modified the original StockItem object that the AllItems array element referred to, not a copy of it, and therefore the object the array element refers to has been changed. This is a case where the situation in Figures 6.7 through 6.9 is actually what we want, rather than an error. The application program needs one more method to be added to the interface of StockItem to retrieve the price from the object once we have found it. This additional method, GetPrice(), is shown in Figure 6.25. int GetPrice() { return m_Price; } Figure 6.25: The implementation of GetPrice (from code\Itemtst5\StockItem.java) Problem Algorithms We’ r e a l m o s t r e a d y t o e x a m i n e t h e r e v i s e d t e s t p r o g r a m . F i r s t , Java though, let’s take a look at the class definitions of the StockItem and Interpreter Inventory classes all in one place. The definition of StockItem is in Hardware Figure 6.26. import WAJ.*; import java.io.*; class StockItem { private int m_InStock; private int m_Price; private String m_Name; private String m_Distributor; private String m_UPC; Figure 6.26: Current implementation for StockItem class (code\Itemtst5\StockItem.java) NOBODY’S HOME 243 StockItem() { m_InStock = 0; m_Price = 0; m_Name = ""; m_Distributor = ""; m_UPC = ""; } StockItem(String Name, int InStock, int Price, String Distributor, String UPC) { m_InStock = InStock; m_Price = Price; m_Name = Name; m_Distributor = Distributor; m_UPC = UPC; } void Display() { System.out.println("Name: " + m_Name); System.out.println("Number in stock: " + m_InStock); System.out.println("Price: " + m_Price); System.out.println("Distributor: " + m_Distributor); System.out.println("UPC: " + m_UPC); System.out.println(); } boolean Read(InputStream s) { DataInputStream dis = new DataInputStream(s); String tempInStock; String tempPrice; try { m_Name = dis.readLine(); tempInStock = dis.readLine(); tempPrice = dis.readLine(); m_Distributor = dis.readLine(); m_UPC = dis.readLine(); } catch (IOException e) { System.out.println(e); return false; } Figure 6.26 continued Problem Algorithms Java Interpreter Hardware 244 Chapter 6: TAKING INVENTORY if (m_UPC == null) return false; try { m_InStock = Integer.parseInt(tempInStock); m_Price = Integer.parseInt(tempPrice); } catch (NumberFormatException e) { System.out.println(e); return false; } return true; } boolean CheckUPC(String ItemUPC) { if (m_UPC.equals(ItemUPC)) return true; return false; } Problem DeductSaleFromInventory(int QuantitySold) Algorithms void { Java m_InStock -= QuantitySold; Interpreter } Hardware int GetInventory() { return m_InStock; } String GetName() { return m_Name; } int GetPrice() { return m_Price; } } Figure 6.26 continued And Figure 6.27 contains the implementation for Inventory. TESTING, 1, 2, 3. . . 245 import WAJ.*; import java.io.*; class Inventory { static final int MaxItems = 100; private int m_StockCount; private StockItem[ ] m_Stock; Inventory() { m_Stock = new StockItem[MaxItems]; m_StockCount = 0; } int LoadInventory(FileInputStream s) { int i; boolean Result; for (i = 0; i < MaxItems; i ++) { m_Stock[i] = new StockItem(); Result = m_Stock[i].Read(s); if (Result == false) break; } m_StockCount = i; return m_StockCount; } StockItem FindItem(String UPC) { int i; boolean Found = false; for (i = 0; i < m_StockCount; i ++) { if (m_Stock[i].CheckUPC(UPC) == true) { Found = true; break; } } if (Found == true) return m_Stock[i]; return null; } } Figure 6.27: Latest Inventory class (code\Itemtst5\Inventory.java) Problem Algorithms Java Interpreter Hardware 246 Chapter 6: TAKING INVENTORY Testing, 1, 2, 3. . . To finish this stage of the inventory control project, Figure 6.28 is the revised test program that uses the Inventory class rather than doing its own search through an array of StockItems. import WAJ.*; import java.io.*; Problem Algorithms Java Interpreter Hardware public class Itemtst5 { public static void main( String args[ ] ) { FileInputStream ShopInfo; String PurchaseUPC; int PurchaseCount; String ItemName; int OldInventory; int NewInventory; Inventory MyInventory = new Inventory(); StockItem FoundItem; String TransactionCode; try { ShopInfo = new FileInputStream("shop2.in"); } catch (IOException e) { System.out.println("Can’t open file \"shop2.in\""); return; } MyInventory.LoadInventory(ShopInfo); System.out.print("What is the UPC of the item? "); PurchaseUPC = RWVar.readString(System.in); FoundItem = MyInventory.FindItem(PurchaseUPC); if (FoundItem == null) { System.out.println("Can’t find that item. Please check UPC."); return; } Figure 6.28: Updated inventory application (code\Itemtst5\Itemtst5.java) TESTING, 1, 2, 3. . . 247 OldInventory = FoundItem.GetInventory(); ItemName = FoundItem.GetName(); System.out.print("There are currently " + OldInventory); System.out.println(" units of " + ItemName + " in stock."); System.out.print("Please enter transaction code as follows:\n"); System.out.print("S (sale), C (price check): "); TransactionCode = RWVar.readString(System.in); if (TransactionCode.equals("C") || TransactionCode.equals("c")) { System.out.println("The name of that item is: " + ItemName); System.out.println("Its price is: " + FoundItem.GetPrice()); } else if (TransactionCode.equals("S") || TransactionCode.equals("s")) { System.out.print("How many items were sold? "); PurchaseCount = RWVar.readInt(System.in); FoundItem.DeductSaleFromInventory(PurchaseCount); System.out.println("The inventory has been updated."); NewInventory = FoundItem.GetInventory(); System.out.print("There are now " + NewInventory + " units of "); System.out.println(ItemName + " in stock."); } } } Figure 6.28 continued This program can perform either of two operations, depending on what the user requests. Once the UPC has been typed in, the user is prompted to type either "C" for price check or "S" for sale. Then an if statement selects which of the two operations to perform. The code for the S (i.e., sale) operation is the same as it was in the previous version of this application, except that, of course, at that time it was the only possible operation, so it wasn’t controlled by an if statement. The code for the C (i.e., price check) operation is new, but it’s very simple; it just displays both the item name and the price. The only part of the program that might not be obvious at this point is the expression in the if statement that determines whether the user wants to enter a price check or sale transaction. The first part of Problem Algorithms Java Interpreter Hardware 248 Chapter 6: TAKING INVENTORY the test is if (TransactionCode.equals("C") || TransactionCode.equals("c")). The || is the "logical or" operator. An approximate translation of this expression is "if at least one of the two expressions on its right or left is true, then produce the result true; if they’re both false, then produce the result false".18 In other words, this if statement will be true if the TransactionCode variable is either C or c. Why do we have to check for either a lower or upper case letter, when the instructions to the user clearly state that the choices are C or S? This is good practice because users generally consider upper and lower case letters to be equivalent. Of course, as programmers, we know that the characters c and C are completely different; however, we should humor the users in this harmless delusion. After all, they’re our customers! Susan had a couple of questions about this program. Susan: What does the following output statement mean: System.out.print("S (sale), C (price check): ");? I am not clear as to what this is doing. Steve: Nothing special; the prompt S (sale), C (price check) is just to notify the user what his or her choices are. Problem Algorithms Java Interpreter Hardware Susan: OK, so the line with the || is how you tell the computer to recognize upper case as well as lower case to have the same meaning? Steve: Yes, that’s what we’re doing here. Susan: So what do you call those || thingys? Steve: They’re called "vertical bars". The operator that is spelled || is called a "logical OR" operator, because it results in the value t r u e i f either the left-hand or the right-hand expression is true (or if both are true). Susan: What do you mean by using else and if in the line else if ( T r a n s a c t i o n C o d e . e q u a l s ( " S " ) | | T r a n s a c t i o n C o d e . e q u a l s ( " s " ) )? I don’t believe I have seen them used together before. Steve: You may be right. However, it’s not that complicated: As always, the else means that we’re specifying actions to be taken if the original if ______________________________________________________________________________ 18. The reason it’s only an approximate translation is that there is a special rule in Java governing the execution of the || operator: If the expression on the left is true, then the answer is known to be true and the expression on the right will not be executed. The reason for this short-circuit evaluation rule is that in some cases you may want to write a right-hand expression that will only be legal if the left-hand expression is false. TESTING, 1, 2, 3. . . 249 isn’t true. The second if merely checks whether another condition is true and executes its controlled block if that is the case. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Itemtst5". When the program asks for a UPC, you can use 7904886261, which is the (made-up) UPC for "antihistamines". When the program asks you for a transaction code, type S for "sale" or P for "price check", and then hit ENTER. Paging Rosie Scenario By this point, you very understandably might have gotten the notion that we have to make changes to our classes every time we need to do anything slightly different in our application program. In that case, where’s the advantage of using classes instead of just writing the whole program in terms of ints, chars, and so on? Well, this is your lucky day. It just so happens that the next (and last) scenario we are going to examine requires no more methods at all; in fact, we don’t even have to change the application program. Here it is, for reference: "Judy comes in looking for chunky chicken soup; there’s none on the shelf where it should be, so we have to check the inventory to see if we’re supposed to have any". The reason we don’t have to do anything special for this scenario is that we’re already displaying the name and inventory for the item as soon as we find it. Of course, if we hadn’t already handled this issue, there are many other ways that we could solve this same problem. For example, we could use the Display method of StockItem to display an item as soon as the UPC lookup succeeds, rather than waiting for the user to indicate what operation our application is supposed to perform. For that matter, we’d have to consider a number of other factors in writing a real application program, even one that does such a simple task as this one. For example, what would happen if the user indicated that 200 units of a particular item had been sold when only 100 were in stock? Also, how would we find an item if the UPC isn’t available? The item might very well be in inventory somewhere, but the current implementation of Inventory doesn’t allow for the Problem Algorithms Java Interpreter Hardware 250 Chapter 6: TAKING INVENTORY possibility of looking up an item by any information other than the UPC. Although these topics and many others are essential to the work of a professional programmer, they would take us too far afield from our purpose in this book, which is to teach you how to program using Java. Therefore, we will leave them for another day (and another book). Now let’s review what we’ve covered in this chapter. Review The most important concept in this chapter is the idea of creating user-defined data types. In Java, this is done by defining a class for each such data type. Each class has a class definition, which both describes the behavior that the class displays to the "outside world" (i.e., other unrelated methods), and tells the compiler how to perform those behaviors. A data item of a class type is called an object. A class is defined in terms of the methods and variables of which the class is composed; the latter are called fields to distinguish them from local variables that don’t belong to a particular object. Problem Of course, one obvious question is why we need to make up our Algorithms own variable types. What’s wrong with char, int, and the rest of the Java primitive types built into Java? The answer is that it’s easier to write Interpreter an inventory control program, for example, if we have data types Hardware representing items in the stock of a store, rather than having to express everything in terms of the primitive types. An analogy is the universal preference of professionals for communicating in technical jargon rather than "plain English": Jargon conveys more information, more precisely, in less time. The idea of using objects as the fundamental building blocks of programming is the basis of the "object-oriented programming" paradigm.19 Then we examined how creating classes differs from using classes, as we have been doing throughout the book. A fairly good analogy is that creating your own classes is to using classes as writing a program is to using a program. Next, we went through the steps needed to actually create a new class; our example is the StockItem class , w h i c h i s d e s i g n e d t o a l l o w ______________________________________________________________________________ 19. Purists may not approve of this use of the term object-oriented programming, as I’m not using this term in its strictest technical sense. However, since we are using objects and classes as our central organizing ideas, using the term object-oriented seems reasonable to me in this context. REVIEW 251 tracking of inventory for a small grocery store. These steps include writing the class d e f i n i t i o n , w r i t i n g t h e p r o g r a m t h a t u s e s t h e class, compiling both, and running the final program. Then we moved from the general to the specific, analyzing the particular data and methods that the StockItem class needed to perform its duties in an application program. The fields needed for each StockItem object included the name, count, distributor, price, and UPC. Of course, merely having these fields doesn’t make a StockItem object very useful, if it can’t do anything with them. This led us to the topic of what methods might be needed for such a class. Next, we looked at the first version of a class definition for StockItem (Figure 6.2) which tells the user (and the compiler) exactly what operations objects of this class can perform and how these operations work. Some items of note in this construct are these: 1. 2. 3. 4. The access specifiers public and private, which control access to the implementation of a class by methods not in the class. Fields and methods marked as public are available for use by outside methods, whereas fields and methods marked as private are usable only by methods of the same class. The definitions of the constructor m e t h o d s , w h i c h c o n s t r u c t a new object of the class. The first noteworthy point about constructors is that they have the same name as the class, which is how the compiler identifies them as constructors. The second point of note is that there can be more than one constructor for a given class; all constructors have the same name, and are distinguished by their argument lists. This facility, called method overloading , is applicable to Java methods in general, not just constructors. That is, you can have any number of methods with the same name as long as they have different argument lists; the difference in argument lists is enough to make the compiler treat them as different methods. In this case, we have written two constructors: the default constructor, which i s u s e d t o c r e a t e a S t o c k I t e m when we don’t specify an initial value, and a constructor that has arguments to specify values for all of the fields. The declaration of a "normal" method (that is, a method that is not a constructor) named Display, which as its name indicates, is used to display a StockItem on the screen. The declaration of the fields of StockItem, which are used to keep track of the information for a specific object of the StockItem class. Problem Algorithms Java Interpreter Hardware 252 Chapter 6: TAKING INVENTORY Next, we examined the default constructor for the StockItem class: StockItem(), which simply sets the values of the fields in a newly constructed StockItem to either 0 or "", whichever is appropriate for their type. The next topic we visited was the scope of fields, which is class scope. Each object of a given class has one set of fields; these fields live as long as the object does. These fields can be accessed from any method of the same class as though they were global variables. Then we examined how the default constructor was actually used in the example program, in the statement item1 = new StockItem();. This led to a discussion of the fact that the person who writes a class isn’t always the person who uses it. One reason for this is that the skills required to write a program using a class are not the same as those required to create the class in the first place. Next, we covered the other constructor for the StockItem class. This one has arguments specifying the values for all of the fields that make up the data part of the class. Then we got to the next-to-the-last method of the first version of the StockItem class: the Display method, which as its name indicates is used to display the contents of a StockItem on the screen. This method uses the preexisting ability of System.out to display the ints and Strings Problem Algorithms that hold the contents of the StockItem. The return type of this method is a type we hadn’t seen before, void, which simply means that there is Java Interpreter no return value from this method. We don’t need a return value from the Display method because we call it solely for its side effect: Hardware displaying the value of its StockItem on the screen. Next, we took up the private items in the StockItem class definition, namely, the fields. We covered two reasons why it is a good idea to keep the fields private: First, it makes debugging easier, because only the methods can modify the fields; second, we can change the names or types of our fields or delete them from the class definition much more easily if we don’t have to worry about what other methods might be relying on them. While we were on the subject of the fields of StockItem, I also explained how we could use an int to store a price: By expressing the price in cents, rather than dollars and cents, any reasonable price could be stored in such a variable. The final method in this version of the StockItem class was SetName, which changes the name of the StockItem for which it is called. I used this method to show that variables that seem to be class objects really aren’t; instead, they are references to those objects. This is very important because if we change an object through a reference, all references to that variable will now refer to that changed object. This REVIEW 253 is not how primitive variables behave, and it is a rich source of subtle program errors, so it’s important to understand this point fully. As we continued with the analysis of how the StockItem objects would be used, we discovered that our example program actually needed an array of such objects, one for each different item in the stock. We also needed some way to read the information for these StockItem objects from a disk file, so we wouldn’t have to type it in every time we started the program up. So the next program we examined provided this method via a Java class we hadn’t seen b e f o r e : F i l e I n p u t S t r e a m (for input from a file). We also added a new method called R e a d t o u s e t h i s n e w class to read information for a StockItem from the file containing that information. This led to a discussion of exception handling, which is the Java mechanism that is used to deal with unexpected results. In this case, we had to handle the possibility that the file we were trying to open might not exist. Then we got to the question of how we could tell when there was n o d a t a l e f t i n t h e i n p u t f i l e ; t h e a n s w e r w a s t h a t t h e Read method could determine this by checking whether it received a null reference instead of the data it was requesting from the file. Read transmitted that information back to its caller by returning a value of the boolean type: true if it had succeeded in reading data from the file and false otherwise. We used a false return value from Read to trigger a break statement, which terminates whatever loop contains the break. In this case, the loop was the one that read data from the input file, so the loop would stop whenever we got to the end of the input file or when we had read 100 records, whichever came first. This led to a detailed investigation of whether the number of records read was always calculated correctly. The problem under discussion was the potential for a fence post error, also known as an off-by-one error. After careful consideration, I concluded that the code as written was correct. Having cleared up that question, we proceeded to some other scenarios that might occur in the grocery store for which this program theoretically was being written. All of the scenarios we looked at had a common requirement: to be able to look up a StockItem, g i v e n s o m e i n f o r m a t i o n a b o u t i t . W e f i r s t t r i e d t o h a n d l e this requirement by reading the UPC directly from each StockItem object in the array. When we found the correct StockItem, we would display and update the inventory for that StockItem. However, this didn’t compile, because we were trying to access private fields of a StockItem object from outside that class, which is illegal. Although we could have changed those fields from private to public, that would Problem Algorithms Java Interpreter Hardware 254 Chapter 6: TAKING INVENTORY directly contradict the reason that we made them private in the first place; that is, to prevent external methods from interfering in the inner workings of our StockItem objects. Therefore, we solved the p r o b l e m b y a d d i n g s o m e n e w m e t h o d s (DeductSaleFromInventory and CheckUPC) to manipulate the inventory information for a StockItem and check the UPC of a StockItem, respectively. While I was making these changes, I noticed that the original version of the test program wasn’t very helpful to its user; it didn’t tell the user whether the UPC was any good, the name of the item, or how much inventory was available for sale. So I added some more methods (GetInventory and GetName) to allow this more "user-friendly" information to be displayed. Then we progressed to the second of the grocery store scenarios, in which the task was to find the price of an item, given its UPC. This turned out to be very similar to the previous problem of finding an item to update its inventory. Therefore, it was a pretty obvious step to try to make a method out of the "find an item by UPC" operation, rather than writing the code for the search over again. However, this couldn’t be a method of StockItem, because the whole idea of this method was to locate a StockItem. A method of StockItem needs a S t o c k I t e m o b j e c t t o w o r k o n , b u t w e d i d n’t have the StockItem object Problem Algorithms yet. The solution was to make another class, called Inventory, which had Java Interpreter methods to load the inventory information in from the disk file (LoadInventory) and search it for a particular StockItem (FindItem). Most of Hardware this class was pretty simple, but we did run into an interesting design question: What should the FindItem method return if the UPC didn’t match anything in the inventory? After some consideration, I decided to use a null reference; that is, a reference that doesn’t refer to any actual object. Then we updated the test program to use this new means of locating a StockItem. Since the new version of the test program could perform either of two methods (price check or sale), we also added some output and input statements to ask the user what he wanted to do. To make this process more flexible, we allowed the user to type in either an upper or lower case letter to select which method to perform. This brought up the use of the "logical OR" operator || to allow the controlled block of an if statement to be executed if either (or both) of two expressions is true. We also saw how to combine an else with a following if statement, when we wanted to select among more than two alternatives. At this point, we noted that changing the StockItem that we got from the FindItem changed the actual StockItem in the array; the reason is that REVIEW 255 we have a copy of a reference to the actual StockItem object, and copying a reference produces another reference that refers to the same object as the first reference. Therefore, we are not working on a copy of the object, but on the original object. This brought us to the final scenario, which required us to look up the inventory for an item, given its UPC. As it happened, we had already solved that problem by the simple expedient of displaying the name and inventory of the StockItem as soon as it was located. Finally, I mentioned a few other factors, such as alternative means of looking up an item without knowing its UPC, that would be important in writing a real application program, and noted that we couldn’t go into them here due to space limitations. Exercises 1. In a real inventory control program, we would need to do more than merely read the inventory information from a disk file, as we have done in this chapter. We’d also want to be able to write the updated inventory back to the disk file via a FileOutputStream object, which is exactly like a FileInputStream object except that it allows us to write to a file rather than reading from one. You can use the RWVar.println methods that write ints and Strings to actually do the output. Modify the class descriptions of the StockItem and Inventory classes to include two new methods, Write and StoreInventory, that are needed in those respective classes to support this new ability. An answer to the preceding exercise can be found at the end of the chapter. 2. 3. Suppose that the store that is using our inventory control program adds a new pharmacy department. Most of their items are nonprescription medications that can be handled with the StockItem class we’ve already created, but their prescription drug items need to be handled more carefully. This means that the DeductSaleFromInventory member function has to ask for a password before allowing the sale to take place. Create a DrugStockItem class that enforces this new rule. The store also needs some way to keep track of its employees’ hours so it can calculate their pay. We’ll assume that the employees are paid their gross wages, ignoring taxes. These wages are calculated as follows: Problem Algorithms Java Interpreter Hardware 256 Chapter 6: TAKING INVENTORY a. b. Managers are paid a flat amount per week, calculated as their hourly rate multiplied by 40 hours. Hourly employees are paid a certain amount per hour, no matter how many hours are worked (i.e., overtime is not paid at a higher rate). Write an Employee class that allows the creation of Employee objects with a specified hourly wage level and either "manager" or "hourly" salary rules. The pay for each object is to be calculated via a CalculatePay member function that uses the "manager" or "hourly" category specified when the object was created. You should use the float data type to store the pay rate and to calculate the total pay; this data type behaves just like an int except that it can store values with fractional parts as well as whole numbers. Conclusion Problem Algorithms Java Interpreter Hardware In this chapter, we’ve delved into the concepts and implementations of classes and objects, which are the constructs that make Java an object-oriented language. Of course, we have only scratched the surface of these powerful topics; in fact, we’ll spend the rest of this book on the fundamentals of classes and objects. Unfortunately, it’s impossible to cover these constructs and all of their uses in any one book, no matter how long or detailed it may be, and I’m not going to try to do that. Instead, we’ll continue with our in-depth examination of the basics of object-oriented programming. In the next chapter, we ’ll continue by seeing how we can extend our StockItem class to handle items with expiration dates. Answers to Exercises 1. F i g u r e 6 . 2 9 s h o w s t h e i m p l e m e n t a t i o n o f t h e Write method for StockItem. ANSWERS TO EXERCISES 257 void Write(OutputStream s) { try { DataOutputStream ostr = new DataOutputStream(s); RWVar.println(ostr,m_Name); RWVar.println(ostr,m_InStock); RWVar.println(ostr,m_Price); RWVar.println(ostr,m_Distributor); RWVar.println(ostr,m_UPC); } catch (IOException e) { System.out.println(e); } } Figure 6.29: The Write method for the StockItem class (from code\Itemtst6\StockItem.java) Figure 6.30 is the implementation of the StoreInventory method of the Inventory class. void StoreInventory(OutputStream s) { int i; for (i = 0; i < m_StockCount; i ++) { m_Stock[i].Write(s); } } Figure 6.30: The StoreInventory method for the Inventory class (from code\Itemtst6\Inventory.java) As you can see, neither of these is tremendously complex or, for that matter, very different from its counterpart used to read the data in from the file in the first place. Finally, Figure 6.31 shows the changes needed to the application program to write the updated inventory back to a new file. Problem Algorithms Java Interpreter Hardware 258 Chapter 6: TAKING INVENTORY FileOutputStream NewShopInfo; try { NewShopInfo = new FileOutputStream("shop2.out"); } catch (IOException e) { System.out.println("Can’t open file \"shop2.out\""); return; } MyInventory.StoreInventory(NewShopInfo); } Figure 6.31: The changes to the application program (from code\Itemtst6\Itemtst6.java) Of course, in a real program, it would probably be better to write the updated inventory back to the original file, so that the next time we ran the program, the updated inventory would be used. However, in the case of a test application, it’s simpler to avoid modifying the Problem input file so we can run the same test again if necessary. Algorithms To use the debugger for this program, follow the instructions in the Java section titled "Using the debugger" in the file "\readme.txt" on the Interpreter CD in the back of the book. These instructions assume that you’ve Hardware installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Itemtst6". When the program asks for a UPC, you can use 7904886261, which is the (made-up) UPC for "antihistamines". When the program asks you for a transaction code, type S for "sale" or P for "price check", and then hit ENTER. Susan had a question about this exercise. Susan: What is the purpose of Write and StoreInventory? Steve: Write writes a StockItem to a file; StoreInventory writes all of the StockItems in an Inventory to a file. Susan: Oh. So all we were doing before was reading and not writing? Steve: Correct. Chapter 7 Stocking Up In this chapter we’ll start to build on our previous StockItem class by using another of the primary organizing principles of Java: inheritance. First, let’s define a few terms and then take a look at the objectives for the chapter. Definitions Inheritance is the definition of one class as a more specific version of another class which has been previously defined. This process is also called extending a class. The newly defined c l a s s i s c a l l e d t h e derived class ( o r s o m e t i m e s t h e s u b c l a s s ) , w h e r e a s t h e p r e v i o u s l y defined class is called the base class (or sometimes the superclass). In this book, we will use the terms "base class" and "derived class". The derived class inherits all of the fields and regular methods from the base class. Inheritance is one of the primary organizing principles of object-oriented programming. A regular method is any method that is not a constructor. A derived class inherits all regular methods (such as the Display method of the StockItem class) from its base class. A method in a derived class is said to override a base class method if the derived class method has the same signature (name and argument types) as the base class method. The derived class method will be called instead of the base class method when the method is called for an object of the derived class. A method in a derived class with the same name as a method in the base class but with a different signature does not override the base class method. Instead, both the 259 260 Chapter 7: STOCKING UP derived class method and the base class method are available for use with objects of the derived class.1 For example, the method Reorder(OutputStream s) may be defined in a base class ( S t o c k I t e m) and in a derived class (DatedStockItem); when Reorder is called for an object of the base class StockItem, the base class version of R e o r d e r w i l l b e c a l l e d , a n d w h e n R e o r d e r is called for an object of the derived class DatedStockItem, the derived class version of Reorder will be called. This behavior of Java allows a derived class to supply the same functionality as a base class but implement that functionality in a different way. The keyword protected is an access specifier. When present in a base class definition, it allows derived class methods access to members in the base class part of a derived class object, while preventing access by other methods outside the base class. The base class p a r t of a derived class object is an unnamed component of the derived class object whose fields and methods are accessible as though they were defined in the derived class, so long as they are either public or protected. Problem Algorithms Java Interpreter Hardware A static method is a method that can be called without reference to a n o b j e c t o f i t s c l a s s. Because such a method call is not connected with any particular object of its class, a static method cannot refer to fields of the class. Objectives for This Chapter By the end of this chapter, you should: 1. 2. Understand how we can use inheritance to create a new class by adding new methods or fields to those provided by an existing class. Understand how methods of a derived class can override methods of the base class when objects of the derived class are involved. ______________________________________________________________________________ 1. C++ programmers should note this significant difference between the handling of inheritance in C++ and Java. UNDER CONTROL 261 Under Control Before we get to the details of our extensions to the StockItem class, let’s expand a bit on the first objective as it applies to this particular case. There are two reasons to use inheritance. The first is to create a new class that has all of the capabilities of an existing class, while adding capabilities that are unique to the new class. In such a case, objects of the new class are clearly not equivalent to objects of the preexisting class, which means that the user of these classes has to know which class any given object belongs to so that he or she can tell which operations that object can perform.2 Therefore, it does not make sense to be able to substitute objects of the derived class f o r objects of the base class. We could call this "inheritance for extension"; it’s illustrated by one of the Employee class exercises in this chapter. In the current case, however, we’ll be using inheritance to create a new class called DatedStockItem that will be exactly like the StockItem class except that its items will have expiration dates. As a result, objects of this class will behave, as far as the application programmer is concerned, just like objects of the StockItem class. Of course, to create an object of this class, the expiration date for the object must be provided, but once such an object exists, its user can view it in exactly the same way as an object of the base class, which makes this an example of "inheritance for re-implementation". In such a case, it is reasonable to be able to substitute objects of the derived class for those of the base class, and we will see how to do that in the next chapter. Susan wanted to know the difference between these two reasons for using inheritance. Susan: I just don’t get what inheritance for extension is. Steve : W e l l , t h e r e a r e t w o d i f f e r e n t r e a s o n s t o c r e a t e a n e w c l a s s by inheritance. The first reason is that the old class can’t do everything that we want it to do because it is missing some methods that we need. In that case, our new class will have new methods to provide those new features that we need. Clearly, this means that we will need to distinguish the new class from the old in our programs, because the old ______________________________________________________________________________ 2. As elsewhere in this book, the user of a class means the application programmer who is using objects of the class to perform work in his or her program, not the "end user" who is using the finished program. Problem Algorithms Java Interpreter Hardware 262 Chapter 7: STOCKING UP class doesn’t have the features we want. This is "inheritance for extension". The second reason to create a new class by inheritance is that we like the features of the old class but we need to implement them somewhat differently to handle a new situation. In that case, we don’t need to add new methods to the derived class; all we need to do is to re-implement the same methods differently. In this case, we can view the old and new classes as interchangeable in our application program. This is "inheritance for re-implementation." Problem Algorithms Java Interpreter Hardware Before we can substitute objects of one class for those of another one, we’ll need to learn how to create a new class by extending an existing class such as the StockItem class. There are a few minor changes between the final version of the StockItem class from Chapter 6 and the first one in this chapter (code\Itemtst20\StockItem.java) that we should get out of the way before moving on. I’ve renamed the method Display to FormattedDisplay, which is more descriptive, and added a new method called R e o r d e r a nd a couple of new fields called m_MinimumStock and m_MinimumReorder, whose purpose we’ll see shortly. Of course, the arguments to the "normal" constructor have been updated to reflect these two new fields. Figure 7.1 shows the new class definition file. Susan had some questions about this program. Susan: Why do you add a + to the code in the FormattedDisplay method? Steve: Because we want to display both the name and the value of each variable. Susan: Yes, I know, but I forgot how that is done. Steve: We want to tell the user not only the name, price, and so on for each StockItem, but which field is which. Therefore, we have to write a title for each field as we’re displaying it. The title of the field is a literal String (e.g., "Name"), whereas the value of the field is the contents of a String variable (e.g., the contents of m_Name). Thus, to display the values along with titles, you need both the literal String and the contents of the field. That is, you have to write something like System.out.println("Name: " + m_Name);. The "+" means "stick the value of the second expression onto the end of the value of the first expression", so if the value of m_Name is "Chunky Chicken", the whole line will be "Name: Chunky Chicken". UNDER CONTROL 263 import WAJ.*; import java.io.*; class StockItem { private int m_InStock; private int m_Price; private int m_MinimumStock; private int m_MinimumReorder; private String m_Name; private String m_Distributor; private String m_UPC; StockItem() { m_InStock = 0; m_Price = 0; m_MinimumStock = 0; m_MinimumReorder = 0; m_Name = ""; m_Distributor = ""; m_UPC = ""; } StockItem(String Name, int InStock, int Price, int MinimumStock, int MinimumReorder, String Distributor, String UPC) { m_InStock = InStock; m_Price = Price; m_MinimumStock = MinimumStock; m_MinimumReorder = MinimumReorder; m_Name = Name; m_Distributor = Distributor; m_UPC = UPC; } void FormattedDisplay(OutputStream s) { RWVar.println(s,"Name: " + m_Name); RWVar.println(s,"Number in stock: " + m_InStock); RWVar.println(s,"Price: " + m_Price); RWVar.println(s,"Minimum stock: " + m_MinimumStock); RWVar.println(s,"Minimum reorder: " + m_MinimumReorder); RWVar.println(s,"Distributor: " + m_Distributor); RWVar.println(s,"UPC: " + m_UPC); } Figure 7.1: The latest StockItem class (code\Itemtst20\StockItem.java) Problem Algorithms Java Interpreter Hardware 264 Chapter 7: STOCKING UP boolean Read(InputStream s) { DataInputStream dis = new DataInputStream(s); String tempInStock; String tempPrice; String tempMinimumStock; String tempMinimumReorder; try { m_Name = dis.readLine(); tempInStock = dis.readLine(); tempPrice = dis.readLine(); tempMinimumStock = dis.readLine(); tempMinimumReorder = dis.readLine(); m_Distributor = dis.readLine(); m_UPC = dis.readLine(); } catch (IOException e) { System.out.println(e); return false; } Problem Algorithms Java Interpreter Hardware if (m_UPC == null) return false; m_InStock = Integer.parseInt(tempInStock); m_Price = Integer.parseInt(tempPrice); m_MinimumStock = Integer.parseInt(tempMinimumStock); m_MinimumReorder = Integer.parseInt(tempMinimumReorder); return true; } void Write(OutputStream s) { try { RWVar.println(s,m_Name); RWVar.println(s,m_InStock); RWVar.println(s,m_Price); RWVar.println(s,m_MinimumStock); RWVar.println(s,m_MinimumReorder); RWVar.println(s,m_Distributor); RWVar.println(s,m_UPC); } Figure 7.1 continued UNDER CONTROL 265 catch (IOException e) { System.out.println(e); } } boolean CheckUPC(String ItemUPC) { if (m_UPC.equals(ItemUPC)) return true; return false; } void DeductSaleFromInventory(int QuantitySold) { m_InStock -= QuantitySold; } int GetInventory() { return m_InStock; } String GetName() { return m_Name; } Problem Algorithms Java Interpreter Hardware int GetPrice() { return m_Price; } void Reorder(OutputStream s) { int ActualReorderQuantity; if (m_InStock < m_MinimumStock) { ActualReorderQuantity = m_MinimumStock - m_InStock; if (m_MinimumReorder > ActualReorderQuantity) ActualReorderQuantity = m_MinimumReorder; RWVar.print(s,"Reorder " + ActualReorderQuantity + " units of "); RWVar.print(s,m_Name + " with UPC " + m_UPC); RWVar.println(s," from " + m_Distributor); } } } Figure 7.1 continued 266 Chapter 7: STOCKING UP Claiming an Inheritance Now let’s work out the details of a particular extension to this inventory control program: calculating how much of each item has to be ordered to refill the stock. I’ve chosen the imaginative name ReorderItems for the method in the Inventory class that will perform this operation. The new ReorderItems method is pretty simple. Its behavior can be described as follows: For each element in the StockItem array in the Inventory object, call its method Reorder to generate an order if that StockItem object needs to be reordered. Of course, this analysis neglects a number of considerations that would be relevant in a real program; however, it has some resemblance to the real algorithm, which is good enough for our purposes. Figure 7.2 shows the implementation of the ReorderItems method in the Inventory class. void ReorderItems(OutputStream s) { int i; Problem Algorithms Java Interpreter } Hardware for (i = 0; i < m_StockCount; i ++) m_Stock[i].Reorder(s); Figure 7.2: The ReorderItems method for the Inventory class (from code\itemtst20\Inventory.java) That could hardly be much simpler: As you can see, it merely tells each StockItem element in the m_Stock array to execute its Reorder method. Now let’s see what that Reorder method needs to do: 1. 2. 3. Check to see if the current stock of that item is less than the desired minimum. If we are below the desired stock minimum, then order the amount needed to bring us back to the stock minimum, unless that order amount is less than the minimum allowable quantity from the distributor. In the latter case, order the minimum allowable reorder quantity. If we are not below the desired stock minimum, do nothing. CLAIMING AN INHERITANCE 267 To support this new Reorder method, we’ll use the new data items m_MinimumStock and m_MinimumReorder, which I added to the first version of the StockItem class definition in this chapter. As I’ve already mentioned, the normal constructor for a StockItem has two additional arguments to initialize these two new fields. Figure 7.3 shows the code for the Reorder method. void Reorder(OutputStream s) { int ActualReorderQuantity; if (m_InStock < m_MinimumStock) { ActualReorderQuantity = m_MinimumStock - m_InStock; if (m_MinimumReorder > ActualReorderQuantity) ActualReorderQuantity = m_MinimumReorder; RWVar.print(s,"Reorder " + ActualReorderQuantity + " units of "); RWVar.print(s,m_Name + " with UPC " + m_UPC); RWVar.println(s," from " + m_Distributor); } } Figure 7.3: The Reorder method for the StockItem class (from code\Itemtst20\StockItem.java) There’s nothing particularly difficult about this code, which implements the reordering algorithm in a very straightforward manner. 1. 2. 3. 4. If the number of units in stock is less than the minimum number desired, we calculate the number needed to bring the inventory back to the minimum. However, the number we want to order may be less than the minimum number that we are allowed to order; the latter quantity is specified by the variable m_MinimumReorder. If the value of m_MinimumReorder is more than the number we actually needed, then we have to substitute the minimum quantity for that previously calculated number. Finally, we display the order for the item. Of course, if we a l r e a d y h a v e e n o u g h u n i t s i n s t o c k , w e d o n’ t h a v e t o r e o r d e r anything, so we don’t display anything in that case. Problem Algorithms Java Interpreter Hardware 268 Chapter 7: STOCKING UP A Dated Approach Now we want to add one wrinkle to this algorithm: handling items that have expiration dates. This actually applies to a fair number of items in a typical grocery store, including dairy products, meats, and even dry cereals. To keep things as simple as possible, we’ll assume that whenever we buy a batch of some item with an expiration date, all of the items of that type have the same date. When we get to the expiration date of a given StockItem, we send back all of the items and reorder as though we had no items in stock. The first question to answer is how to store the expiration date. My first inclination was to use our old friend, the int, to store each date as a number representing the number of days from, for example, January 1, 1990 to the date in question. Since there are approximately 365.25 days in a year, the range of an int extends approximately 5 1/2 million years into the future, which is good enough for our purposes; perhaps by that time we’ll all be eating food pills that don’t spoil. However, storing a date as a number of days since a base date such as January 1, 1990 does require a means of translating from a "normal" date format like "September 4, 1995" into a number of days Problem from the base date, and vice versa. Owing to the peculiarities of our Algorithms G r e g o r i a n c a l e n d a r ( p r i m a r i l y t h e d i f f e r e n t n u m b e r s o f d a y s i n Java different months and the complication of leap years), this is not a Interpreter trivial matter, and is a distraction from our goal here. Is there a Hardware simpler way to store and use dates that will still allow us to determine whether a given date is before or after another date? As a matter of fact, there is. Suppose we represent a date as an int of the form YYYYMMDD, where YYYY is the year, MM is the month number, and DD is the day number within the month.3 Then we can compare two of these "date numbers" and be sure that the one with a lower numeric value will represent the earlier date. Why is this? Here’s the analysis: 1. Of two dates with different year numbers, whichever has the higher year number is the later date. Thus, if we compare two "date numbers" that have different year portions, the one with the higher year number will represent a later date. ______________________________________________________________________________ 3. As I mentioned in Chapter 4, we need to allocate 4 digits for the year to ensure that the program will still work after the end of 1999. A DATED APPROACH 2. 3. 269 Of two dates with the same year number but different month numbers, whichever has the higher month number is a later date. Of course, this is just the same reasoning as step 1. Of two dates having the same year and month numbers, whichever has the higher day number is later. Now all we have to do is figure out how to generate a date number, and we’re all set. Luckily, this is pretty easy; Figure 7.4 shows how to do this, using a static method. That’s just a method that we can call without having to specify an object that it should operate on. This should make sense because today’s date is the same for all objects.4 protected static int Today() { Date d = new Date(); int year; int day; int month; int TodaysDate; year = d.getYear()+1900; month = d.getMonth()+1; day = d.getDate(); TodaysDate = year * 10000 + month * 100 + day; return TodaysDate; } Figure 7.4: The Today method for the DatedStockItem class (from code\Itemtst21\DatedStockItem.java) To make sense of this method, you should know that the default constructor for Date creates a Date object that represents the current date. It’s also important to know that the year part of a Date object is returned as a number of years since 1900, the month as a number 0 through 11 (why not 1 through 12?), and the day as a number 1 through 31. So the first part of the algorithm just gets those values and adjusts them to make sense as part of an 8-digit "date number". Then we combine these individual parts as follows: 1. Multiplying the year by 10000 to produce a number with 4 trailing zeroes (to leave room for the month and day). ______________________________________________________________________________ 4. At least if we ignore relativistic effects, which we can do pretty safely in this situation. Problem Algorithms Java Interpreter Hardware 270 2. 3. Chapter 7: STOCKING UP Multiplying the month by 100 to produce a number with 2 trailing zeroes (to leave room for the day). Leaving the day alone. Finally, we add these three pieces to produce an 8-digit date number that we can store as an int and can compare to other date numbers to see which represents an earlier date. Apparently my explanation of static methods wasn’t clear enough for Susan. Susan: I am not sure exactly what static is. How is it different from void? Steve: It’s almost but not quite entirely unlike void. A void method is just one that doesn’t return a value. On the other hand, a static method is one that we can call without supplying an object. That is, unlike the Display method, where we have to say, for example, "item1.Display();", with a static method like Today, we can just say Today();. Susan: Okay, so void doesn’t return a value, and static doesn’t have an object to call, but does return something. Steve: A method can be both static and void (as main is). In that case, you Problem don’t need an object to call it, and it doesn’t return anything. Algorithms Java Interpreter Now that we’ve figured out that we can store the expiration date as an int, how do we arrange for it to be included in the StockItem object? Hardware One obvious solution is to make up a new class called, say, DatedStockItem, by copying the definition of StockItem, adding a new field m_Expires, and modifying the copied Reorder method to take the expiration date into account. However, doing this would create a maintenance problem when we had to make a change that would affect both of these classes; we’d have to make such a change in two places. Just multiply this nuisance by ten or twenty times, and you’ll get a pretty good idea of how program maintenance has acquired its reputation as difficult and tedious work. Susan had some questions about the idea of program maintenance. Susan: What kind of change would you want to make? What is maintenance? What is a typical thing that you would want to do to some code? Steve: Any kind of change. For example, if we decided that an int wasn’t the right kind of variable to hold the price, then we would have to change its definition to some other type. A DATED APPROACH 271 Since one of the purposes of object-oriented programming is to reduce the difficulty of maintenance, surely there must be a better way to create a new class "just like" StockItem but with an added field and a modified method to use it. Ancestor Worship In fact, there is; it’s called inheritance. We can define our new class called DatedStockItem with a notation that it inherits (or derives) from StockItem. This makes StockItem the base class (sometimes referred to as the superclass), and our new class DatedStockItem the derived class (sometimes referred to as the subclass). By doing this, we are specifying that a DatedStockItem includes every data member and regular method that a StockItem has. Since DatedStockItem is a separate class from StockItem, we can also add whatever other methods and data we need to handle the differences between StockItem and DatedStockItem. I think a picture might help here. Let’s start with a simplified version of the StockItem class, illustrated in Figure 7.5. Variable name m_Name m_InStock m_MinimumStock Types and contents of fields in StockItem object ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ String ³ ³ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿³ ³³ "12 oz. soda" ³³ ³ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ int ³ ³ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿³ ³³ 10 ³³ ³ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ int ³ ³ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿³ ³³ 20 ³³ ³ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 7.5: A StockItem object Problem Algorithms Java Interpreter Hardware 272 Chapter 7: STOCKING UP A simplified version of a DatedStockItem object might look as depicted in Figure 7.6. Variable name Types and contents of fields in DatedStockItem object ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ StockItem ³ ³ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿³ ³³Variable name Type and contents ³³ ³³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿³³ ³³ ³ String ³³³ ³³ ³ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿³³³ ³³m_Name ³³ "16 oz. soup" ³³³³ base class part³³ ³ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ³³³ (unnamed) ³³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´³³ ³³ ³ int ³³³ ³³ ³ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿³³³ ³³m_InStock ³³ 15 ³³³³ ³³ ³ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ³³³ ³³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´³³ ³³ ³ int ³³³ ³³ ³ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿³³³ ³³m_MinimumStock³³ 35 ³³³³ ³³ ³ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ³³³ ³³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ³³ ³ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ Problem ³ int ³ Algorithms ³ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Java m_Expires ³³ 19960125 ³ ³ ³ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ Interpreter ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Hardware Figure 7.6: A DatedStockItem object As you can see, an object of the new DatedStockItem class effectively contains a StockItem as part of its data. In fact, this "base class part" of a DatedStockItem accounts for most of the data of a DatedStockItem; all we’ve added is a data member called m_Expires.5 In fact, a derived class object always contains all of the variables and "regular" methods in the base class because the derived class object effectively has an object of the base class embedded in it, as indicated in Figure 7.6.6 We can access those fields and methods that ______________________________________________________________________________ 5. This diagram and all the others that depict object layouts are purely schematic; there’s no guarantee that an object looks anything like these pictures. However, objects behave pretty much as though they did look this way, which is good enough for our purposes. 6. The reason that I prefer the terms base class and derived class to superclass and subclass, respectively, is that a derived class contains at least all the fields and methods of the base class and usually contains at least one added field or method. The terms sub and super generally indicate "less" and "more", respectively, so I find it confusing to call the c l a s s w i t h l e s s functionality the superclass and the one with greater functionality the subclass. ANCESTOR WORSHIP 273 are part of the base class part of our derived class object exactly as though they were defined in the derived class object, so long as their access specifiers are either public or protected. Although the public and private access specifiers have been part of our arsenal of tools for some time, we haven’t used protected before; we’ll see shortly that the sole purpose of the protected access specifier is to allow methods of a derived class unfettered access to methods and variables of the base c l a s s p a r t o f a n o b j e c t o f t h a t d e r i v e d class , w h i l e p r o t e c t i n g t h o s e methods and variables from use by unrelated classes. Of course, as noted above, we don’t have to rely solely on the f a c i l i t i e s w e i n h e r i t f r o m o u r b a s e c l a s s; we can also add whatever new methods or variables we need to provide the new functionality of the new class. As you will see, we don’t want or need to add any public methods in the present case, because our goal is to allow the application programmer to treat objects of the new DatedStockItem class as being equivalent to objects of the StockItem class. In order to reach this goal, these two classes must have the same behavior, which means that they must have the same public methods. I n s t e a d o f a d d i n g n e w public m e t h o d s , w e w i l l o v e r r i d e t h e b a s e c l a s s version of Reorder by writing a new version of R e o r d e r f o r o u r DatedStockItem class. Our new method, which has the same signature as the base class method Reorder, will use the new data member m_Expires. Since the base class version of Reorder has been overridden by the derived class Reorder method, the latter method will be called whenever the user’s program calls the Reorder method of a DatedStockItem. Why do we want to override the base class method Reorder rather than writing an entirely new method with a new name? Precisely because our goal is to allow the user to be able to use stock items with and without dates interchangeably; if StockItem and DatedStockItem had different names for their reordering method, then the user would have to call a different method depending on which type the object really was, which would defeat our attempt to make them interchangeable. Before we get into the details of the Reorder method in the DatedStockItem class, I should explain in more detail what I mean by "regular method". A regular method is one that is not a constructor. For example, Reorder(OutputStream s) is a regular method of the DatedStockItem class, whereas DatedStockItem() is a constructor and therefore not a regular method. If the concepts I’ve just introduced aren’t immediately obvious to you, you’re not alone. Here’s the discussion I had with Susan on this matter: Problem Algorithms Java Interpreter Hardware 274 Chapter 7: STOCKING UP Susan: Are inheritance and derivation the same thing? Steve: Yes. To say that A inherits from B is the same as saying that A is derived from B. Susan: Why don’t you want to add any methods to DatedStockItem? Steve: Because a DatedStockItem should act just like a StockItem; this won’t be the case if we add new methods that the class user can see. Instead of adding new public methods, we’ll write a new version of the Reorder method we already have. Susan: Huh? Why do you have to use a new version? It is supposed to act just like a StockItem. Steve: Yes, it is supposed to act "just like" a StockItem. However, that means that it has to do the "same" things differently; in particular, reordering items is different when you have to send things back because their expiration dates have passed. However, this difference in implementation is not important to the application program, which can treat DatedStockItems just like StockItems. Problem Algorithms Java Interpreter Hardware Susan: Why is the term "override" used here? The derived class method is called for an object of the derived class, so I don’t see how it "overrides" the base class method with the same signature. Steve: What would happen if we didn’t write the derived class method? Then the base class method would be called for a derived class object. Therefore the derived class method is overriding the previously existing base class method. Susan: The first thought that came to mind was, why can’t we just add some new methods and member variables to a class instead of making a derived class? So do you use inheritance here to make a point or is it vital to achieve what we want to achieve? Steve: If you added more methods, then StockItem would not be StockItem as it is and needs to be. You could write another class like StockItem which would be more labor intensive, and it would have to do everything that the original Reorder does and add the expiration dates. If you are going to use Java properly, then it is vital to use inheritance to avoid these problems. Susan: Is it the expiration date which makes it necessary to make a derived class? Steve: Yes. ANCESTOR WORSHIP 275 Susan: Explain again how the regular method is not a constructor. Steve: I’m not sure what you mean by this question. A "regular" method is simply any method that isn’t a constructor. For example, Display is a regular method. Susan: Then I am confusing a method with a constructor. Look at your definition for a constructor in the glossary. It says, "A constructor is a method that creates new objects..." Steve: Yes, and a Lexus is an automobile. Does that mean that every automobile is a Lexus? A constructor is a particular kind of method: the kind that makes a new object of a given class. Other methods, which don’t create new objects, aren’t constructors: Those are the "regular" methods I’ve been talking about. Susan: The nomenclature doesn’t make sense. If constructors are methods then regular methods should be called non-constructors, or constructors should be called irregular methods. I am confused by the wording. Steve: S o r r y , I d i d n’t make it up. There are two kinds of methods, constructors and "other". Since there are generally more "other" methods than constructors, we refer to the "other" methods as "regular". In other words, when we write a derived class (in this case, DatedStockItem), it does not inherit the constructors from the base class (in this case, StockItem); instead, we have to write our own c o n s t r u c t o r s f o r t h e d e r i v e d c l a s s, if we don’t want to rely on the compiler-generated versions in the derived class. Why is this? It’s because base class constructors are called automatically to construct the base class part of the derived class object; therefore, all w e h a v e t o w o r r y a b o u t i n o u r d e r i v e d c l a s s is handling the newly added parts of that class. We’ll see exactly how and when these base class constructors are called as we go through the corresponding derived class methods. Susan didn’t buy this idea of having to write our own constructors for the derived class. Susan: What is a compiler-generated version and why is it bad? Steve: It’s the version of the constructor generated by the compiler. It’s not always bad, but sometimes we can’t use it, for one of two reasons: First, because the compiler doesn’t know which base class constructor we want to call to initialize the base class part of the derived class object. Problem Algorithms Java Interpreter Hardware 276 Chapter 7: STOCKING UP Second, because the compiler doesn’t know how we want to initialize the new fields we’ve added in creating the derived class. Susan: What if your derived class needs a regular constructor, what do you do then? Steve: You write one that does the right thing, as we do with DatedStockItem in this chapter. Susan: But if the base class constructors are similar to the derived class ones, couldn’t you use them? Steve: We actually do use the base class constructors indirectly; the compiler will always call a base class constructor when a derived class constructor is executed. However, any new member variables added to the derived class will have to be handled in the derived class constructors. Susan: But why can’t you ever use the base class constructors as they are? Why do you always have to write derived class versions? Problem Algorithms Java Interpreter Hardware Steve: Because the new class will almost always have new variables. Clearly, the constructor for the base class cannot know what to do with a new variable added in the derived class; therefore, we will need a new constructor in the derived class. Susan: So anything not inherited that is added to a derived class then has to be handled as a separate entity than the stuff in the base class part of the derived class? UGH!!!! Steve: Yes, but just the new stuff has to be handled separately; whatever is inherited from the base class can be handled by the base class methods. After all, s o m e o n e has to write the code to handle the new member variables; the compiler can’t read our minds! However, there is one change that we have to make to our previous definition of StockItem to make it a suitable base c l a s s; we have to change the access specifiers for its fields from private to protected. By t h i s p o i n t , y o u s h o u l d b e f a m i l i a r w i t h t h e m e a n i n g o f private: Any fields or methods that are marked as private can be referred to only by methods of the same class, whereas all other methods are prohibited from accessing those private fields or methods. On the other hand, marking methods or member data of a class as public means that any method, whether or not a member of that class, can access those public methods or data. That seems to take care of all the possibilities, so what is protected good for? PROTECTION RACKET 277 Protection Racket Fields and methods that are listed as protected are treated the same as though they were private, with one important exception: Methods of derived classes can access these fields and methods when they occur as the base class part of a derived class object. Susan had a couple of questions about the notion of a base class part of a derived class object. Susan: What do you mean by "base class part of the derived class object"? I am fuzzy here. Steve: Every DatedStockItem (derived) object effectively contains an unnamed StockItem (base class) object, because DatedStockItem is derived from StockItem. This is what allows us to avoid duplicating the data and code from the base class in the derived class. Susan: If fields have names, why doesn’t the base class part? Steve: Because it is an inherent part of the derived class object. As such, you can access its fields, methods, and so on (assuming that they’re not private) as though they were part of the derived class object. In the current case, we’ve seen that a DatedStockItem is "just like" a StockItem with one additional field and some other additions and changes that aren’t relevant here. The important point is that every DatedStockItem contains everything that a StockItem contains; for example, every DatedStockItem has a m_MinimumStock field included in it, because the StockItem class has a m_MinimumStock field, and we ’re defining a DatedStockItem as being derived from StockItem. Logically, therefore, we should be able to access the value of the m_MinimumStock field in our DatedStockItem; however, if that field is declared as private, we can’t. The private access specifier doesn’t care about inheritance; since DatedStockItem is a different class from StockItem, private fields and methods of StockItem wouldn’t be accessible to methods of DatedStockItem, even though the fields of StockItem are actually present in every DatedStockItem! That’s why we have to make those variables protected rather than private. It wasn’t obvious to Susan why we need protected at all. Susan: I don’t understand why if DatedStockItem has those member variables it would not be able to access them if they were specified as private in the base class. Problem Algorithms Java Interpreter Hardware 278 Chapter 7: STOCKING UP Steve: Because the compiler wouldn’t let DatedStockItem methods access them; private variables are private even if they are part of the base class part of a derived class object. That’s why protected was invented in the first place. Now that we’ve have cleared up that point (I hope), we have to consider the question of when to use protected versus private fields. Because private fields of the base class cannot be accessed directly by derived class methods, we have to decide whether we want any derived classes to be able to access fields of the base class part of the derived class object when we define the base class; if we do, we have to use the protected access specifier for those fields. If we don’t do that and later discover that we need access to those variables in a derived class, then we will have to change the definition of the base class so that the variables are protected rather than private. Such changes are not too much trouble when we have written all of the classes involved, but they can be extremely difficult or even impossible when we are trying to derive new classes from previously existing classes written by someone else. However, protected variables and methods have some of the drawbacks of public variables and methods, because anyone can Problem derive a new class that uses those variables or methods. Once they Algorithms have done that, any changes to those variables or methods will cause Java their code to break. Hence, making everything protected isn’t an Interpreter unalloyed blessing. Hardware Another possibility is to use protected methods to allow access to private fields in the base class part, rather than using protected fields for the same purpose. I haven’t investigated this approach enough to render a definitive opinion, but at first glance it appears to provide the advantages of protected variables while being less likely to cause maintenance problems down the road. The moral of the story is that it’s easier to design classes for our own use and derivation than for the use of others; even though we could go back and change our class definitions to make them more flexible, that alternative may not be available to others. The result may be that our classes will not meet their needs. Apparently this explanation wasn’t as comprehensible as I thought it was. Susan: I don’t get your moral of the story. Sorry. Steve: The moral is that when designing classes that may be used by o t h e r s a s b a s e c l a s ses, we have to know whether t h e y w i l l e v e r n e e d access to our fields. If we are in charge of all of the class es, we can STOCK FOOTAGE 279 change the access specifiers easily enough, but that’s not a very good solution if someone else is deriving new classes from our classes. Stock Footage Because the next version of StockItem is identical to the previous version except for changing its fields from private to protected, I won’t waste the space on reproducing the whole class definition: instead, Figure 7.7 shows the changed field definitions. protected int m_InStock; protected int m_Price; protected int m_MinimumStock; protected int m_MinimumReorder; protected String m_Name; protected String m_Distributor; protected String m_UPC; Figure 7.7: protected fields in StockItem (from code\itemtst21\StockItem.java) After clearing that up, let’s look at the class definition of DatedStockItem, shown in Figure 7.8.7 import WAJ.*; import java.io.*; import java.util.*; class DatedStockItem extends StockItem { protected int m_Expires; DatedStockItem(String Name, int InStock, int Price, int MinimumStock, int MinimumReorder, String Distributor, String UPC, String Expires) { super(Name,InStock,Price,MinimumStock, MinimumReorder,Distributor,UPC); m_Expires = Integer.parseInt(Expires); } Figure 7.8: Implementation for DatedStockItem (code\Itemtst21\DatedStockItem.java) ______________________________________________________________________________ 7. I know there are some constructs in this file that haven’t been covered yet, such as super, but rest assured that we’ll get to them shortly. Problem Algorithms Java Interpreter Hardware 280 Chapter 7: STOCKING UP DatedStockItem() { m_Expires = 0; } protected static int Today() { Date d = new Date(); int year; int day; int month; int TodaysDate; year = d.getYear()+1900; month = d.getMonth()+1; day = d.getDate(); TodaysDate = year * 10000 + month * 100 + day; return TodaysDate; } void Reorder(OutputStream s) Problem Algorithms { if (m_Expires < Today()) Java { Interpreter RWVar.print(s,"Return " + m_InStock + " units of "); Hardware RWVar.print(s,m_Name + " with UPC " + m_UPC); RWVar.println(s, " to " + m_Distributor); m_InStock = 0; } super.Reorder(s); } void FormattedDisplay(OutputStream s) { RWVar.println(s, "Expiration Date: " + m_Expires); super.FormattedDisplay(s); } Figure 7.8 continued STOCK FOOTAGE 281 boolean Read(InputStream s) { DataInputStream dis = new DataInputStream(s); String tempExpires; try { tempExpires = dis.readLine(); } catch (IOException e) { System.out.println(e); return false; } super.Read(s); if (m_UPC == null) return false; m_Expires = Integer.parseInt(tempExpires); return true; } void Write(OutputStream s) { try { RWVar.println(s,m_Expires); } catch (IOException e) { System.out.println(e); } super.Write(s); } } Figure 7.8 continued Most of this should be pretty familiar by now. However, there are a couple of constructs here that we haven’t seen before. The first one is in the class header: DatedStockItem extends StockItem. I’m referring Problem Algorithms Java Interpreter Hardware 282 Chapter 7: STOCKING UP specifically to the expression extends StockItem, which states that this class, DatedStockItem, is derived from StockItem. Before we get into the implementation of the DatedStockItem class, let’s take a look back at the other new feature of the Today method (Figure 7.4): the notion of a static method. Getting static The static modifier means that we don’t have to specify an object for the method to apply to. Thus, we can refer to the static method called Today by just its name. Within DatedStockItem methods, that means writing "Today();" is sufficient; if we wanted to call Today() from a method of another class, we would have to refer to it by its full name: DatedStockItem.Today().8 This is clearly different from the normal use of a method, where we specify the method along with the object to which it applies; for example, soup.GetInventory();. What is the advantage of a static method? The advantage is precisely that we can call it without needing an o b j e c t t o c a l l i t f o r . I n t h i s c a s e , t h e v a l u e o f t o d a y’s date is not dependent on any DatedStockItem object; therefore, it’s quite handy to Problem Algorithms b e a b l e t o c a l l T o d a y ( ) w i t h o u t r e f e r r i n g t o a n y o b j e c t o f t h e DatedStockItem class. Java Interpreter There’s one other thing here that we haven’t seen before: Today() is a protected method, which means that it is accessible only to methods Hardware of DatedStockItem and descendants of DatedStockItem, just as a protected field is. The reason that we want to keep this method from being called by application programs is the same reason that we protect fields by restricting access: to reserve the right to change its name, return value, or argument types. Since application code can’t access this method, they can’t depend on its interface. Now that we’ve taken care of the new method Today, let’s take a look at the other methods of the DatedStockItem class that differ significantly from their counterparts in the base class StockItem; these are the constructors, the Reorder method, and FormattedDisplay. Let’s start with the default constructor, which of course is called DatedStockItem() (Figure 7.9). It’s a very short method, but there’s a bit more here than meets the eye. ______________________________________________________________________________ 8. The . notation here is analogous to the use of the . to specify a reference and a method or field of that reference in an expression such as item1.Display(). In such cases, the type of the object to which the reference refers is sufficient to determine which method should be called; in the case of a static method, however, there is no reference to help with this determination. Therefore, we have to specify the class to which the method belongs explicitly. GETTING STATIC 283 DatedStockItem() { m_Expires = 0; } Figure 7.9: Default constructor for DatedStockItem (from code\Itemtst21\DatedStockItem.java) A very good question here is "what happens to the base class part of the object?" This is taken care of by the default constructor of the StockItem class, which will be invoked by default to initialize that part of this object. The following is a general rule: Any base class part of a derived class object will automatically be initialized when the derived class object is created at run time, by calling a base class constructor. By default, the base class default constructor will be called when we don’t specify which base c l a s s constructor we want to execute. In other words, the code in Figure 7.9 is translated by the compiler as though it were the code in Figure 7.10. DatedStockItem() { super(); m_Expires = 0; } Figure 7.10: How the compiler interprets the default constructor for DatedStockItem (from code\Itemtst21\DatedStockItem.java) The statement super(); specifies which base class constructor we want to use to initialize the base class part of the DatedStockItem object. In this case, we’re calling the default constructor for the base class, StockItem. You probably won’t be surprised to hear that Susan had some questions about this idea of calling the base class constructor. Susan: Would super stand for supersede? Steve: Good guess, but it actually stands for "superclass", which is the official Java term for "base class". Susan: I don’t understand your "good question". What do you mean by base class part? Steve: The base class part is the embedded base class object in the derived class object. Problem Algorithms Java Interpreter Hardware 284 Chapter 7: STOCKING UP Susan: So derived classes use the default constructor from the base classes? Steve: They always use some base class constructor to construct the base class part of a derived class object. By default, they use the default constructor for the base class object, but you can specify which base class constructor you want to use. Susan: If that is so, then why are you writing one for DatedStockItem? Steve: Because the base class constructor only constructs the base class part. The rest of the derived class object has to be constructed too, and that job is handled by the derived class constructor. Susan: Let me see if I can get this straight. The derived class will use a base class default constructor unless you specify otherwise. Steve: Correct so far. Susan: Under what circumstances wouldn’t the base class constructor be called? Problem Algorithms Java Interpreter Hardware Steve: Some base class constructor will always be called to initialize the base class part; the only question is which constructor is to be called, and what arguments should be used to call it. We can use super to select the constructor and to specify the arguments. Susan: It just doesn’t "know" to go to the base class as a default unless you tell it to? Steve: No, it always goes to the base class; the question is which base class constructor is called. Susan: Ugh. Put this super thing down as necessary to let the derived class know which constructor you are using. This is not clear. Steve: Okay, here’s another way of stating the same point: If you don’t want the base class part to be initialized by the base class default constructor, you have to use the super keyword to specify which base class constructor you want to use. Whether we allow the compiler to call the default base class constructor automatically, as in Figure 7.9, or explicitly specify that it’s the one we want, as in Figure 7.10, the path of execution for the default DatedStockItem constructor is as illustrated in Figure 7.11. GETTING STATIC 285 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ DatedStockItem() ³ ³ { ³ ³ super() ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄ¿( 1 ) ÚÄÅÄÄÄ¿ ³ ³ ³ ³ ² ³ ³ ³ ³ m_Expires = 0; (4) ³ ³ ³ ³ } ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ÚÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ² ³ ³ ³ StockItem() ³ (2) ³ ³ { ³ ³ ³ m_InStock = 0; ³ ³ ³ m_Price = 0; ³ ³ ³ m_MinimumStock = 0; ³ ³ ³ m_MinimumReorder = 0; ³ ³ ³ m_Name = ""; ³ ³ ³ m_Distributor = ""; ³ ³ ³ m_UPC = ""; ³ ÀÄÅÄÄ} ³ (3) ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 7.11: Constructing a default DatedStockItem object At step (1) the DatedStockItem calls the default constructor for StockItem, which starts in step (2) by initializing all the variables in the StockItem class to their default values. Once the default constructor for StockItem is finished, in step (3), it returns to the DatedStockItem constructor. In step (4), that constructor finishes the initialization of the DatedStockItem object by initializing m_Expires to the default String value. This is fine as long as the base class default constructor is the one we want to use to construct the base class part of the DatedStockItem object. However, if that constructor doesn’t do what we want, we can specify which base constructor we wish to call, as shown in the "normal" constructor for the DatedStockItem class (Figure 7.12). Susan wanted to know why the base class constructor wouldn’t do what we wanted. Susan: Where you say, "if that constructor doesn’t do what we want", why wouldn’t it? I don’t get the negative part of this. Steve: Because there can be more than one base class constructor. The default one is only appropriate when we don’t know the data for the base class object, as is the case in our derived class default constructor. With our derived class "normal" constructor, we do know the data for the Problem Algorithms Java Interpreter Hardware 286 Chapter 7: STOCKING UP base class object and therefore have to call the "normal" constructor for the base class rather than the default constructor. DatedStockItem(String Name, int InStock, int Price, int MinimumStock, int MinimumReorder, String Distributor, String UPC, String Expires) { super(Name,InStock,Price,MinimumStock, MinimumReorder,Distributor,UPC); m_Expires = Integer.parseInt(Expires); } Figure 7.12: Normal constructor for DatedStockItem (from code\Itemtst21\DatedStockItem.java) As before, we have to use super to specify the base class constructor. For the "normal" DatedStockItem constructor, we use the statement Problem Algorithms Java Interpreter Hardware super(Name, InStock, Price, MinimumStock, MinimumReorder, Distributor, UPC); to call the corresponding "normal" base class constructor to initialize the base class part of the DatedStockItem object. This means that the StockItem part of the DatedStockItem object will be initialized exactly as though it were being created by the corresponding constructor for StockItem. Figure 7.13 illustrates how this works. At step (1) the DatedStockItem "normal" constructor is entered. In step (2), that constructor calls the "normal" constructor for StockItem, which constructs the base class part of a DatedStockItem exactly as though it had been created by calling the StockItem constructor with the arguments Name, InStock, Price, MinimumStock, MinimumReorder, Distributor, and UPC. These arguments, of course, are the corresponding arguments of the DatedStockItem constructor, so in effect we are just passing them through to the base class constructor. Once the "normal" constructor for StockItem is finished, in step (3), it returns to the DatedStockItem constructor. In step (4), that constructor finishes the initialization of the DatedStockItem object by initializing m_Expires to the value of the argument Expires. GETTING STATIC 287 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ DatedStockItem(String Name, ³ ³ int InStock, ³ ³ int Price, ³ ³ int MinimumStock, ³ ³ int MinimumReorder, ³ ³ String Distributor, ³ ³ String UPC, ³ ³ String Expires) ³ ³ { ³ ³ super(Name,InStock,Price,MinimumStock, ³ ³ MinimumReorder,Distributor,UPC); ÄÄÄÄÄÄÄÄÄÄÅÄÄ¿( 1 ) ÚÄÅÄÄÄ¿ ³ ³ ³ ³ ² ³ ³ ³ ³ m_Expires = Integer.parseInt(Expires); ( 4 ) ³ ³ ³ ³ } ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ÚÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ² ³ ³ ³ StockItem(String Name, ³ ³ ³ int InStock, ³ ³ ³ int Price, ³ ³ ³ int MinimumStock, ³ ³ ³ int MinimumReorder, ³ ³ ³ String Distributor, ³ ³ ³ String UPC) ³ (2) ³ ³ { ³ ³ ³ m_InStock = InStock; ³ ³ ³ m_Price = Price; ³ ³ ³ m_MinimumStock = MinimumStock; ³ ³ ³ m_MinimumReorder = MinimumReorder; ³ ³ ³ m_Name = Name; ³ ³ ³ m_Distributor = Distributor; ³ ³ ³ m_UPC = UPC; ³ ÀÄÅÄÄ} ³ (3) ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 7.13: Constructing a DatedStockItem object As you can see by these examples, using super allows us to call the base class constructor to initialize the base class object, which in turn means that our derived class constructor won’t have to keep track of the details of the base class. This is an example of one of the main benefits claimed for object-oriented programming: We can confine the knowledge of the details of a class to the methods of that class, which simplifies maintenance efforts. In this case, after calling the base class constructor via super, the only task left for the DatedStockItem constructor is to initialize the field m_Expires. Susan had some questions about exactly how this works. Problem Algorithms Java Interpreter Hardware 288 Chapter 7: STOCKING UP Susan: How does all the information of (3) get into (4)? And exactly what part of the code here calls the base class constructor? I don’t see which part it is. Steve: The information from (3) gets into the derived class DatedStockItem object in the upper part of the diagram because the DatedStockItem object contains a base class part consisting of a StockItem object. That’s the object that is being initialized by the call to the base class constructor caused by the super keyword in the following line: super(Name, InStock, Price, MinimumStock, MinimumReorder, Distributor, UPC); There’s another reason that we should use super rather than trying to initialize the fields of the base class part of our object directly: It’s much safer. If we initialized the base class variables ourselves, and if the base class definition were later changed to include some new variables initialized according to arguments to the normal constructor, we might very well neglect to modify our derived class code to initialize the new variables. On the other hand, when we use super to call a base class constructor, and its arguments change (as they presumably will if new variables need to be initialized), a derived class constructor that calls that constructor will no longer compile. That will alert us to the changes that we have to make. Problem Algorithms Java Interpreter Reordering Priorities Hardware Now that we have dealt with the constructors, let’s take a look at the Reorder method (Figure 7.14). void Reorder(OutputStream s) { if (m_Expires < Today()) { RWVar.print(s,"Return " + m_InStock + " units of "); RWVar.print(s,m_Name + " with UPC " + m_UPC); RWVar.println(s, " to " + m_Distributor); m_InStock = 0; } super.Reorder(s); } Figure 7.14: Reorder method for DatedStockItem (from code\Itemtst21\DatedStockItem.java) REORDERING PRIORITIES 289 We have added some new code that checks whether the expiration date on the current batch of product is before today’s date; if so, we create an output line indicating that the batch is to be returned. But what about the "normal" case for which the base class Reorder method is perfectly suitable? That’s handled by the line super.Reorder(s);, which calls the Reorder method in the StockItem class by using the super keyword to specify that we want to call the Reorder method in our base class. If we just wrote Reorder(s), that would call the method that we’re in again, a process known as recursion. Recursion has its uses in certain complex programming situations, but we can do without it in this book. In this case, of course, it would not do what we wanted, as we have already handled the possibility of expired items; we need to deal with the "normal" case of running low on stock, which is handled very nicely by the base class Reorder method. We should not pass by this method without noting one more point; the only reason that we can access m_InStock and the other fields of the StockItem base class part of our object is that those fields of StockItem were declared protected rather than private; had they been declared private, we wouldn’t be able to access them in our DatedStockItem methods, even though every DatedStockItem object would still have such fields. Susan wanted a clarification of this point. Susan: I can’t picture the statement "even though every DatedStockItem object would still have such fields." I don’t get it. Steve: Well, every DatedStockItem has a StockItem base class part, and that base class part contributes its member variables to the DatedStockItem. What about F o r m a t t e d D i s p l a y ? T h e r e ’s nothing that should be very unfamiliar in that method, which is illustrated in Figure 7.15. void FormattedDisplay(OutputStream s) { RWVar.println(s, "Expiration Date: " + m_Expires); super.FormattedDisplay(s); } Figure 7.15: FormattedDisplay method for DatedStockItem (from code\Itemtst21\DatedStockItem.java) As in the case of Reorder, we handle the new field that we’ve added and defer the rest to the base class method via a super method call. Of course, if we just wrote that line as FormattedDisplay(s) rather than as Problem Algorithms Java Interpreter Hardware 290 Chapter 7: STOCKING UP super.FormattedDisplay(s), the DatedStockItem method called FormattedDisplay method would be called rather than the StockItem FormattedDisplay method, which wouldn’t work very well because that’s the same method that’s making the call! Now that we’ve seen all the pieces, let’s take a look at how they all fit together. Figure 7.16 shows the main program that reads DatedStockItems from a file and displays them. import WAJ.*; import java.io.*; public class Itemtst21 { public static void main( String args[ ] ) { FileInputStream ShopInfo; FileOutputStream ReorderInfo; Inventory MyInventory = new Inventory(); try { ShopInfo = new FileInputStream("shop21.in"); } catch (IOException e) { System.out.println("Can’t open file \"shop21.in\""); return; } Problem Algorithms Java Interpreter Hardware MyInventory.LoadInventory(ShopInfo); try { ReorderInfo = new FileOutputStream("shop21.reo"); } catch (IOException e) { System.out.println("Can’t open file \"shop21.reo\""); return; } MyInventory.ReorderItems(ReorderInfo); } } Figure 7.16: Test program for DatedStockItem (code\Itemtst21\itemtst21.java) To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the REORDERING PRIORITIES 291 CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Itemtst21". Now we have a good solution to the creation of stock items with dates. But what we originally wanted was to be able to mix StockItem and DatedStockItem objects in the same array. Have we accomplished that goal? As a matter of fact, we have. However, to understand exactly how this works, we’ll need to go into the depths of Java a bit further, which is the task of the next chapter. First, though, it’s time to review what we’ve learned in this chapter. Review We started out the chapter by adding some new methods to the StockItem class and the Inventory class that were missing from the final versions of these classes in Chapter 6, including a method called ReorderItem that can be called for an Inventory object to produce a reordering report. This method calls a Reorder method for each StockItem i n i t s S t o c k I t e m a r r a y t o c a l c u l a t e h o w m a n y i t e m s o f t h a t StockItem need to be ordered, based on the desired stock and the current stock. Then we built on the previous StockItem class by adding an expiration date. Rather than copying all of the old code and class definitions, we made use of a concept that is essential to the full use of Java for object-oriented programming, namely, inheritance. Inheritance is a method of constructing one class (the derived class) by specifying how it differs from another class (the base class ) , r a t h e r than writing it from scratch. We used inheritance to create a new DatedStockItem class that had all of the capabilities of the StockItem class and added the ability to handle items with expiration dates. In the process, we wrote a new Reorder method with the same signature as the base class method of the same name. This is called overriding the base class method. When the method with that signature is called via an object of the base class, the base class method will be called; on the other hand, when the method with that signature is called via an object of the derived class, the derived class method will be called. This allows a derived class to supply the same functionality as a base class but implement that functionality in a different way. The reason why a derived class object can do anything that a base class object can do is that a derived class object effectively contains an Problem Algorithms Java Interpreter Hardware 292 Chapter 7: STOCKING UP object of the base class, called the base class part of the derived class object.9 This base class part is very similar to a field in the derived class, but it is not the same, for two reasons: 1. 2. A field always has a name, whereas the base class part does not. The base class definition can give derived class methods privileged access to some fields and methods of the base class part of an object of the derived class, by marking those fields and methods protected. In the process of writing the new Reorder method for the DatedStockItem class, we saw how we could store a date as an int that allowed comparison of two dates to see which was later. To do this, we needed to create an eight-digit number representing the date as YYYYMMDD; that is, a four-digit year number, a two-digit month number, and a two-digit day number. After the discussion of creating these "date numbers", we continued by examining the default constructor of the DatedStockItem class. Although this is an extremely short method, consisting of a lone assignment statement, there is more to it than meets the eye. The default constructor deals only with the newly added field m_Expires, Problem Algorithms but behind the scenes, the base class part of the DatedStockItem object is being initialized by the default constructor of the base class, i.e., Java Interpreter StockItem(). The rule is that a base class constructor will always be called for the base class part of a derived class object; if we don’t Hardware specify which base class constructor we want to use, the default constructor for the base class will be used. If we do want to select the constructor for the base class part, we can do it by an explicit call to that base class c o n s t r u c t o r v i a t h e s u p e r k e y w o r d . I n o u r " n o r m a l " constructor for DatedStockItem, we used this construct to call the corresponding constructor for the base class (see Figures 7.12 and 7.13). Then we looked at the Reorder method for the DatedStockItem class, which included code to request the return of any items that were past their expiration date and called the base class Reorder method to handle the rest of the job, again using the super keyword; this time, we used it to specify the base class method rather than the same named method in DatedStockItem. At that point, we had a working DatedStockItem class, but we hadn’t investigated what happens if we mix StockItem and DatedStockItem ______________________________________________________________________________ 9. Whether or not a derived class object actually contains a base class object is an implementation detail that may vary from one Java compile to another; however, this is not visible to the programmer, who can proceed as though this is always the case. EXERCISES 293 objects in the same array; we’ll find out how that works in the next chapter. Exercises 1. 2. 3. Rewrite the DrugStockItem class that you wrote in Chapter 6, using derivation from the StockItem class. R e w r i t e t h e E m p l o y e e c l a s s that you wrote in Chapter 6 as two classes, the base Manager class and a Hourly class derived from the base class. The CalculatePay method for each of these classes should use the appropriate method of calculating the pay for each class; in particular, this method doesn’t need an argument specifying the number of hours worked for the Manager class, whereas the corresponding method in the Hourly class does need such an argument. Because the CalculatePay methods in these two classes don’t have the same arguments, objects of these classes cannot be treated as though they were of the same class; therefore, this is an example of inheritance for extension. W h a t h a p p e n s i f y o u c a l l t h e CalculatePay m e t h o d f o r a n H o u r l y object with a float argument? Why? Conclusion In this chapter we have extended the functionality provided in the StockItem class by deriving a new class called DatedStockItem, based on StockItem. However, we have not yet seen how to use objects of these two classes interchangeably. In Chapter 8, we will see exactly how to do that. Problem Algorithms Java Interpreter Hardware Chapter 8 Pretty Poly At the end of the previous chapter, we had created a DatedStockItem class by inheritance from the StockItem class, adding an expiration date field. This was a fine solution to the problem of creating a new class based on the existing StockItem class without rewriting all of the already functioning code in that class. As we will see in this chapter, these two classes can also be used interchangeably in an application program, which is an application of the third and final major organizing principle that characterizes object-oriented programming: polymorphism. Once we have defined some terms and objectives for this chapter, we’ll get right to the explanation of how polymorphism works. Definitions Static typing means determining the exact type of a data item when the program is compiled. Note that this has no particular relation to the keyword static. Dynamic typing means delaying the determination of the exact type of a data item until run time, rather than fixing that type at compile time as in static typing. Java uses dynamic typing for user-defined types and static typing for primitive types. Polymorphism is the major organizing principle in Java that allows us to implement several classes whose objects can be used interchangeably by the user of these classes. In Java, variables of user-defined types are polymorphic, because they can refer to objects of different classes at run time (dynamic typing). The word 295 296 Chapter 8: PRETTY POLY "polymorphism" is derived from the Greek words "poly", meaning "many", and "morph", meaning "form". In other words, the same behavior is implemented in different forms. Objectives for This Chapter By the end of this chapter, you should: 1. 2. U n d e r s t a n d h o w polymorphism a l l o w s o b j e c t s o f a n u m b e r o f different classes to be treated interchangeably by the user of these classes. Understand the Java mechanisms that allow polymorphism to work. Polymorphism To select the correct method to be called based on the actual type of an object at run time, Java uses polymorphism. Polymorphic Problem Algorithms behavior of our StockItem and DatedStockItem classes means that we can, for example, mix StockItem and DatedStockItem objects in an array and Java Interpreter have the right Reorder method be executed for each object in the array. Hardware Why would we want to do this? Because the objects of these two class e s p e r f o r m t h e s a m e o p e r a t i o n , a l t h o u g h i n a s l i g h t l y d i f f e r e n t way. In our example, a DatedStockItem acts just like a StockItem, except that it has an additional data field and produces different reordering information. Because neither of these differences significantly affects the way that the application program uses objects of these two classes, it is handy to be able to mix objects of these two types in the application program without having to worry about which class each object belongs to except when creating an individual item (at which time we have to know whether the item has an expiration date or not). When we create an array, we have to specify the type of the elements of that array. However, in the present case we want to be able to store a mixture of StockItem and DatedStockItem objects in that array. How do we specify the type of that array, when we don’t know the type of each element? If we specify the array to contain StockItem references, those references will be able to store objects of both of these types. The POLYMORPHISM 297 reason is that Java allows a derived class object to be assigned to a reference of its base class. Moreover, the exact method to be executed for any given method call will be determined by the actual type of the object to which the reference refers, rather than being based on the declared type of the reference. This implies that the selection of the method to be called is postponed to run time rather than being determined at compile time; clearly, if the actual run-time type of the object determines which version of the method is called, the compiler can’t select the method at compile time. Susan had some questions about polymorphism. Susan: Explain how polymorphism is different from inheritance. Recap that the three organizational principles of object-oriented programming are: 1. encapsulation 2. inheritance 3. polymorphism Steve: Sure. Encapsulation is just the idea that the user of a class shouldn’t have to know how it works, just what it does. Therefore, we should keep our fields private (or at least protected) so we can change them, delete them, or add new ones whenever we want. Inheritance is the definition of one class in terms of another, so that we don’t have to write the common code used by several classes over and over again. Instead, we can reuse existing code where it is appropriate and write new code only where we need to make the new class behave differently from the old one. Polymorphism is the idea that two or more classes can vary only in their implementation while presenting the same appearance to their users. In such a case, we want to be able to write our application programs so that we don’t care which class each object belongs to. For example, we might have an array of StockItem references, each of which might actually refer to either a StockItem or a DatedStockItem, and the application programmer can process each item in the same way without having to write special code to say, "if it’s a StockItem, do this; otherwise, if it’s a DatedStockItem, do this." Of course, objects of different types will have to execute different code sometimes, but by using polymorphism the compiler can determine which method to call rather than putting that burden on the application programmer. Susan: Why do you write a new version of Reorder instead of adding a new public method? Steve: So that the application program can treat StockItems and DatedStockItem s i n t h e s a m e w a y . I f t h e y h a d d i f f e r e n t m e t h o d n a m e s , then they wouldn’t seem the same to the user program. Problem Algorithms Java Interpreter Hardware 298 Chapter 8: PRETTY POLY Susan: But what if the two versions of Reorder were exactly the same, couldn’t you just declare them public? Steve: If they had different names, then the user program would still have to distinguish between them, which is what we’re trying to avoid. Susan: Yes, but if they were exactly the same and the names were the same, couldn’t they be used anywhere just by making them public? I thought that this was the whole idea not to have to rewrite these things. Steve: They are public. The point is that StockItem’s version of Reorder and DatedStockItem’s version of Reorder accomplish the same result in different ways, so the user of objects of either of these classes should be able to just call Reorder and get the correct method executed without having to worry about which one that is. Susan: I don’t get it. You say that both Reorders are exactly the same but then you say that they work in different ways. I don’t like that. Steve: They do the same thing, but in different ways, appropriate to the type of object they do it for. Problem Algorithms Java Interpreter Hardware Susan: Okay, I guess that makes sense. But what else do StockItem and DatedStockItem share besides the names of the methods? Steve: The derived class contains all of the fields and methods of the base class, along with others it adds. Susan: See, the word "derived" is confusing. If a DatedStockItem is derived from a StockItem, one tends to think in a linear approach as in a family tree, which isn’t quite right. It would be better to think of DatedStockItem as a fruit like a plum, with the pit being the StockItem that is the core of the object. Steve: Or maybe like a chocolate-covered cherry, with a fruit inside another kind of food, rather than a pit and an edible part. Susan: So is it correct to say that polymorphism is the same as interchangeable inheritance classes, otherwise inheritance is used side by side (i.e., either/or)? Steve: Yes, that’s correct. But exactly how does polymorphism help us with our Reorder method? Let’s take a look at what happens when we create objects of both StockItem and DatedStockItem classes, and access them through StockItem and DatedStockItem references, in Figure 8.1. POLYMORPHISM 299 import WAJ.*; public class Polytest { public static void main( String args[ ] ) { StockItem SI = new StockItem("soup",32,100,40, 15,"Bob’s Distribution","1234567890"); DatedStockItem DSI = new DatedStockItem("milk", 10,15,40,20,"Phil’s Distribution", "9876543210","19950110"); SI.Reorder(System.out); System.out.println(); DSI.Reorder(System.out); System.out.println(); SI = new DatedStockItem("steak",90,95,30, 20,"Sam’s Stuff","1212343456","19960110"); SI.Reorder(System.out); System.out.println(); } } Figure 8.1: Polymorphism test program (code\Polytest\Polytest.java) To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Polytest". Figure 8.2 shows the output of this test program. Reorder 15 units of soup with UPC 1234567890 from Bob’s Distribution Return 10 units of milk with UPC 9876543210 to Phil’s Distribution Reorder 40 units of milk with UPC 9876543210 from Phil’s Distribution Return 90 units of steak with UPC 1212343456 to Sam’s Stuff Reorder 30 units of steak with UPC 1212343456 from Sam’s Stuff Figure 8.2: Polymorphism test program output (code\Polytest\Polytest.out) Problem Algorithms Java Interpreter Hardware 300 Chapter 8: PRETTY POLY According to our rules, the correct answer is to return 90 units of steak and reorder 30, because the previous stock has expired. Therefore, the program is working correctly. But how can we call the DatedStockItem version of Reorder through a StockItem reference? Poly Anna Because when we call a method through a base class reference, the method that will be executed will be the one defined in the class of the actual object to which the reference points, not the one defined in the class of the reference.1 This means that the compiler can’t decide exactly which methods will be called for an object referred to by a StockItem, because the actual object may be a descendant of StockItem rather than an actual StockItem; in that case, we will want the method defined in the derived class (e.g., DatedStockItem) to be called even though the reference is declared to point to an object of the base class (e.g., StockItem). Since the actual type of the object for which we want to call the method isn’t available at compile time, another way must be found to Problem determine which method should be called. The most logical place to Algorithms store this information is in the object itself, because after all we Java already need to know where the object is located in order to call the Interpreter method for it. So whenever a method call is compiled, the compiler Hardware translates that call into instructions that use the information in the object to determine at run time which version of the method will be called.2 If every object needed to contain the addresses of all its methods, that might make objects a lot larger than they would otherwise have to be. However, this is not necessary, because all objects of the same class have the same methods. Therefore, the addresses of all of the methods for a given class are stored in a method address table, which we’ll call an mtable for short. Every object of a given class contains the address of the mtable for that class. As you might imagine, Susan had some questions about mtables. Susan: Are mtables customized for each class? ______________________________________________________________________________ 1. In case you would like a picture of this, there’s one coming up, in Figure 8.7. 2. Actually, there are times when the compiler can tell at compile time what the type of the object will have to be at run time; in that case, it can generate more efficient ways to call methods on that object. However, we can ignore this detail in our analysis, because it never makes any difference in the way our programs work. POLY ANNA 301 Steve: Yes. Susan: Where do they come from, how are they created, and how do they work? Steve: The compiler creates them based on the class definition, and all they do is store the addresses of the methods for that class, so that the compiler can generate code that will select the correct method for the object being referred to at run time. Susan: I don’t get this mtable stuff; why is it here again? Steve: To allow the program to find the correct Reorder method at run time. Susan: And does this just point the Reorder method in the proper direction at run time? Steve: Yes. Susan: Is the mtable in the object? Steve: No, the address of the mtable is in the object. Putting the whole mtable in the object would waste a lot of space if there were very many methods. Susan: How is an mtable made? Is it created only when there is a derived class? Steve: No, every class has an mtable associated with it, because that’s the usual way to find the methods for that class. Sometimes the compiler can figure out what methods are going to be called at compile time, but that isn’t true in general. Therefore, an mtable is required for every class. Susan: This stuff is beyond "UGH!"; it is just outrageous and I can’t believe that you understand it. Steve: It’s a dirty, lousy job, but somebody has to do it. Thus, a simplified version of a StockItem object, including its mtable, might look something like Figure 8.3.3 ______________________________________________________________________________ 3. Of course, all of the "memory addresses" in this and the succeeding figures are arbitrary; the real addresses are assigned by the interpreter when the program is executed. Problem Algorithms Java Interpreter Hardware 302 Chapter 8: PRETTY POLY StockItem soup Address Name 12340000 (mtable address) 12340004 12340006 12350000 Address 12351000 12352000 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ 12350000 ÃÄÄ¿ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ m_InStock ³ 0005 ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ m_MinimumStock ³ 0008 ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ StockItem mtable ³ ÚÄÄÄÄÄÄÄÄÄÄÄÙ ² ÚÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄ¿ Reorder address ³ 12352000 ÃÄÄ¿ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ StockItem methods ³ ³ Name ³ ³ StockItem(int, int) ³ ³ Reorder(OutputStream) ±ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 8.3: A simplified StockItem object Problem Algorithms Java Interpreter Hardware On the other hand, a simplified version of a DatedStockItem object might look like Figure 8.4. DatedStockItem milk Address Name 12330000 (mtable address) 12330004 12330006 12330008 12360000 Address 12361000 12362000 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ 12360000 ÃÄÄ¿ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ m_InStock ³ 0002 ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ m_MinimumStock ³ 0005 ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ m_Expires ³ 19960629 ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ DatedStockItem mtable ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÙ ² ÚÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄ¿ Reorder address ³ 12362000 ÃÄÄ¿ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ DatedStockItem methods ³ ³ Name ³ ³ DatedStockItem(int, int, String) ³ ³ Reorder(OutputStream) ±ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 8.4: A simplified DatedStockItem object POLY ANNA 303 G i v e n t h e s e p i c t u r e s o f S t o c k I t e m a nd DatedStockItem objects, let ’ s examine the behavior of three method call examples, one for each legal combination of references and objects. First, Figure 8.5 shows what happens when Reorder is called for a StockItem object through a StockItem variable named SI (short for "stock item"). ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ public static void main(String args[ ]) ³ ³ { ³ ³ StockItem SI; ³ ³ SI = new StockItem(5,8); ³ ÚÄÅÄ¿ SI.Reorder(System.out); ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄ¿ ³ ³ ² ³ ³(1) ³ ³ } ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ StockItem ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ Name ² ³ ÚÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄ¿ ³ (mtable address) ³ 12350000 ÃÄÄ¿ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ m_InStock ³ 0005 ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ m_MinimumStock ³ 0008 ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ÚÄÄÄÄÙ ³ StockItem mtable ² (2) ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄ¿ ³ Reorder address ³ 12352000 ÃÄÄ¿ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³(3) ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÚÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ² ³ ³ ³ Reorder(OutputStream s) ³ ³ ³ { ³ ³ ³ //code to reorder a StockItem ³ ÀÄÅÄÄ} ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 8.5: Calling the Reorder method of StockItem through a StockItem reference to a StockItem object Step (1) gets the mtable pointer from the StockItem object to which SI refers. Step (2) g e t s t h e a d d r e s s o f t h e Reorder method from that mtable. Then step (3) uses that address to call the StockItem version of Reorder. At the end of that method, it returns to the next statement in the main program. So the net result of the call illustrated in Figure 8.5 is that the StockItem version of Reorder is called, which is what we want in this situation. Next, Figure 8.6 shows what happens when Reorder is called for a DatedStockItem object through a DatedStockItem reference. Problem Algorithms Java Interpreter Hardware 304 Problem Algorithms Java Interpreter Hardware Chapter 8: PRETTY POLY ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ public static void main(String args[ ]) ³ ³ { ³ ³ DatedStockItem DSI; ³ ³ ³ ³ DSI = new DatedStockItem(2,5, ³ ³ "19960629"); ³ ³ ³ ÚÄÅÄ¿ DSI.Reorder(System.out); ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄ¿ ³ ³ ² ³ ³(1) ³ ³ } ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ DatedStockItem ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ Name ² ³ ÚÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄ¿ ³ (mtable address) ³ 12360000 ÃÄÄ¿ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ m_InStock ³ 0002 ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ m_MinimumStock ³ 0005 ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ m_Expires ³ 19960629 ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ DatedStockItem mtable ³ ³ ³(2) ³ ÚÄÄÄÄÄÄÄÄÄÄÄÙ ³ ² ³ ÚÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄ¿ ³ Reorder address ³ 12362000 ÃÄÄ¿ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³(3) ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÚÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ² ³ ³ ³ Reorder(OutputStream s) ³ ³ ³ { ³ ³ ³ //code to reorder a DatedStockItem ³ ÀÄÅÄÄ} ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 8.6: Calling the Reorder method of DatedStockItem through a DatedStockItem reference Step (1) gets the mtable pointer from the DatedStockItem object to which DSI refers. Step (2) gets the address of the Reorder method from that mtable. Then step (3) uses that address to call the DatedStockItem version of Reorder. At the end of that method, it returns to the next statement in the main program. So the net result of the call illustrated in Figure 8.6 is that the DatedStockItem version of Reorder is called, which is what we want in this situation. Finally, Figure 8.7 shows what happens when Reorder is called for a D a t e d S t o c k I t e m o b j e c t t h r o u g h a StockItem r e f e r e n c e . T h i s i s w h e r e polymorphism pays off, because we can use a base class reference to POLY ANNA 305 refer to a derived class object, and the correct method for that derived class object will be called anyway. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ public static void main(String args[ ]) ³ ³ { ³ ³ StockItem SI; ³ ³ ³ ³ SI = new DatedStockItem(2.5, ³ ³ "19960629"); ³ ³ ³ ÚÄÅÄ¿ SI.Reorder(System.out); ÄÄÄÄÄÄÄÄÄÄÄÅÄÄ¿ ³ ³ ² ³ ³(1) ³ ³ } ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ DatedStockItem ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ Name ² ³ ÚÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄ¿ ³ (mtable address) ³ 12360000 ÃÄÄ¿ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ m_InStock ³ 0002 ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ m_MinimumStock ³ 0005 ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ m_Expires ³ 19960629 ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ DatedStockItem mtable ³ ³ ³(2) ³ ÚÄÄÄÄÄÄÄÄÄÄÄÙ ³ ² ³ ÚÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄ¿ ³ Reorder address ³ 12362000 ÃÄÄ¿ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³(3) ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÚÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ² ³ ³ ³ Reorder(OutputStream s) ³ ³ ³ { ³ ³ ³ //code to reorder a DatedStockItem ³ ÀÄÅÄÄ} ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 8.7: Calling the Reorder method of DatedStockItem through a StockItem reference Step (1) gets the mtable pointer from the DatedStockItem object to which SI refers. Step (2) gets the address of the Reorder method from that mtable. Then step (3) uses that address to call the DatedStockItem version of Reorder. At the end of that method, it returns to the next statement in the main program. So the net result of the call illustrated in Figure 8.7 is that the correct method, DatedStockItem.Reorder, is called even though the type of the reference through which it is called is StockItem. Problem Algorithms Java Interpreter Hardware 306 Chapter 8: PRETTY POLY To translate this into what I hope is understandable English, the call to the Reorder method in any of these figures might be expressed as follows: 1. 2. 3. Get the mtable address from the object referred to by SI. Since Reorder is the first defined method, retrieve its address from the first method address slot in the mtable. Execute the method at that address. What happens if we add another method, say Write, to the StockItem class, after the Reorder method? The new method will be added to the mtables for both the StockItem and DatedStockItem classes. Susan had some questions about mtables. Susan: What do you mean by added to both mtables? Do StockItem and DatedStockItem each have their own? Steve: Yes. Problem Algorithms Java Interpreter Hardware Susan: If so, then why are the numbers of their addresses so similar? They are just different addresses of the same mtable? Steve: In the StockItem case, I’ve given the starting address as 12350000, and in the DatedStockItem case, as 12360000. These are the addresses I made up for the two mtables. Susan: How does the mtable get the address for the new StockItems? Steve: It’s the other way around. The constructor for each class has code added to it by the compiler to fill in the mtable address for the objects of that class. Therefore, each StockItem, when it is created by the constructor, has its mtable address filled in by that compiler-generated code. By following this sequence, you can see that while both versions of Reorder are referred to via the same relative position in the StockItem and DatedStockItem mtables, the particular version of Reorder that is executed will depend on which mtable the object points to. Since all objects of the same class have the same methods, all StockItem objects will point to the same StockItem mtable, and all DatedStockItem objects will point to the same DatedStockItem mtable. To make this more tangible, if we added another method to the StockItem class, its layout might look something like Figure 8.8. POLY ANNA 307 StockItem Address Name 12340000 (mtable address) 12340004 12340006 12350000 12350004 Address 12351000 12351800 12352000 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ 12350000 ÃÄÄ¿ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ m_InStock ³ 0005 ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ m_MinimumStock ³ 0008 ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÚÄÄÄÄÙ StockItem mtable ² ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄ¿ Reorder address ³ 12352000 ÃÄÄÄÄ¿ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ Write address ³ 12351800 ÃÄÄ¿ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ StockItem methods ³ ³ ³ ³ Name ³ ³ ³ ³ StockItem(int, int) ³ ³ ³ ³ Write() ±ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ Reorder(OutputStream) ±ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 8.8: A simplified StockItem object with two methods The situation for a DatedStockItem object might look like Figure 8.9. DatedStockItem Address Name 12330000 (mtable address) 12330004 12330006 12330008 12360000 12360004 Address 12361000 12361800 12362000 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ 12360000 ÃÄÄ¿ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ m_InStock ³ 0002 ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ m_MinimumStock ³ 0005 ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ m_Expires ³ 19960629 ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÚÄÄÄÄÙ DatedStockItem mtable ² ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄ¿ Reorder address ³ 12362000 ÃÄÄÄÄ¿ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ Write address ³ 12361800 ÃÄÄ¿ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ DatedStockItem methods ³ ³ ³ ³ Name ³ ³ ³ ³ DatedStockItem(int, int) ³ ³ ³ ³ Write() ±ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ Reorder(OutputStream) ±ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 8.9: A simplified DatedStockItem object with two methods Problem Algorithms Java Interpreter Hardware 308 Chapter 8: PRETTY POLY As you can see, the new method has been added to both mtables, so that a call to Write through a base class reference will call the correct method. A Pointed Reminder Now let’s see how we can use polymorphism in an application program to handle a mix of StockItem and DatedStockItem objects. Figure 8.10 shows how to do this. import WAJ.*; public class Polytest2 { public static void main( String args[ ] ) { StockItem[ ] x = new StockItem[2]; x[0] = new StockItem("3-ounce cups",71,78,40, 15,"Bob’s Distribution","1234567890"); Problem Algorithms Java Interpreter Hardware x[1] = new DatedStockItem("milk",76,87,40, 20,"Phil’s Distribution","9876543210","19970719"); System.out.println("A StockItem: "); x[0].Write(System.out); System.out.println(); System.out.println("A DatedStockItem: "); x[1].Write(System.out); System.out.println(); } } Figure 8.10: Mixing StockItem and DatedStockItems in a single array (code\Polytest2\Polytest2.java) The first item of note in this program is the statement StockItem[ ] x = n e w S t o c k I t e m [ 2 ] ; , w h i c h c r e a t e s a n a r r a y o f StockItem references. The elements of this array can refer to any mixture of StockItems and DatedStockItems, because we can assign objects of either of those types to a base class reference, which in this case is a StockItem reference. Once we have the array of StockItem references, we use new, together A POINTED REMINDER 309 with the appropriate constructor, to create whichever type of object we actually need; that’s the job of the next two statements. Once we have created those objects, we can call Write to display both a StockItem and a DatedStockItem, even though the display methods for those two types are actually different. The reason, of course, is that there are actually two different versions of Write, one for StockItems and one for DatedStockItems. The Java interpreter will determine which one of these polymorphic Write m e t h o d s t o c a l l a t run time, based on the actual type of the object being displayed. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is program is "c:\whosj\code\Polytest2". Susan had some questions about this program. Susan: Why are you using new here? Steve: We always have to use new to create an object of any userdefined type, to allocate the memory for the object. Susan: Why does Write need to be different for each class? Steve: Because each class has different fields that have to be written. Let’s look at some diagrams that show how this polymorphic method call works, using the simplified versions of StockItem and DatedStockItem that we used in our previous discussion. First, Figure 8.11 illustrates the method call to the StockItem version of Write. Step (1) gets the mtable pointer from the StockItem object to which x[0] refers. Step (2) gets the address of the Write method from that mtable. Then step (3) uses that address to call the StockItem version of Write. At the end of that method, it returns to the next statement in the main program. So the net result of the call illustrated in Figure 8.11 is that the StockItem version of Write is called, which is what we want in this situation. Problem Algorithms Java Interpreter Hardware 310 Problem Algorithms Java Interpreter Hardware Chapter 8: PRETTY POLY ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ public static void main(String args[ ]) ³ ³ { ³ ³ StockItem[ ] x = new StockItem[2]; ³ ³ ³ ³ x[0] = new StockItem(5,8); ³ ³ ³ ³ x[1] = new DatedStockItem(2.5, ³ ³ "19970719"); ³ ³ ³ ³ x[0].Write(System.out); ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄ¿ ³ ³ ³(1) ÚÄÅÄÄÄ¿ ³ ³ ³ ³ ² ³ ³ ³ ³ x[1].Write(System.out); ³ ³ ³ ³ } ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ StockItem ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ Name ² ³ ÚÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄ¿ ³ (mtable address) ³ 12350000 ÃÄÄ¿ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ m_InStock ³ 0005 ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ m_MinimumStock ³ 0008 ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³(2) ³ ÚÄÄÄÄÙ ³ StockItem mtable ² ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄ¿ ³ Write address ³ 12351800 ÃÄÄ¿ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³(3) ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÚÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ² ³ ³ ³ Write(OutputStream s) ³ ³ ³ { ³ ³ ³ //code to write a StockItem ³ ÀÄÅÄÄ} ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 8.11: Calling the Write method of StockItem through a StockItem reference Now let’s see what happens when the second call to Write is executed in Figure 8.11. Step (1) gets the mtable pointer from the DatedStockItem object to which x[1] refers. Step (2) gets the address of the Write method from that mtable. Then step (3) uses that address to call the DatedStockItem version of Write. At the end of that method, it returns to the next statement in the main program. So the net result of the call illustrated in Figure 8.11 is that the DatedStockItem version of Write is called, which is what we want in this situation. A POINTED REMINDER 311 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ public static void main(String args[ ]) ³ ³ { ³ ³ StockItem[ ] x = new StockItem[2]; ³ ³ ³ ³ x[0] = new StockItem(5,8); ³ ³ ³ ³ x[1] = new DatedStockItem(2,5, ³ ³ "19970719"); ³ ³ ³ ³ x[0].Write(System.out); ³ ³ ³ ³ x[1].Write(System.out); ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄ¿ ³ ³ ³(1) ÚÄÅÄ¿ ³ ³ ³ ³ ² ³ ³ ³ ³ } ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ DatedStockItem ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ Name ² ³ ÚÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄ¿ ³ (mtable address) ³ 12360000 ÃÄÄ¿ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ m_InStock ³ 0002 ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ m_MinimumStock ³ 0005 ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ m_Expires ³ 19970919 ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ DatedStockItem mtable ³ ³ ³(2) ³ ÚÄÄÄÄÄÄÄÄÄÄÄÙ ³ ² ³ ÚÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄ¿ ³ Write address ³ 12361800 ÃÄÄ¿ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³(3) ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÚÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ² ³ ³ ³ Write(OutputStream s) ³ ³ ³ { ³ ³ ³ //code to write a DatedStockItem ³ ÀÄÅÄÄ} ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Figure 8.12: Calling the Write method of DatedStockItem through a StockItem reference Susan had another question about polymorphism and its application. Susan: So polymorphism is necessary only when using arrays, otherwise inheritance will do? Steve: Not exactly. Polymorphism is needed whenever we want to be able to decide at run time rather than at compile time what the actual Problem Algorithms Java Interpreter Hardware 312 Chapter 8: PRETTY POLY type of an object will be. Usually this is when we have lots of objects to deal with, in which case we would be using arrays or some similar means of keeping track of all those objects. However, sometimes there are only a few objects that can be of various types depending on exactly what the program is doing, and there we would still need polymorphism. That explains how we can write out an object from our application without having to worry whether it is a StockItem or a DatedStockItem. However, this simple example might understate the importance of polymorphism. How important is it really? Maintenance Required Polymorphism is crucial to the maintainability of large systems. The problem is that without polymorphism, the application programmer has to keep track of all the possible variations on the basic types of objects the program uses. That is, in the absence of polymorphism, a scenario like the following one would be almost certain to develop during the lifespan of a large program. Problem Algorithms 1. Java Interpreter Hardware 2. The program would start out with two types of stock items, StockItem and DatedStockItem. Therefore, whenever it was necessary to write one of these data items to a file, the application programmer would need an if statement that said, "If it’s a StockItem, call the Write method for that type. Otherwise, call the Write method for a DatedStockItem." Sometime later, another type of stock item would be needed, say a DrugStockItem, which needs special permission to sell or reorder it. The programmer now has to change the if statement to say, "If it’s a StockItem, call the Write method for that type. Otherwise, if it’s a DatedStockItem, call the Write method for DatedStockItem. Otherwise, call the Write method for DrugStockItem." That doesn’t sound so bad, does it? Actually, it is very serious. The difficulty is that this sort of change must be made everywhere in the program, and must be made correctly in every case. To see this, consider what would happen if the programmer missed a place where the change in step 2 was needed. The Write method for DatedStockItem would be called whenever the actual type was DrugStockItem! The problem is the "otherwise" clause in the first version of the program, which makes the assumption that if the item in question isn’t a StockItem, then it must be a DatedStockItem. MAINTENANCE REQUIRED 313 Would the program work properly if it were written to say "if it’s a StockItem, do this; if it’s a DatedStockItem, do that"? No, because then if the object weren’t either of those types, it wouldn’t do anything! Whether that would be better or worse than doing the wrong thing depends on the exact situation, but in any event it’s clear that neither of these behaviors is correct. By contrast, with polymorphic classes, the application programmer doesn’t need any if statements to select the correct Write method, but just calls Write for each object that needs to be written to the file. The determination of which method to call is made by the compiler rather than the application programmer. Therefore, adding a new class derived from StockItem would not affect the application program at all; so long as the new class is properly implemented, the application program could simply use it without even being recompiled. This goes a long way toward preventing the type of maintenance nightmare that is facing every large organization in the form of the Year 2000 problem. Begin at the Beginning Now let’ s l o o k a t t h e i m p l e m e n t a t i o n o f W r i t e for StockItem (Figure 8.13) and for DatedStockItem (Figure 8.14). The main thing that might not be obvious about the StockItem.Write method is why StockItem.Write writes the "0" out as its first action. We know that there’s no date for a StockItem, so why not just write out the data that it does have? void Write(OutputStream s) { RWVar.println(s,0); RWVar.println(s,m_Name); RWVar.println(s,m_InStock); RWVar.println(s,m_Price); RWVar.println(s,m_MinimumStock); RWVar.println(s,m_MinimumReorder); RWVar.println(s,m_Distributor); RWVar.println(s,m_UPC); } Figure 8.13: StockItem.Write (from code\Itemtst22\StockItem.java) Problem Algorithms Java Interpreter Hardware 314 Chapter 8: PRETTY POLY void Write(OutputStream s) { RWVar.println(s,m_Expires); RWVar.println(s,m_Name); RWVar.println(s,m_InStock); RWVar.println(s,m_Price); RWVar.println(s,m_MinimumStock); RWVar.println(s,m_MinimumReorder); RWVar.println(s,m_Distributor); RWVar.println(s,m_UPC); } Figure 8.14: DatedStockItem.Write (from code\Itemtst22\DatedStockItem.java) Because when we read the data back in, we’ll need to be able to tell a StockItem from a DatedStockItem. Since "0" is not a valid date, we will use it to mean that the following data belong to a StockItem, not a DatedStockItem. When we read data from the inventory file to create our StockItem and DatedStockItem objects, any object whose data begins a "0" will become a StockItem, whereas any object whose data starts with a valid date will become a DatedStockItem. This is also why the DatedStockItem Write method can’t just handle its Problem n e w field and then call StockItem.Write to do the rest of the work; Algorithms StockItem.Write writes its own "0" value for the "date", so this would Java p r o d u c e t h e w rong output. Of course, it would be possible (and Interpreter desirable) to handle the common part of this task via another (private Hardware or protected) method that both the StockItem and DatedStockItem Write methods called, but for now this solution is good enough. Now that we can write the data out, how do we read it back in? The main problem is that we can’t create an item until we know which type it actually is. That’s handled by the next method we’re going to tackle: the Read method in the Inventory class. Why do we have to use a method in the Inventory class rather than one in StockItem? Because we don’t know whether we’re creating a StockItem or a DatedStockItem until we look at the data being read from the file. If there is an "expiration date" of 0, then it must be a S t o c k I t e m ; o t h e r w i s e , i t m u s t b e a D a t e d S t o c k I t e m. H o w e v e r , w e c a n ’ t call a constructor until we know what type of object we want to construct! Therefore, we need a method that will figure out the type of object to construct and call the constructor for StockItem or DatedStockItem, whichever is appropriate. Since the Inventory class is the manager for the StockItem and DatedStockItem objects used in our application program, its Read method (Figure 8.15) is a good place to create those objects. BEGIN AT THE BEGINNING 315 protected static StockItem Read(InputStream s) { DataInputStream dis = new DataInputStream(s); String tempInStock; String tempPrice; String tempMinimumStock; String tempMinimumReorder; String Name; String Distributor; String UPC; int InStock; int Price; int MinimumStock; int MinimumReorder; String Expires; StockItem Result; try { Expires = dis.readLine(); Name = dis.readLine(); tempInStock = dis.readLine(); tempPrice = dis.readLine(); tempMinimumStock = dis.readLine(); tempMinimumReorder = dis.readLine(); Distributor = dis.readLine(); UPC = dis.readLine(); } catch (IOException e) { System.out.println(e); return null; } if (UPC == null) return null; InStock = Integer.parseInt(tempInStock); Price = Integer.parseInt(tempPrice); MinimumStock = Integer.parseInt(tempMinimumStock); MinimumReorder = Integer.parseInt(tempMinimumReorder); Figure 8.15: The Inventory.Read method (from code\Itemtst22\Inventory.java) Problem Algorithms Java Interpreter Hardware 316 Chapter 8: PRETTY POLY if (Expires.equals("0")) { Result = new StockItem(Name, InStock, Price, MinimumStock, MinimumReorder, Distributor, UPC); } else { Result = new DatedStockItem(Name, InStock, Price, MinimumStock, MinimumReorder, Distributor, UPC, Expires); } return Result; } Figure 8.15 continued This starts out reasonably enough with the header indicating that it is a protected static method, which means that we can use it in either the Inventory class o r a n y o f i t s d e s c e n d a n t s ( i f a n y ) , a n d t h a t w e d o n’t need a particular Inventory object to call it. Since its job is to return a StockItem or DatedStockItem that we can stick into any Inventory object, this makes sense. However, what about the return type, which is Problem Algorithms StockItem? We don’t know whether we are actually going to create a StockItem or a DatedStockItem; that’s why we have to use a method in Java Interpreter Inventory rather than in StockItem or DatedStockItem. So how can the return value be a StockItem? Hardware Blind Date That was a trick question. The return value isn’t an object; it’s a reference to a StockItem. Because a reference to a base class can actually refer to any object of that type or any of its descendants, either a StockItem or a DatedStockItem can be referred to by references of type StockItem. The code itself is pretty simple. We read each data item from the file and convert each one that represents a numeric value from its String form to a numeric form via the Integer.parseInt method. Then we check whether the Expires value is equal to "0". If it is, this must be a regular StockItem; otherwise, it must be a DatedStockItem, and we create the appropriate type of object via its normal constructor and assign it to the Result StockItem reference. That reference is then returned to the user. BLIND DATE 317 Now that we’ve seen all the pieces, let’s take a look at how they all fit together. Figure 8.16 shows the main program that reads DatedStockItems and StockItems from a file and displays them. import WAJ.*; import java.io.*; public class Itemtst22 { public static void main( String args[ ] ) { FileInputStream ShopInfo; FileOutputStream ReorderInfo; Inventory MyInventory = new Inventory(); try { ShopInfo = new FileInputStream("shop22.in"); } catch (IOException e) { System.out.println("Can’t open file \"shop22.in\""); return; } MyInventory.LoadInventory(ShopInfo); try { ReorderInfo = new FileOutputStream("shop22.reo"); } catch (IOException e) { System.out.println("Can’t open file \"shop22.reo\""); return; } MyInventory.ReorderItems(ReorderInfo); } } Figure 8.16: Test program for DatedStockItem and StockItem (code\Itemtst22\Itemtst22.java) Problem Algorithms Java Interpreter Hardware 318 Chapter 8: PRETTY POLY To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Itemtst22". By the way, the ability to use an object of a derived class wherever an object of its base class is specified is also the explanation of the somewhat cryptic note in Chapter 6 mentioning that a catch statement can actually catch any exception of a type that is of either the class specified in its argument list or of any derived class of that class. Of course, this applies not only to the argument list of a catch statement but to all other argument lists as well. Exercises 1. Problem 2. Algorithms Java Interpreter Hardware 3. Rewrite the DrugStockItem class that you wrote in Chapter 6 as a derived class of DatedStockItem, so that DrugStockItem objects can be used in place of StockItem objects or DatedStockItem objects, just as you can use DatedStockItem objects in place of StockItem objects. Modify the Manager class that you wrote in Chapter 7 so that the CalculatePay method in that class has an argument specifying the number of hours worked. The implementation of CalculatePay in the Manager class will fire the manager if the number of hours is less than 50, but will otherwise ignore the argument, while the Hourly implementation of CalculatePay will continue to use the argument as it did previously. Because the CalculatePay methods i n t h e s e t w o class e s n o w h a v e t h e s a m e a r g u m e n t s , o b j e c t s o f these classes can be treated as though they were of the same class; therefore, this is an example of inheritance for reimplementation. Write an essay comparing the advantages and disadvantages of the two approaches to inheritance in the previous exercise and the corresponding exercise in Chapter 7. Review We started out this chapter with a DatedStockItem class that was derived from the StockItem class, with the addition of an expiration date field. While this was a fine solution to the problem of creating a class based on the StockItem class without having to rewrite all the previously REVIEW 319 f u n c t i o n i n g c o d e i n t h e l a t t e r class, it didn’t explain how we could mix objects that might or might not have expiration dates in the same array. Because Java allows us to assign an object of a derived type to a reference of its base class, creating an array of StockItem r e f e r e n c e s allows us to create both StockItems and DatedStockItems and assign them to various elements of the StockItem array. The correct method is always called in this situation because when the compiler sees a call to the Reorder method, it will generate code that will call the appropriate version of that method for the actual type of the object being referred to. In this case, StockItem.Reorder will be called if the actual object being referred to through a StockItem reference is a StockItem, and DatedStockItem.Reorder will be called if the actual object being referred to through a StockItem reference is a DatedStockItem. This is exactly the behavior that we need to make our StockItem objects and DatedStockItem objects do the right thing when we call them through a StockItem reference. To make this run time determination of which method will be called, the compiler has to add something to every object, namely a reference to an mtable, which is short for "method address table". An mtable effectively contains the addresses of all the methods that can be called in the current class, which includes methods defined in any of its ancestors. The code that the compiler generates for a method call uses this mtable to look up the actual address of the method to be called at run time. After going over some examples showing how this works in the context of our example program that assigns a StockItem to one element of a StockItem array and a DatedStockItem to another element of the same array, we discussed why polymorphism is important to maintainability of large systems. Finally, we looked at the implementation of the Write and Read methods for the StockItem and DatedStockItem classes. While Write is fairly simple, because the type of the object to be written is already known when we’re writing it, the same is not true of Read. In fact, Read can’t even be a method of StockItem or DatedStockItem, because we don’t know which of those types we have until we have already read the data for the object. Therefore, we had to put this Read method in t h e I n v e n t o r y c l a s s, which is responsible for managing StockItem a n d DatedStockItem objects in our example programs. Problem Algorithms Java Interpreter Hardware Appendix A Tying up Loose Ends Where Am I, Anyway? Now that you’ve reached the end of this book, there are some questions that have probably occurred to you. For example, 1. 2. 3. 4. Am I a programmer now? What am I qualified to do? Where do I go from here? Is that all there is to Java? The answers to the first three of these questions, as usual with such open-ended topics, is "It all depends". Of course, I can give you some general answers; let’s start with questions 1 and 2. Yes, in the broadest sense, you are a programmer. You’ve read a fair amount of code and written some programs yourself. But, of course, this doesn’t mean that you’re a professional programmer. As I said way back at the beginning, no book can turn a novice into a professional programmer. Being a professional in any field takes a lot of hard work, and although you’ve undoubtedly worked hard in understanding this book, you’ve just begun the exploration of programming. Questions 3 and 4 are also closely related. You now have enough background that you should be able to get some benefit from a wellwritten book about Java that assumes you are already acquainted with programming; that would be a good way to continue. As for whether we’ve covered everything about Java, the answer is unequivocal: absolutely not. I would estimate that we have examined perhaps 10% of the very large and complicated Java language; 321 322 Appendix A: TYING UP LOOSE ENDS however, that 10% is the foundation for the rest of your learning in this subject. Most books on Java try to cover every aspect of the language and, as a result, cannot provide deep coverage of fundamentals; I’ve worked very hard to ensure that you have the correct tools to continue your learning. Tying up Loose Ends I’ve skipped over some topics because they weren’t essential to the discussion. However, since they are likely to be covered in any other book that you might read on programming in Java, I’ll discuss them here briefly. This will ensure that they won’t be completely foreign to you when you encounter them in your future reading. Reference Substitution Whenever you are asked for a reference of a particular class, you can provide a derived class reference instead. This is very similar to the Problem rule that a reference of a given class can refer to objects of that class or Algorithms any of its derived classes, but it’s not identical: this rule allows us to Java supply a reference to a DatedStockItem, for example, where an Interpreter argument list specifies a reference to a StockItem. The old rule, by Hardware contrast, would allow a StockItem reference to refer to a DatedStockItem object. Command Line Arguments The by-now-familiar line public static void main(String args[ ]) has another function besides specifying where every Java program starts. The expression String args[ ] is the argument to the main function. What it contains is the Strings that were typed in on the command line when the program was run. For example, if you ran the A v e r a g e p r o g r a m from Chapter 5 by changing to the "\whosj\code\func1" directory and typing jview Average 14 22, then the value of the String args[0] would be "14" and the value of the String args[1] would be "22". The way that program is currently written, any values typed on the command line are ignored. However, if we wanted the Average program to take input from its command line arguments and convert them to ints, then we could change it to use those values from the command line rather OPERATOR PRECEDENCE 323 than prompting the user to type them in after the program started running. Operator Precedence You may recall from high school arithmetic that an expression like 5 + 3 * 9 is calculated as though it were written 5 + (3 * 9), not (5 + 3) * 9; that is, you have to do the * before the +, so that the correct result is 32, not 72, as it would be under the latter interpretation. The reason for performing the operations in the former order is that multiplication has a higher precedence t h a n a d d i t i o n . W e l l , e v e r y operator in Java also has a precedence that determines the order of application of each operator in an expression with more than one operator. This seems like a good idea at first glance, since after all, arithmetic does follow precedence rules like the one we just saw. Unfortunately, Java is just a little more complicated than arithmetic, and so its precedence rules are not as simple and easy to remember as those of arithmetic. In fact, there are 13 different levels of precedence, which no one can remember. Therefore, everyone1 ends up using parentheses to specify what order was meant when the expression was written; of course, if we’re going to have to use parentheses, then why do we need the precedence rules in the first place? Other Native Data Types We’ve confined our use of native data types to short, char, int and boolean (and float in some of the exercises). As I mentioned in Chapter 6, there are other native types; you’ll be seeing them in other programs and in other textbooks, so I should tell you about them now. By the way, I haven’t avoided them because they’re particularly difficult to use; the reason is simply that they weren’t necessary to the task at hand, which was teaching you how to program, using Java. Now that we have accomplished that task, you might as well add them to your arsenal of tools. These other native types are 1. 2. 3. double long byte ______________________________________________________________________________ 1. At least, everyone who is sensible. Problem Algorithms Java Interpreter Hardware 324 Appendix A: TYING UP LOOSE ENDS As we’ve already seen in some of the exercises, the float types are used to store values that can contain fractional parts (so-called floating-point numbers), rather than being restricted to whole numbers as in the case of short and the other integral types. Of course, t h i s r a i s e s t w o q u e s t i o n s : F i r s t , w h y d o n’t we use this type all the time, if it’s more flexible? Second, why do we need double as well as float? These questions are related, because the main difference between float and double is that a float is 4 bytes long and a double is 8 bytes long; therefore, a double c a n s t o r e l a r g e r v a l u e s a n d m a i n t a i n higher accuracy. However, it also uses up twice the amount of memory of a float, which may not be important when we’re dealing with a few values but is quite important if we have an array of thousands or tens of thousands of elements. A long is like an int, except longer. It can handle very large integer values, from –9223372036854775808 to 9223372036854775807. If we have whole numbers that are too big to fit into an int, a long would do the trick. On the other hand, if we need values with fractional parts, a double or float would be appropriate. The byte, as its name indicates, is used to hold one byte of data, which can be useful when exchanging information with the external world, where characters are often one byte in length rather than two as in Java. Problem Algorithms Java Interpreter The Vector Hardware We’ve used arrays to good effect in our programs, but they have some drawbacks, the primary one being that you have to know exactly how large an array is before you create it. While this is often a reasonable restriction, there are times when it would be very handy to be able to create a collection of objects without having to know how many there will be in the collection before adding them. This is possible in Java by using a Vector, which is similar in some ways to an array except that it will grow as required to accommodate new elements. Unfortunately, Vectors also have some serious drawbacks, primarily the inability to use the normal indexing operator [ ] to select an element. Because Java is too simple to have operator overloading, you have to use an elementAt method to retrieve values from a Vector and an addElement method to add an element. THE interface 325 The interface We’ve already seen how to create a new class that has the behavior of a preexisting class with some added features: inheritance. However, what if we want to create a new class that has the behavior of more than one existing class? Java doesn’t allow multiple inheritance, in which one class is derived from more than one existing class. However, there is a way to solve this problem, by using an interface, which is quite similar to a class, with two important exceptions. First, it has no implementation; instead, it must be implemented separately by every class that wants to display its behavior. Second, it is legal in Java to implement several interfaces in a single c l a s s. This provides many of the advantages of multiple inheritance without all of the complexity of that facility. Threads Many programming languages support only sequential execution, in which only one statement in a program can be executed at any given time, and it appears to the programmer that a particular method runs from beginning to end without interruption. However, in Java it is possible to write a program as a collection of independent threads, each of which can be thought of as executing at the same time as the others. Of course, if you have only one CPU in your computer, only one instruction can really be executing at a given time, but the statements in a given thread can execute without waiting for statements in another thread to finish. Writing programs that use threads correctly can be difficult, but Java has a number of facilities that will help you keep the threads in your programs untangled. Standard packages Besides all of the features of the Java language itself, there are quite a few standard packages that supply facilities not built into the language. Unfortunately, these are far too large and complicated to discuss here: I recommend that you get a copy of Java in a Nutshell, by David Flanagan, published by O’Reilly & Associates. Although this book will not teach you Java, and contains some dubious statements about the nature of the language and its relationship to C and C++, it is nevertheless the best reference book on the Java language that I have found. Problem Algorithms Java Interpreter Hardware 326 Appendix A: TYING UP LOOSE ENDS Applets Finally, I should mention that Java can be used to write applets, which are small programs that run inside a WWW browser such as Netscape Navigator or Internet Explorer. This book doesn’t discuss applets, because I believe that it makes more sense to learn how to use the Java language before you try to use it to write interactive programs such as applets. However, you shouldn’t have any trouble finding a book that will teach you how to write applets, now that you have a pretty good understanding of the basics of the Java language. Problem Algorithms Java Interpreter Hardware Appendix B Java Q&A Questions and Answers If you are a C++ programmer, you may have a lot of questions about the similarities and differences between that language and Java. If you’re not a C++ programmer, you may still have a lot of questions about some of the finer points of the language. In this Appendix, I’ll try to answer some of the more frequently asked questions to your satisfaction, whichever category you fall in. Note: some of these answers are quite different from the ones that diehard Java supporters might give you. If you want the party line, you can find it in virtually any other book on Java, but not here. Before we begin with the "frequently asked questions", let’s hear what Susan has to say about the relationship between Java and C++, as well as how you can decide whether Java is right for you. Susan: Some of these things may be considered minor nit picking. For one, compared to C++, Java looks too busy on the page. The operators that control input and output are unnecessarily wordy. I would think that they would be very annoying to type many times in a large program. On a more serious level, I very much disliked the idea of exception handling (or error checking, which I found easier to understand). This may very well be an improvement over C++ in terms of a less error prone program, but it seems to add unnecessary "busy-ness" to the code and is just one more thing to think about when you’re starting out. The method of adding variable values to strings was a bit of a surprise. I find it understandable if not a little silly. Yet, it works so I won’t complain. The idea of practically everything being a reference is also a little hard to swallow, especially for a language that claims it does not employ pointers, when it seems that in reality, almost everything is a pointer. 327 328 Appendix B: QUESTIONS AND ANSWERS On a more positive note, the hardest concept that I have had to deal with in both languages is polymorphism, which I found to be much easier to learn in Java than in C++. With the flexibility and usefulness of this object-oriented facility, it is good to see it put in a form that will be faster and easier for more people to learn and use. My final comment is an opinion that I share with Steve, whose expertise in programming I tend to trust: Java is just another language. It is no better nor worse than other languages. We have different languages to suit different needs and this language came along to fill gaps where there were needs. Is this the end-all language that everyone will be using to the exclusion of others? No, it is simply more useful in doing some things than other languages. It can’t hold up to C++ in major programming endeavors. However, it seems fine for its main intention, the making of applets for Internet usage. The excitement and fervor surrounding this language in my opinion is due primarily to the excitement and fervor of the Internet. As with other languages, Java has its place, but how you plan on using it should guide your decision on whether it is the right language for you. Equality Now? Problem Algorithms Java Interpreter Hardware Q: The following code (Figure B.1) doesn’t work. The result is always "Please answer with either yes or no", no matter what I type. What’s wrong? import WAJ.*; public class Equality { public static void main( String args[ ] ) { String answer; System.out.print("Are we having fun yet? (yes/no): "); answer = RWVar.readString(System.in); if (answer == "yes") System.out.println("Your answer is correct"); else if (answer == "no") System.out.println("Your answer is erroneous"); else System.out.println("Please answer with either yes or no."); } } Figure B.1: Equality testing program (code\Equality\Equality.java) EQUALITY NOW? 329 A: The problem is that you can’t use != or == to compare two Strings for equality, because those comparisons test whether the two Strings refer to the same actual object rather than whether they have the same contents. Instead, you have to use equals, as in Figure B.2. import WAJ.*; public class Equal2 { public static void main( String args[ ] ) { String answer; System.out.print("Are we having fun yet? (yes/no): "); answer = RWVar.readString(System.in); if (answer.equals("yes")) System.out.println("Your answer is correct"); else if (answer.equals("no")) System.out.println("Your answer is erroneous"); else System.out.println("Please answer with either yes or no."); } } Figure B.2: Corrected equality testing program (code\Equal2\Equal2.java) To use the debugger for either of these programs, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions a s s u m e t h a t y o u ’ve installed the examples on drive C:, so that the location of the first program is "c:\whosj\code\Equality" and the location of the second program is "c:\whosj\code\Basic08". Q: I want to know whether the Strings have the same contents; I don’t care whether they refer to the same object. How do I make == do the sensible thing and compare the contents of the Strings? A: The difficulty is that it’s impossible for the compiler to know exactly what = = s h o u l d d o f o r a u s e r - d e f i n e d v a r i a b l e . T h i s m e a n s that in order to make == work correctly for such variables, we would have to write our own version of == for that type of variable. However, Java is a simple language, without a lot of frills like letting the class designer define what == means. If you want to know whether two Strings have the same content, you have to use equals; to Problem Algorithms Java Interpreter Hardware 330 Appendix B: QUESTIONS AND ANSWERS compare two objects of other types that you define, you have to write your own equals method.1 Of course, if you use == when you meant to use equals, your program will still compile; you’ll just get the wrong answer. So don’t do that! By the way, according to the authors of The Java Programming Language, one of whom was the originator of the language, "Using equality operators on String objects does not work as expected. Given String objects str1 and str2, str1==str2 tests whether str1 and str2 refer to the same String object. It does not t e s t w h e t h e r t h e y h a v e t h e s a m e c o n t e n t s . C o n t e n t e q u a l i t y i s t e s t e d u s i n g String.equals, described in Chapter 8." (p. 116, emphasis in the original) For C++ programmers: In C++, of course, you can use != and == to compare the values of two strings (or objects of another class), so long as the designer of the class has written versions of != and == for that purpose. However, that requires operator overloading, which is much too complex for a simple language like Java.2 Input and Output Problem Algorithms Q: How can I read a number from the keyboard with standard Java functions, rather than having to use the WAJ package to do that? Java Interpreter A: Okay, you asked for it. Figure B.3 is the simplest method I could Hardware come up with to read an int from the keyboard. Q: Why is this so complicated? A: Remember, Java is a simple language; it doesn’t have all the complicated stuff built into it that many other languages do. This means that you often have to know a great deal about the language in order to do the simplest things. Let’s see what we need to know to write this function: 1. 2. H o w t o c r e a t e a D a t a I n p u t S t r e a m from an InputStream, so that we can read data from the input source (the keyboard, in this case). How to cause any waiting output to be sent to the screen before we try to read data (the flush method). ______________________________________________________________________________ 1. Which is obviously much simpler than writing your own == method! 2. Except when the designers of Java really wanted to use it, as they did with the + method for Strings. But that doesn’t count. INPUT AND OUTPUT 3. 4. 5. 331 How to use exception handling to deal with possible errors when trying to read data from the keyboard.3 How to read a String from a DataInputStream. How to use the parseInt method of the Integer class to convert the String we’ve read to an int. See how simple that was? static public int readInt(InputStream in) { String templine; int result; DataInputStream dis = new DataInputStream(in); System.out.flush(); try { templine = dis.readLine(); result = Integer.parseInt(templine); } catch (IOException ioe) { System.out.println(ioe.toString()); System.out.println("Unable to get int data."); return 0; } return result; } Figure B.3: Reading an int from the keyboard (from code\WAJ\WAJ\RWvar.java) Q: I don’t believe it. I’ve heard that you can just read a number from t h e k e y b o a r d b y u s i n g readInt (as in Figure B.4), rather than going through all that malarkey. (To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Testinput".) ______________________________________________________________________________ 3. See the "Exceptional Opportunities" section for some more details on exception handling. Problem Algorithms Java Interpreter Hardware 332 Appendix B: QUESTIONS AND ANSWERS import java.io.*; public class Testinput { public static void main(String args[ ]) throws IOException { DataInputStream d = new DataInputStream(System.in); int i; int j; System.out.print("Enter the first number: "); System.out.flush(); i = d.readInt(); System.out.print("Enter the second number: "); System.out.flush(); j = d.readInt(); System.out.println("The first number is: " + i); System.out.println("The second number is: " + j); System.out.println("The sum is: " + (i+j)); } } Problem Algorithms Figure B.4: Test input program (code\Testinput\Testinput.java) Java Interpreter A: First of all, that code doesn’t look very simple. However, a more Hardware significant problem is that it won’t work properly. Here’s the result of running that program: Enter the first number: 1000 Enter the second number: 2000 The first number is: 825241648 The second number is: 171061296 The sum is: 996302944 The problem is that readInt doesn’t work the way you would expect. Rather than interpreting the characters read from the keyboard as digits of a decimal number as my RWVar.readInt method does, the readInt method of the DataInputStream class uses the byte value of each character from the keyboard as one byte of the resulting int. In this case, the value of i is calculated as the number created by taking the character ‘1’ as the first byte of the number and the three occurrences of the character ‘0’ as the other three bytes of the number. Since ‘1’ has the value 49 and ‘0’ has the value 48, the entire value of i can be INPUT AND OUTPUT 333 calculated as 256*256*256*49 + 256*256*48 + 256*48 + 48, which comes out to 825241648. What about j; surely "2000" should give a higher value than "1000"! However, the actual value is the result of combining the value of the next character typed in, which is the newline character that ended the line where the "1000" was typed in, with the next three characters, which are the ‘2’ and two ‘0’s. The newline has a value of 10, so combining this with the ‘2’ (value 50) and the first two ‘0’s (value 48) allows us to calculate the value of j as 256*256*256*10 + 256*256*50 + 256*48 + 48, or 171061296. For C++ programmers: In C++, of course, you can just say cin >> x; and the data will be read in the format that you expect for a variable of whatever type x might be, but providing that facility requires operator overloading. Exceptional Opportunities Q: Why do I get the error message "error J0122: Exception ’IOException’ not caught or declared by ’String IOTest1.readInfo()’" when I try to compile the program in Figure B.5? import java.io.*; public class IOTest1 { public static void main(String args[ ]) { String text; System.out.println("What do you say? "); text = readInfo(); System.out.println("You said: " + text); } static String readInfo() { String templine; DataInputStream dis = new DataInputStream(System.in); templine = dis.readLine(); return templine; } } Figure B.5: Test input/output program (code\IOTest1\IOTest1.java) Problem Algorithms Java Interpreter Hardware 334 Appendix B: QUESTIONS AND ANSWERS A: The compiler is telling you that your readInfo method has to deal with the possibility that you might not be able to read a line from the keyboard for some reason; if that should occur in your method, the readLine method would throw an exception. You have the choice of either stating that you aren’t going to worry about the exception, thus pawning it off to the method that calls the method you’re writing (which in this case is main), or doing the exception handling yourself. If an exception does occur and no method currently in use wants to handle the exception, the program will terminate. If that’s what you want to happen, all you have to do is add the expression throws IOException to the declarations of your readInfo method and your main method, resulting in the program in Figure B.6. import java.io.*; public class IOTest2 { public static void main(String args[ ]) throws IOException { String text; Problem Algorithms Java Interpreter Hardware System.out.print("What do you say? "); System.out.flush(); text = readInfo(); System.out.println("You said: " + text); } static String readInfo() throws IOException { String templine; DataInputStream dis = new DataInputStream(System.in); templine = dis.readLine(); return templine; } } Figure B.6: Test input/output program (code\IOTest2\IOTest2.java) To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve EXCEPTIONAL OPPORTUNITIES 335 installed the examples on drive C:, so that the location of this program is "c:\whosj\code\IOTest2". Q: What’s an exception? A: It’s an unexpected event that occurs during the execution of a program, or in other words, an error. What makes it different from other kinds of errors is that it isn’t predictable because it may arise from causes outside the program. Examples are missing files, a t t e m p t s t o d i v i d e b y z e r o , a n d o t h e r e v e n t s t h a t a r e n’ t c a u s e d b y errors in the program itself. Q: Why does Java force you to handle exceptions when reading data (e.g., readLine()) but not when writing (e.g., println())? How does Java decide when it does and doesn’t require exception handling? A: If you find the distinction between these categories far from obvious, you’re not alone. I consider the division into checked and unchecked exceptions fairly arbitrary; you just have to look up which is which, or let the compiler tell you when you’ve called a method that might throw a checked exception. In general, it ’s a good idea to handle exceptions as soon as you can, because that will allow you to give better error messages to the user of the method. Figure B.8 shows how to do that; its output, assuming you type in "Hello", is: What do you say? Hello You said: Hello Figure B.7: Output of first test input/output program with exception handling To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Except1". Problem Algorithms Java Interpreter Hardware 336 Appendix B: QUESTIONS AND ANSWERS import java.io.*; public class Except1 { public static void main(String args[ ]) { String text; System.out.print("What do you say? "); System.out.flush(); text = readInfo(); System.out.println("You said: " + text); } static String readInfo() { String templine; DataInputStream dis = new DataInputStream(System.in); try { templine = dis.readLine(); } catch (IOException ioe) { System.out.println(); System.out.println("*****"); System.out.println(ioe.toString()); System.out.println("Unable to get keyboard data."); System.out.println("*****"); return ""; } Problem Algorithms Java Interpreter Hardware return templine; } } Figure B.8: Test input/output program with exception handling (code\Except1\Except1.java) Q: What could go wrong when you’re just trying to read a String from System.in? There must be some reason that you have to catch exceptions in this case, but I haven’t been able to make it throw an exception. A: The program in Figure B.9 shows how to cause an exception to be thrown when trying to read from System.in. Note that in this case you EXCEPTIONAL OPPORTUNITIES 337 have to specify that main can throw IOException, because the close method can theoretically cause that to happen. import java.io.*; public class Except2 { public static void main(String args[ ]) throws IOException { String text; System.out.print("What do you say? "); System.out.flush(); System.in.close(); text = readInfo(); System.out.println("You said: " + text); } static String readInfo() { String templine; DataInputStream dis = new DataInputStream(System.in); try { templine = dis.readLine(); } catch (IOException ioe) { System.out.println(); System.out.println("*****"); System.out.println(ioe); System.out.println("Unable to get keyboard data."); System.out.println("*****"); return ""; } return templine; } } Figure B.9: Second test input/output program with exception handling (code\Except2\Except2.java) Problem Algorithms Java Interpreter Hardware 338 Appendix B: QUESTIONS AND ANSWERS The result of running this program is: What do you say? ***** java.io.IOException: read error Unable to get keyboard data. ***** You said: Figure B.10: Output of second test input/output program with exception handling The reason the main program displays "You said:" is that it doesn’t know that the exception has occurred in the readInfo method. Depending on your viewpoint, this is either an advantage or a disadvantage of handling the exception at the lowest level and not telling the calling method about it. To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Except2". Problem Algorithms Java For C++ programmers: In C++, of course, you don’t have to Interpreter declare what exceptions your functions can throw, although you are Hardware permitted to do so (and also to declare that it won’t throw any). I don’t particularly mind the extra rigor of Java, although I’m not convinced that it is of significant benefit. In Resplendent Array Q: When trying to use an element of an array of StockItems I get a run-time error that says "ERROR: java.lang.NullPointerException". What’s wrong with this picture (Figure B.11)? A: The problem is that when you create an array of a user-defined type, you’re really not creating an array of objects, but an array of references to objects of that type. Until you set a reference to refer to some particular object, you can’t use it. In the present example, we can use new to initialize each reference before using it. Figure B.12 is the corrected program. IN RESPLENDENT ARRAY 339 class Test { int x; } class ArrayTest1 { public static void main(String args[ ]) { int i; Test TestArray[ ] = new Test[100]; for (i = 0; i < 100; i ++) { TestArray[i].x = i; } System.out.println("We made it!"); } } Figure B.11: A sample program for arrays (code\ArrayTest1\ArrayTest1.java) class Test { int x; } class ArrayTest2 { public static void main(String args[ ]) { int i; Test TestArray[ ] = new Test[100]; for (i = 0; i < 100; i ++) { TestArray[i] = new Test(); TestArray[i].x = i; } System.out.println("We made it!"); } } Figure B.12: A sample program for arrays (code\ArrayTest2\ArrayTest2.java) Problem Algorithms Java Interpreter Hardware 340 Appendix B: QUESTIONS AND ANSWERS To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\ArrayTest2". For C++ programmers: A Java array of references is more like a C++ array of pointers rather than one of objects, because Java references are pretty much the same as C pointers except that you can’t do arithmetic with them (a restriction that doesn’t bother me at all). You’ll have to watch out for this difference between Java and C++ when writing Java programs, or they will fail mysteriously with "Null pointer exceptions".4 Getting into an Argument Q: How do arguments work in Java? Are they copies of the variables in the calling method (pass by value) or are they merely different names for the corresponding variables in the calling method (pass by reference)? Problem Algorithms A: As is usually the case in Java, the answer is simple: Pass by value, Java Interpreter with an explanation. Variables of primitive types are always passed by value. Variables of reference types are also passed by value; Hardware however, the meaning of passing a reference variable by value is not quite as obvious as it might appear. For example, consider the program in Figure B.13, in which the SetName method of a StockItem a r g u m e n t i s c a l l e d , a n d a n o t h e r StockItem argument is set to a new value. The output of this program is: Name: New first item name Name: Second item To use the debugger for this program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book. These instructions assume that you’ve installed the examples on drive C:, so that the location of this program is "c:\whosj\code\Arg1". ______________________________________________________________________________ 4. What happened to the claim that Java doesn’t have pointers? GETTING INTO AN ARGUMENT 341 import WAJ.*; public class Arg1 { public static void main( String args[ ] ) { StockItem item1; StockItem item2; item1 = new StockItem("First item"); item2 = new StockItem("Second item"); Argtest(item1, item2); item1.Display(); item2.Display(); } static void Argtest(StockItem first, StockItem second) { first.SetName("New first item name"); second = new StockItem("New second item"); } } class StockItem { private String m_Name; StockItem() { m_Name = ""; } StockItem(String Name) { m_Name = Name; } void Display() { System.out.print("Name: "); System.out.println(m_Name); } void SetName(String Name) { m_Name = Name; } } Figure B.13: How arguments work (code\Arg1\Arg1.java) Problem Algorithms Java Interpreter Hardware 342 Appendix B: QUESTIONS AND ANSWERS Q: I don’t get it. Why was the first one changed but not the second? A: The first StockItem was changed because copying a reference variable produces another reference variable that refers to the same object that the original one did. Therefore, first in Argtest refers to the same object that item1 did in main, and calling the SetName method on first changes the name in item1. The second StockItem argument, second, also started out as a copy of the corresponding reference variable in main, which in this case was item2. However, once we assigned second a new value in the line second = new StockItem("New second item");, second no longer referred to the same StockItem object that item2 did, but to a new StockItem object that was unrelated to the one that item2 refers to. Therefore, item2 remains unchanged after the return to main. Q: What about using arguments to return values from a method? A: Because arguments are always passed by value, assigning a new value to an argument affects only the argument, not the calling method’s variable. However, as we saw in the previous example, the o b j e c t t h a t a c a l l i n g p r o g r a m ’s reference variable refers to c a n b e Problem Algorithms c h a n g e d b y c a l l i n g a m e t h o d o n t h a t s a m e o b j e c t t h r o u g h t h e argument in the called method. In the example, although you can’t Java Interpreter make item1 refer to a different object by assigning a new object to first, you can change the contents of the object to which both item1 and first Hardware refer. For C++ programmers: Java has no way to specify that you want to pass a variable by reference rather than by value. However, an argument of reference type allows you to change the object to which the calling method’s reference variable refers.5 Thus, "pass by value" in Java, when applied to a reference type, has all the hazards of "pass by reference" in C++, but is more complex semantically. Semifinalist Q: How can I make sure that all of the resources (other than memory) used in my objects are released when I’m finished using the objects? ______________________________________________________________________________ 5. This assumes that there are methods that will change the object. Some classes, notably String, have no such methods, so references to such classes are safe to use as arguments. SEMIFINALIST 343 A: That’s easy: you just write a method that frees the resources and remember to call it whenever you need to. Q: Yes, but what if someone has copied a reference to the object and it’s still in use? A: Then don’t free the resources until the object isn’t being used. Q: B u t t h a t ’ s j u s t l i k e s a y i n g " D o n’t forget to delete your objects when you’re done with them". If garbage collection is needed to p r e v e n t p r o g r a m m e r s f r o m m a k i n g t h a t m i s t a k e , w h y w o n’ t t h o s e same programmers make mistakes about other resources? A: Well, you can always write a finalize method for your objects. That method will be called before each object is garbage collected. Does that satisfy your requirements? Q: Sure, as long as I know that the objects will be garbage collected in time to free the resources before I run out of them. When will they be garbage collected? A: I have no idea. There’s no guarantee in Java that the garbage collector will ever run during the execution of a program. In Java 1.1, you can request that finalizers be run at the end of the program, but of course that’s usually too late to do you any good. Q: Then what good is the finalize method? A: You’ve got me there. For C++ programmers: As this discussion suggests, the finalize method is not the equivalent of C++ destructors. In fact, it doesn’t seem to be of any value, since you can’t be sure when or if it will be called. You might think it would be simple to change the Java language specification to require that when an object with a f i n a l i z e method is no longer in use, that object will be garbage collected immediately (after running the finalize method, of course). Unfortunately, that is apparently not feasible as it would make the implementation of the Java interpreter much more complex. Under current and foreseeable circumstances, therefore, there is no automatic way to reclaim resources when an object is no longer in use. This means that Java is unsuitable for projects where you have to Problem Algorithms Java Interpreter Hardware be sure that resource deallocation is done in a timely manner without requiring omniscience of the application programmers. Glossary Special Characters < is the "less than" operator, which returns the value true if the expression on its left has a lower value than the expression on its right; otherwise, it returns the value false. = is the assignment operator, which assigns the expression on its right to the variable on its left. Note that assigning one reference to another will result in these two references pointing to the same object, so changes made to that object via one of the references will be reflected when that object is referred to via the other reference. > is the "greater than" operator, which returns the value true if the expression on its left has a greater value than the expression on its right; otherwise, it returns the value false. . is the "object member access" operator. It separates an object name, on its left, from the member variable or method on its right. [ is the left square bracket; see square brackets for usage. ] is the right square bracket; see square brackets for usage. { is the left curly brace; see curly braces for usage. } is the right curly brace; see curly braces for usage. != is the "not equals" operator, which returns the value true if the expression on its left has a value different from the expression on its right; otherwise, it returns the value false. && is the "logical AND" operator. It produces the result true if both of the expressions on its right and left are true; if either of those 345 346 GLOSSARY expressions is false, it produces the result false. However, this isn’t the whole story. There is a special rule in Java governing the execution of the && operator: If the expression on the left is false, then the answer must be false and the expression on the right is not executed at all. The reason for this short-circuit evaluation rule is that in some cases you may want to write a right-hand expression that will only be legal if the left-hand expression is false. ++ is the increment operator, which adds 1 to the variable to which it is affixed. += is the add to variable operator, which adds the expression on its right to the variable on its left. -= is the subtract from variable operator, which subtracts the expression on its right from the variable on its left. // is the comment operator; see comment for usage. <= is the "less than or equal to" operator, which returns the value true if the expression on its left has the same value or a lower value than the expression on its right; otherwise, it returns the value false. == is the "equals" operator, which returns the value true if the expression on its left has the same value as the expression on its right; otherwise, it returns the value false. Note that the result of comparing two references will not tell you whether the objects these references point to are equal, but whether the references point to the same object. >= is the "greater than or equal to" operator, which returns the value true if the expression on its left has the same value or a greater value than the expression on its right; otherwise, it returns the value false. || is the "logical OR" operator. It produces the result true if at least one of the two expressions on its right and left is t r u e ; i f b o t h o f t h o s e expressions are false, it produces the result false. However, this isn’t the whole story. There is a special rule in Java governing the execution of the || operator: If the expression on the left is true, then the answer must be true and the expression on the right is not executed at all. The reason for this short-circuit evaluation rule is that in some cases you may want to write a right-hand expression that will only be legal if the left-hand expression is false. GLOSSARY 347 A An access specifier controls the access of methods of other classes to the methods and variables of a given class. The access specifiers used in this book are public, which allows general access to methods and variables; p r i v a t e , w h i c h f o r b i d s a c c e s s b y methods of other classes; and protected, which allows access by methods of derived classes. Access time is a measure of how long it takes to retrieve data from a storage device, such as a hard disk or RAM. Address; see memory address. An algorithm is a set of precisely defined steps guaranteed to arrive at an answer to a problem or set of problems. As this implies, a set of steps that might never end is not an algorithm. An application program is a program that actually accomplishes some useful or interesting task. Examples include inventory control, payroll, and game programs. An a p p l i c a t i o n p r o g r a m m e r ( o r u s e r ) i s a p r o g r a m m e r w h o u s e s primitive and class variables to write an application program. See also library designer. An argument is a value that is supplied by one method (the calling method) that wishes to make use of the services of another method (the called method). Arguments are copies of the values from the calling method. In the case of primitive types, this means that making changes to an argument will not affect the corresponding variable or value in the calling method. However, this is not true of arguments of user-defined types, because these are actually references to the same objects used in the calling method. As a result, changes made to arguments of userd e f i n e d t y p e s i n a m e t h o d can , u n d e r c e r t a i n c i r c u m s t a n c e s , a f f e c t the objects to which references in the calling method refer. An argument list is a set of argument definitions specified in a method declaration. The argument list describes the types and names of all the variables that the method receives when it is called by a calling method. 348 GLOSSARY An array is a group of elements of the same type which we wish to consider a single variable; for example, we can create an array of chars. We can refer to the individual elements by their indexes; so, if we have an array of chars called m_Data, m_Data[i] refers to the ith char in the array. Assignment is the operation of setting a variable to a value. The operator that indicates assignment is the equal sign, =. An assignment statement such as x = 5; is not an algebraic equality, no matter how much it may resemble one. It is a command telling the computer to assign a value to a variable. In the example, the variable is x and the value is 5. B A base class is a class from which one or more other classes are derived. The derived classes inherit all the fields and regular methods from the base class. See also inheritance. The base class part of a derived class object is an unnamed component of the derived class object whose fields and methods are accessible as though they were defined in the derived class, so long as they are either public or protected. See also inheritance. A binary number system uses only two digits, 0 and 1. A bit is the fundamental unit of storage in a modern computer; the word bit is derived from the phrase binary digit. Each bit, as this suggests, can have one of two states, 0 and 1. A block is a group of statements that are considered one logical statement. A block is delimited by the "curly braces", { and }; the first of these symbols starts a block, and the second one ends the block. A block can be used anywhere that a statement can be used, and is treated in exactly the same way as if it were one statement. For example, if a block is the controlled block of an if statement, then all of the statements in the block are executed if the condition in the if is true, and none of the statements are executed if the condition in the if is false. GLOSSARY 349 A boolean is a type of variable whose range of values is limited to true or false. This is the most appropriate return type for a method that uses its return value to report whether some condition exists. Brace; see curly braces. A break statement is a loop control device that interrupts processing o f a loop w h e n e v e r i t i s e x e c u t e d w i t h i n t h e c o n t r o l l e d b l o c k o f a loop control statement. When a break statement is executed, the flow of control passes to the next statement after the end of the controlled block. A byte is the unit in which data capacities are stated, whether in RAM or on a disk. In modern computers, a byte consists of eight bits. A byte-code instruction is one of the fundamental operations that a Java interpreter can perform. Some examples of these operations are addition, subtraction, or other arithmetic operations; other possibilities include operations that control what instruction will be executed next. All Java programs must be converted into byte-code instructions by a compiler before they can be executed by the Java interpreter. A byte-code program is a program in a form suitable for execution by the Java interpreter. C A cache is a small amount of fast memory where frequently used data are stored temporarily. Call; see method call. A called method is a method that starts execution as the result of a method call. Normally, it will return to the calling method via a return statement when finished. A calling method is a method that suspends execution as a result of a method call; the called method begins execution at the point of the method call. 350 GLOSSARY A char is a type of variable that can represent one character of text. It occupies two bytes of RAM. The c h a r i s s o m e t i m e s c o n s i d e r e d a n integer variable type and can be used for arithmetic, but this is not commonly done. In case you were wondering how to pronounce this term, the most common pronunciation has an a like the a in "married", whereas the ch sounds like k. Other pronunciations include the standard English pronunciation of "char", as in over-cooking meat, and even "car" as in "automobile". A checked exception is an exception that the design of Java assumes can happen only at certain reasonably predictable places in your program; therefore, you are required to let the callers of your methods know about the possibility of its occurring if you don’t handle it yourself. An example of a condition that can cause a checked exception is trying to open a nonexistent file for reading. See also unchecked exception. Child class; see inheritance, derived class. A class is a user-defined type; for example, StockItem is a class. A class definition tells the compiler what facilities the class supplies and how to implement those facilities. A class definition is found in a source-code file, which has the extension .java. A class implementation is the code that is responsible for actually doing the things that the objects of the class are supposed to do. See also class interface. A class interface is the set of operations that objects of that class can perform, as contrasted with the implementation of those operations. class scope describes the visibility of fields; that is, variables that are defined within a class rather than within a method. Such variables can be accessed by any method of that class; their accessibility to methods outside the class is controlled by the access specifier specified when they were defined in the class interface. A comment is a note to yourself or another programmer; it is ignored by the compiler. The symbol // marks the beginning of a comment; the comment continues until the end of the line containing the //. For GLOSSARY 351 those of you with BASIC experience, this is just like REM (the "remark" keyword); anything after it on a line is ignored. Compilation i s t h e p r o c e s s o f t r a n s l a t i n g s o u r c e c o d e i n t o a b y t e c o d e p r o g r a m, which is composed of byte-code instructions a l o n g with the data needed by those instructions. A compiler is a program that performs the process of compilation. Compile time m e a n s " w h i l e t h e c o m p i l e r is compiling the source code of a program". A constructor is a method that creates new objects of a (particular) class type. All constructors have the same name as the class for which they are constructors; therefore, the constructors for the StockItem class also have the name StockItem. A continuation expression is the part of a for statement computed before every execution of the controlled statement. The statement controlled by the for will be executed if the result of the computation is true, but not if it is false; see the entry for the for statement for an example. A controlled block is a block u n d e r t h e c o n t r o l o f a loop control s t a t e m e n t o r a n i f or else statement. The controlled block of a loop control statement can be executed a variable number of times, whereas the controlled block of an if or else statement is executed either once or not at all. Controlled statement; see controlled block. CPU is an abbreviation for Central Processing Unit. This is the "active" part of your computer, which executes the machine instructions that make the computer do useful work. The curly braces { and } are used to surround a block. The compiler treats the statements in the block as one statement. 352 GLOSSARY D Data are the pieces of information that are operated on by programs. The singular of "data" is "datum"; however, the word "data" is commonly used as both singular and plural. A debugger is a program that controls the execution of another program, so that you can see what the latter program is doing. The Visual J++ compiler on the CD-ROM in the back of this book includes a debugger. A default constructor is a method that is used to create an object when no initial value is specified for that object. For example, StockItem() is the default constructor for the StockItem class. A derived class is one that inherits methods and fields from a base class. See also inheritance. A digit is one of the characters used in any positional number system to represent all numbers starting at 0 and ending at one less than the base of the number system. In the decimal system, there are ten digits, 0 through 9, and in the hexadecimal system there are sixteen digits, 0 through 9 and a through f. A double is a type of floating-point variable that can represent a range of positive and negative numbers of magnitude from approximately 4.940656e–324 to approximately 1.79769e+308 (and 0), with approximately 16 digits of precision. Dynamic type checking refers to the practice of checking the correct usage of variables of different types during execution of a program rather than during compilation; see the type system entry for further discussion. Dynamic typing means delaying the determination of the exact type of a variable u n t i l r u n t i m e r a t h e r t h a n f i x i n g t h a t t y p e a t compile time as in static typing. This is the typing mechanism used for userdefined data types in Java. Please note that dynamic typing is not the same as dynamic type checking; Java has the former but not the latter. See the type system entry for further discussion. GLOSSARY 353 E An element is one of the variables that makes up an array. The keyword else causes its controlled block to be executed if the condition in its matching if statement turns out to be false at run time. An empty stack is a stack that currently contains no values. Encapsulation is the concept of hiding details inside a class implementation rather than exposing them in the class interface. This is one of the primary organizing principles that characterize objectoriented programming. An end user is the person who actually uses an application program to perform some useful or interesting task. See also application programmer, library designer. An exception is an unexpected event that occurs during the execution of a program, or in other words, an error. The idea behind exception handling can be expressed by the old saying: "Hope for the best, but prepare for the worst." In other words, rather than trying to handle every possible error condition in the normal flow of a program, we assume that everything will work all right. If it doesn’t, we have a fallback plan to handle the error. An expression is one of the units of which a statement is made. It is made up of one or more tokens. In the statement i = k + 3;, "k + 3" is an expression composed of the three tokens k, +, and 3. F The keyword false is a predefined value, representing the result of a conditional expression whose condition is not satisfied. For example, in the conditional expression x < y, if x is not less than y, the value of the expression will be false. See also boolean. A fence post error is a logical error that causes a loop to be executed one more or one less time than the correct count. A common cause of this error is confusing the number of elements in an array with the index of the last element. The derivation of this term is by analogy 354 GLOSSARY with the problem of calculating the number of fence sections and fence posts that you need for a given fence. For example, if you have to put up a fence 100 feet long, and each section of the fence is 10 feet long, how many sections of fence do you need? Obviously, the answer is 10. Now, how many fence posts do you need? 11. The confusion caused by counting fence posts when you should be counting segments of the fence (or vice versa) is the cause of a fence post error. To return to a programming example, if you have an array with 11 elements, the index of the last element is 10, not 11. Thus, confusing the number of elements with the highest index has much the same effect as the fence post problem. This sort of problem is also known, less colorfully, as an off-byone error. A field is a variable that is part of the definition of a class. It is viewed as "belonging" to the class. A float is a type of floating-point variable that can represent a range of positive and negative numbers of magnitude from approximately 1.401298e–45 to approximately 3.40282e+38 (and 0), with approximately 6 digits of precision. A floating-point variable is a Java approximation of a mathematical "real number". Unlike mathematical real numbers, Java floatingpoint variables have a limited range and precision, depending on their types; see the individual types, float and double, for details. A for statement is a loop control statement that causes its controlled block to be executed while a specified logical expression (the continuation expression) is true. It also provides for a starting expression to be executed before the first execution of the controlled statement, and a modification expression to be executed after every execution of the controlled statement. For example, in the for statement for (i = 0; i < 10; i ++), the initialization expression is i = 0, the continuation expression is i < 10, and the modification expression is i ++. GLOSSARY 355 H Hardware refers to the physical components of a computer, the ones you can touch. Examples include the keyboard, the monitor, the printer. The heap is the area of memory where objects are stored. Hex is an abbreviation for hexadecimal. A hexadecimal number system has 16 digits, 0–9 and a–f. I An identifier is a user-defined name; both method names and variable names are identifiers. Identifiers must not conflict with keywords such as if and for; for example, you cannot create a method or a variable with the name for. An if statement is a statement that causes its controlled block to be executed if the logical expression specified in the if statement is true. Implementation; see class implementation. To increment a variable m e a n s t o a d d 1 t o i t s v a l u e . T h i s c a n b e done in Java by using the increment operator, ++. An index is an expression used to select one of a number of elements of an array. It is enclosed in square brackets ([ ]). For example, in the expression a[i+1], the index is the expression i+1. An index variable is a variable used to hold an index. Inheritance is the definition of one class as a more specific version of another class which has been previously defined. The newly defined class is called the derived class (or sometimes the child class or the subclass), whereas the previously defined class is called the base class (or sometimes the parent class or the superclass). This book uses the terms "base" and "derived". The derived class inherits all of the fields and regular methods from the base class. Inheritance is one of the primary organizing principles of object-oriented programming. 356 GLOSSARY Initialization is the process of setting the initial value of a variable. It is very similar to assignment but is not identical: Initialization is done only once for each variable, when that variable is created, whereas assignment can be done as many times as desired. Input refers to the process of reading data into the computer from the outside world, or sometimes the process of providing data for a called method to operate on. A very commonly used source of input for simple programs is the keyboard. Instruction; see byte-code instruction, machine instruction. An int (short for integer) is a type of integer variable that can represent a whole number in the range –2147483648 (-2^31) to 2147483647 (2^31-1). It is 4 bytes long. An integer variable is a Java representation of a whole number. Unlike mathematical integers, Java integers have a limited range, depending on their types; see the individual types short, int, and long for details. The char type is also sometimes considered an integer variable, but is not generally used for arithmetic. I/O is an abbreviation for "input/output". This refers to the process of getting information into and out of the computer or a particular method in a program; see input and output for more details. J The Java interpreter is the program that controls the execution of Java programs. A Java program is actually a set of instructions to be used by the Java interpreter, describing the actions that would be taken by a hypothetical specialized computer that would solve the problem our program is designed to solve. When the Java interpreter is executed, it performs the actions specified by your Java program. java.io is the name of the package that tells the compiler how to compile code that uses predefined variables like System.out and their methods such as println. The Java program counter is a variable in the Java interpreter that h o l d s t h e a d d r e s s o f t h e n e x t byte-code instruction to be executed. During a method call, a call byte-code instruction pushes the contents GLOSSARY 357 o f t h e p r o g r a m c o u n t e r o n t h e s t a c k s o t h a t t h e called method can return to the calling method when it is finished executing. K A keyword is a word defined in the Java language, such as if and for. It is illegal to define an identifier such as a variable or method name that conflicts with a keyword; for example, you cannot create a method or a variable with the name for. L A library designer is a programmer who creates classes for application programmers to use in writing application programs. A literal value doesn’t have a name, but represents itself in a literal manner. Some examples are ’x’ (a char literal having the ASCII value that represents the letter x), and 5 (a numeric literal with the value 5). Local scope describes the visibility of variables that are defined within a method; such variables can be accessed only by code in that method.1 A logical expression is an expression that takes on the value true or false, rather than a numeric value. Some examples of such expressions are: x > y, which will be true if x has a greater value than y, and false otherwise; and a == b, which will be true if a has the same value as b, and false otherwise; see also boolean. A long is a type of integer variable that contains 64 bits (8 bytes) of data and therefore can represent a whole number in the range –9223372036854775808 (-2^63) to 9223372036854775807 (2^63-1). A loop is a means of executing a controlled block a (possibly variable) number of times, depending on some condition. The ______________________________________________________________________________ 1. In fact, a variable can be declared in any block, not just in a method; in that case, its scope is from the point where it is declared until the end of the block where it is defined. However, in this book all local variables have method scope, so this distinction is not critical here and omitting it simplifies the discussion. 358 GLOSSARY statement that controls the controlled block is called a loop control statement. This book covers the while and for loop control statements; see those headings for details. A loop control statement is a statement that controls the controlled block in a loop. M Machine address; see memory address. A machine instruction is one of the fundamental operations that a CPU can perform. Some examples of these operations are addition, subtraction, or other arithmetic operations; other possibilities include operations that control what instruction will be executed next. A memory address is a unique number identifying a particular byte of RAM. A method is a section of code defined in a class, and is viewed as belonging to that class . A m e t h o d h a s a n a m e , o p t i o n a l a r g u m e n t s, and a return type. The name makes it possible for one method to start execution of another one via a method call; the arguments are used to provide input for the method, and the return type allows the method to provide output to its calling method when the return statement causes the calling method to resume execution; see Figure 5.2 for a diagram illustrating a method call and return. A method call (or call for short) causes execution to be transferred temporarily from the current method (the calling method) to the one named in the method call (the called method). Normally, when a called method is finished with its task, it will return to the calling method, which will pick up execution at the statement after the method call. A method declaration is the first line of a method. It tells the compiler some vital statistics of the method. The method declaration consists of three required parts: a return type, the method’s name, and an argument list. There are also several optional parts called qualifiers which provide the compiler with more details about the method being declared. In this book, we use the static qualifier. GLOSSARY 359 Method overloading is the Java facility that allows us to create more than one method with the same name. So long as all such methods have different signatures, we can write as many of them as we wish, and the compiler will be able to figure out which one we mean. A modification expression is the part of a for statement executed after every execution of the controlled block. It is often used to increment an index variable to refer to the next element of an array; see the entry for the for statement for an example. N A nanosecond is one-billionth of a second. The new operator is used to allocate memory for objects. Non-display character; see non-printing character. A non-numeric variable is a variable that is not used in calculations like adding, multiplying, or subtracting. Such variables might represent names, addresses, telephone numbers, bank account numbers, or drivers license numbers. Note that just because something is called a number o r e v e n i s c o m p o s e d e n t i r e l y o f t h e digits 0–9, does not make it a numeric variable by our standards; the question is how the item is used. No one adds, multiplies, or subtracts drivers license numbers, for example; they serve solely as identifiers, and could just as easily have letters in them, as indeed some of them do. A non-printing character is used to control the format of our displayed or printed information, rather than to represent a particular letter, digit, or other special character. The space ( ) is one of the more important nonprinting characters. A null reference is a reference that isn’t pointing to anything at the moment. A numeric variable is a variable representing a quantity that can be expressed as a number, whether a whole number (an integer variable) or a number with a fractional part (a floating-point variable), and which can be used in calculations such as addition, subtraction, multiplication, or division. The integer variable types in 360 GLOSSARY Java are short, int, and long; each of these types can represent both negative and positive values (and 0). The floating-point variable types are float and double, which differ in their range and precision. Like the integer variable types, the floating-point types can represent either positive or negative numbers as well as 0; see the individual headings, float and double, for details on range and precision. O An object is a programming construct that represents a specific item of user-defined data that we wish to keep track of in a program. Note that an object is similar to a variable in that it represents an item of data. However, unlike a variable, we cannot use an object directly but must refer to it via a reference. Object-oriented programming is an approach to solving programming problems by creating objects to represent the entities being handled by the program, rather than relying solely on primitive data types. This approach has the advantage that you can match the language to the needs of the problem you’re trying to solve. For example, if you were writing a nurse’s station program in Java, you would want to have objects that represented nurses, doctors, patients, various sorts of equipment, and so on. Each of these objects would display the behavior appropriate to the thing or person it was representing. Off-by-one error; see fence post error. An operating system is a program that deals with the actual hardware of your computer; it supplies the lowest level of the software infrastructure needed to run a program. As this is written, MS-DOS (which is also the basis for Windows 95) is by far the most common operating system for Intel CPUs. It is followed by OS/2, Linux, and Windows NT, not necessarily in that order. An operator is one of the facilities of Java that is built into the language rather than being added on later, and therefore can have a name that does not conform to the rules for identifiers. Examples are +, -, and =. GLOSSARY 361 Output refers to the process of sending data from the computer to the outside world, or sometimes the process of returning data from a called method to its calling method. The most commonly used destination for program output is the screen. Overloading: see method overloading. A method in a derived class is said to override a base class method if the derived class method has the same signature (name and argument types) as the base class method. The derived class method will be called instead of the base class method when the method is called for an object of the derived class. A method in a derived class with the same name but a different signature from a method in the base class does not override the base class method. Instead, both the derived class method and the base class method are available for use with objects of the derived class.2 P A package contains the source code and byte code belonging to s e v e r a l source-code files , i n a f o r m t h a t t h e i n t e r p r e t e r c a n s e a r c h when it needs to find general-purpose methods. Parent class; see inheritance. Polymorphism is the major organizing principle in Java that allows us to implement several classes with the same class interface and treat objects of all these classes as though they were of the same class. This is the Java mechanism for dynamic typing. The word "polymorphism" is derived from the Greek words "poly", meaning "many", and "morph", meaning "form". In other words, the same behavior is implemented in different forms. Pop means "remove the top value from a stack". A primitive data type (also sometimes known as a "native" data type) is one that is defined in the Java language, as opposed to a user-defined data type (class). In Java, it is impossible to create userdefined data types that behave in the same way as primitive types. ______________________________________________________________________________ 2. C++ programmers should note this significant difference between the handling of inheritance in C++ and Java. 362 GLOSSARY print: see System.out.print. println: see System.out.println. The keyword private is an access specifier that allows access only by methods of the same class to a specified field or method. A program is a set of instructions specifying the solution to a set of problems, along with the data used by those instructions. Program counter: see Java program counter. Programming is the art and science of solving problems by the following procedure: 1. 2. 3. Find or invent a general solution to a set of problems. Express this solution as an algorithm or set of algorithms. Translate the algorithm(s) into terms so simple that a stupid machine like a computer can follow them to calculate the specific answer for any specific problem in the set. Warning: This definition may be somewhat misleading since it implies that the development of a program is straightforward and linear, with no revision. This is known as the "waterfall model" of programming, since water going over a waterfall follows a preordained course in one direction. However, real-life programming doesn’t usually work this way; rather, most programs are written in an incremental process as assumptions are changed and errors are found and corrected. The keyword protected is an access specifier. When present in a base class definition, it allows derived class methods access to fields and methods in the base class part of a derived class object, while preventing access by other methods outside the base class. The keyword public is an access specifier that allows methods of other classes access to methods and fields of its class. Push means "add another value to a stack". GLOSSARY 363 R RAM is an acronym for Random Access Memory. This is the working storage of a computer, where data and programs are stored while we’re using them. A reference is a variable that refers to an object and thus allows us to use that object. A register is a storage area that is on the same chip as the CPU itself. Programs use registers to hold data items that are actively in use; data in registers can be accessed within the time allocated to instruction execution, rather than the much longer times needed to access data in RAM. A regular method is any method that is not a constructor. A derived class inherits all regular methods from its base class. A retrieval method is a method that retrieves data, which may have been previously stored by a storage method or may be generated when needed by some other process such as calculation according to a formula. A return address is the memory address of the next byte-code instruction in a calling method. It is used during execution of a return statement in a called method to transfer execution back to the correct place in the calling method. A return statement is used by a called method to transfer execution back to the c a l l i n g m e t h o d . T h e r e t u r n statement can also specify a value of the correct return type for the called method; this value is made available to the calling method to be used for further calculation. An example of a return statement is return 0;, which returns the value 0 to the calling method. A return type tells the compiler what sort of data (if any) a called method returns to the calling method when the called method finishes executing. If no data is to be returned from the called method, the return type is specified as void. ROM is an abbreviation for Read-Only Memory. This is the permanent internal storage of a computer, where the programs needed to start up the computer are stored. As this suggests, ROM 364 GLOSSARY does not lose its contents when the power is turned off, as contrasted with RAM. Run time m e a n s " w h i l e a ( p r e v i o u s l y c o m p i l e d ) p r o g r a m i s b e i n g executed". S A scalar variable has a single value (at any one time); this is contrasted with an array, which contains a number of values, each of which is referred to by its index. The scope of a variable is the part of the program in which the variable can be accessed. The two scopes in Java are local and class; see the entry for each specific scope for more details. A s e l e c t i o n s o r t is a sorting algorithm that selects the highest (or lowest) element from a set of elements (the "input list"), moving that selected element to another set of elements (the "output list"). The next highest (or lowest) element is then treated in the same manner; this operation is repeated until as many elements as desired have been moved to the output list. A short is a type of integer variable that can represent a whole number in the range –32768 to 32767. It is two bytes long. The short-circuit evaluation rule governs the execution of the || and the && operators. See || and && for details. A side effect is any result of calling a method that persists beyond the execution of that method, other than providing a return value. For example, writing data to a file is a side effect. The signature of a method consists of its name and the types of its arguments. Every method is uniquely identified by the combination of its signature and the class to which the method belongs, which is what makes it possible to have more than one method with the same name; this is called method overloading. Software refers to the nonphysical components of a computer, the ones you cannot touch. If you can install it on your hard disk, it ’s GLOSSARY 365 software. Examples include a spreadsheet, a word processor, or a database program. Source code is a program in a form suitable for reading and writing by a human being. A source-code file contains source code statements that are turned into byte-code instructions by the Java c o m p i l e r. Source-code files have the extension .java. The space character ( ) is one of the non-printing characters (or non-display characters) that control the format of displayed or printed information. The s q u a r e b r a c k e t s , [ a n d ] , a r e u s e d t o e n c l o s e a n array index, which selects an individual element of the array. A stack is a data structure with characteristics similar to a springloaded plate holder such as you might see in a cafeteria. The last plate deposited on the stack of plates will be the first one to be removed when a customer needs a fresh plate; similarly, the last value deposited (pushed) onto a stack is the first value retrieved (popped). The stack pointer is a dedicated register. The stack pointer is used to keep track of the most recently pushed value on the stack. A s t a r t i n g e x p r e s s i o n is the part of a f o r s t a t e m e n t e x e c u t e d o n c e before the controlled block of the for statement is first executed. It is often used to initialize an index variable to 0, so that the index variable can be used to refer to the first element of an array; see the entry for the for statement for an example. A statement is a complete operation understood by the Java compiler. Each statement is ended with a semicolon (;). A static method is a method that can be called without reference to a n o b j e c t o f i t s c l a s s. Because such a method call is not connected with any particular object of its class, a static method cannot refer to fields of the class. S t a t i c t y p e c h e c k i n g refers to the practice of checking the correct usage of variables of different types during compilation of a program 366 GLOSSARY rather than during execution. Java uses static type checking; see the type system entry for further discussion. Note that this has no particular relation to the keyword static. Static typing means determining the exact type of a variable when the program is compiled. It is the typing mechanism used for primitive variables in Java. Note that this has no particular relation to the keyword static, and also that it is not the same as static type checking; see the type system entry for further discussion. Stepwise refinement is the process of developing an algorithm by starting out with a "coarse" solution and "refining" it until the steps are within the capability of the Java language. Storage; syn. memory. A storage class is the characteristic of a variable that determines how and when a memory address is assigned to that variable. In Java, primitive variables are usually stored on the stack, while user-defined data types always store their data on the heap. Please note that the term storage class has nothing to do with the Java term class. A storage method is a method that stores data for later retrieval by a retrieval method. Subclass: syn. derived class. The keyword super refers to the base class of the current class. One use of this keyword is to specify which base class constructor a derived class constructor wishes to call to initialize the base class part of the derived class. Another use of this keyword is to call a method i n t h e b a s e class from a derived class method, when the base c l a s s method has the same name as a method in the derived class. In such a case, if we didn’t use the super keyword the derived class method would be called rather than the base class method. Superclass: syn. base class. System.out.print is a method that displays its argument on the screen. System.out.println is a method that displays its argument on the screen and then moves the output position to the next line. GLOSSARY 367 T A token is a part of a program that the compiler treats as a separate unit. It’s analogous to a word in English; a statement is more like a sentence. For example, StockItem is a token, as is (. On the other hand, x = 5; is a statement. The keyword true is a predefined value representing the result of a conditional expression whose condition is satisfied. For example, in the conditional expression x < y, if x is less than y, the value of the expression will be true. The type of a class variable is the class to which it belongs. The type of a primitive variable is one of the predefined variable types in Java; see integer variable, floating-point variable, and boolean for details on the primitive types. The type system refers to the set of rules that the compiler uses to decide what uses are legal for a variable of a given type. In Java, these determinations are made by the compiler (static type checking). This makes it easier to prevent type errors than it is in languages where type checking is done during execution of the program (dynamic type checking). P l e a s e n o t e t h a t J a v a h a s b o t h s t a t i c t y p e c h e c k i n g and dynamic typing. This is possible because the set of types that is acceptable in any given situation can be determined at compile time, even though the exact type of a given variable may not be known until run time. U An unchecked exception is an exception that the design of Java assumes can happen virtually anywhere in your program, so it wouldn’t make sense to force you to specify that your methods can throw such an exception. Examples of conditions that cause unchecked exceptions are attempting to use a null reference, trying to access an element of an array that is past the end of the array, and the like. We can handle unchecked exceptions in our methods if we wish, but we don’t have to; whether or not we handle them, we don’t have to declare that we will throw them, because any method is permitted to throw them. See also checked exception. 368 GLOSSARY An uninitialized variable is one that has never been set to a known value. Attempting to use such a variable is an error that will be caught either at compile time (in most cases) or at run time. An unqualified name is a reference to a field or a method that doesn’t specify which object the field belongs to. When we use an unqualified field name in a method, the compiler assumes that the object we are referring to is the object for which that method has been called. When we use an unqualified method name, the compiler assumes that we mean the method of that name in the current class. The term user has several meanings in programming. The primary usage in this book is application programmer. However, it can also mean library designer (in the phrase user-defined data type) or even end user. A user-defined data type is one that is, well, defined by the user. In this usage, however, "user" means "someone using language facilities to extend the range of variable types in the language" (i.e., library designer). The primary mechanism used to define a user-defined type is the class. V A value argument is a variable of local scope created when a method begins execution. Its initial value is set to the value of the corresponding argument in the calling method. Changing a value argument does not affect any variable in the calling method, except in some cases when that value argument is of reference type. A variable is a programming construct that represents a specific item of data that we wish to keep track of in a program. Some examples are the weight of a pumpkin or the number of cartons of milk in the inventory of a store. A void return type specifier in a method declaration indicates that the method in question does not return any value when it finishes executing. GLOSSARY 369 W A while statement is a loop control statement that causes its controlled block to be executed while a specified logical expression is true. Z Zero-based indexing refers to the practice of numbering the elements of a n a r r a y starting at 0 rather than 1. Although it might seem arbitrary to start counting at 0 rather than at 1, there are historical reasons for this decision, stemming from the assembly language ancestry of the C language. 370 INDEX INDEX 371 Index < 71, 101 = 41, 81, 82 > 101 [ 104 ] 104 { 75 } 75 != 101 B ++ 110 += 141 -= 225 // 40 <= 101 == 101 >= 101 || 248 base class 259 (See Also: inheritance) base class initializer 283-287 base class part 260 base class reference 300 binary 10, 12-13, 17-31 bit 12 block 34, 75 boolean 225 brace (See: {, }) break 219 byte 12, 171, 324 byte-code 80 byte-code instruction 4 byte-code program 4, 40 A C access specifier 180-183, 186-189 address (See: memory address) algorithm 3, 76, 94-102 applets 326 application programmer 261 argument 154-164 argument list 152, 154 array 93, 94, 203, 220, 238, 324 assignment 41 called method 144, 146-169 calling method 144, 146-169 catch 208 char 60-64 character sets 61 checked exception 215 class 62, 143 class definition 172, 177 class file 144 class implementation 185-250 class interface 172 class scope 186-189 comment 40 compilation 4, 38 compile time 43-52 372 INDEX compiler 4 constructor 180, 184 continuation expression 109 controlled block 85, 109 controlled statement (See: controlled block) CPU 11, 28 curly brace (See: {, }) , 75 floating-point variable 17, 108 (See Also: double, float) for loop 109 for statement 94, 109 D H data 12 data type user-defined (See: class) DatedStockItem 260-319 debugger 74 default constructor 180, 184, 189 derived class 259 (See Also: inheritance) digit 10 disk 11 double 171, 324 Dvorak keyboard 108 dynamic type checking 64 dynamic typing 295 hardware 4, 9-31, 35, 166, 167 hex (See: hexadecimal) hexadecimal 10, 22-28 E element 93, 103-135, 203, 221-222 empty stack 45 encapsulation 172 exception handling 207 expiration date 268-272 expression 34 F false 214 fence post error 221-223 field 172, 178-179, 195-196 file input 204-223, 237, 239 file output 255 FileInputStream 206-226 float 171, 256, 324 G garbage collection 203 garbage collector 203 I I/O 66 identifier 33, 153 if statement 34, 70 implementation (See: class implementation) incrementing 110 index 104-135 index variable 107 (See Also: index) inheritance 259-319 inheritance for extension 261, 293 inheritance for reimplementation 261-293, 295-319 initialization 96, 120, 159, 180, 184, 185, 187, 189, 190 input 118, 133-135, 137 instruction (See: machine instruction, byte-code instruction) int 40 integer variable (See: char, short, int, long) interface 325 interpreter 4 iteration 113 INDEX J Java interpreter 5 java.io 167, 194 java.lang 167 Java program counter 168 K keyword 33, 154 L literal 41 local variable (See: local scope) logical expression 214 long 171, 324 loop (See: for loop, while loop) loop control statement (See: for loop, while loop) M machine address (See: memory address) machine code (See: machine instruction) machine instruction 110, 141 Mega 13 memory (See: memory address) memory address 15-17 memory leak 203 method 144, 146-169, 179-195, 212-219 method call 144, 146-169 method declaration 152, 158 method overloading 191 method table 301-308 modification expression 109 mtable 300-310 multiple inheritance 325 N non-display characters 65 non-numeric variable 36 373 non-printing characters 65 numeric variable 17 (See Also: integer variable, floatingpoint variable) O object 172, 174-196 object-oriented programming 172 octal 22 off-by-one error (See: fence post error) operating system 165 operator 33 output 76, 167, 248 override 259, 273 P package 143, 145, 167, 206 polymorphism 295-319 pop 45 primitive variable 171-183 print (See: System.out.print) println (See: System.out.println) private 187-189, 195, 226 processor (See: CPU) protected 260, 276-278 protected method 282 public 180-183, 187-189, 195, 227 push 44 R RAM 11, 14 reference 172, 300, 296-310, 316-319 regular method 259 Reorder 260, 267, 296-319 retrieval method 147 return address 167 return statement 144, 148-153 return type 152, 153, 194 ROM 16 374 S scalar (See: scalar variable) scalar variable 105 scope (See: class scope, local scope) selection sort 118 short 17, 20, 27, 28, 31 short-circuit evaluation 248 side effect 149 signature 191 software 4 source code 40 source-code file 143, 146, 172 source-code program 4, 38 square bracket 104 stack 44-168 starting expression 109 statement 34, 40 static method 260, 282 static type checking 64, 153 static typing 295 stepwise refinement 6 StockItem 174-258, 259-319 storage (See: memory address) storage method 147 subclass (See: derived class) subscript 104 superclass (See: base class) System.out.print 68 System.out.println 67 T threads 325 INDEX Today 282 token 34 true 214 try 207 try block 207 type 171-177 type system 64 U unchecked exception 215 Unicode standard 61 uninitialized variable (See: initialization) user 261 user-defined data type (See: class) V value argument 155-158 variable 10, 22 W while loop 34, 71, 109 while statement (See: while loop) Y Year 2000 problem 128-132, 268, 313 Z zero-based indexing 105