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