Enumeration COMP 14 Prasun Dewan1 11. Indexing and Enumeration We often refer to individual elements of a sequence using indexing, e.g.: Question 1, Question 2, Question 3.We will learn, in this chapter, an analogous concept supported by Java, illustrating it on strings, which are essentially sequences of characters. We will also look at a new kind of looping statement, called a for loop, which is more convenient than a while loop in problems involving indexing. Indexing is only one technique for providing access to the elements of an ordered list. Another powerful technique is to enumerate the elements in sequence, from the first to the last. It is less flexible than indexing because the elements have to be accessed in order. However, for several classes of problems, it is the more appropriate technique. We will look here at one important class of such problems: the task of scanning a string of characters. This kind of problem arises in a variety of applications. For instance, a Java compiler scans a program to find identifiers, literals, and operators; and a spelling checker scans a document for errors. We will study a simple instance of this problem scanning a string looking for upper case letters, which nonetheless will illustrate general techniques for scanning and is perhaps the most complex problem discussed in this book. In solving this problem, we will write new kinds of loops those that are both counter- and event- controlled. Indexing To understand the need for indexing, let us return to our discussion of structured objects. Recall these are objects that can be decomposed into simpler components. For example, an instance of ALoanPair is a structured object that can be decomposed into the simpler components, carLoan and houseLoan of type Loan. We saw two kinds of components: logical components or properties, which are visible outside the class of the object; and physical components or instance variables, which should be visible only inside the class. An important structured object we have already used is the class String: Each character in a String instance is a logical component of it. How should these components be accessed from outside the class? Like ALoanPair, String could define a separate getter method for each of its components: public char getFirstChar(); public char getSecondChar(); … A problem with this approach is that it is not clear when we should stop, that is, how many such getter methods we should define? The number of components defined by the class String is variable, that is, different instances of it can have different number of components. For instance, “h” has one component, while “hello world” has 11 components. We did not face this problem in the definition of ALoanPair, since all instances these types have the same number of (logical) components. The solution is based on the observation that we can identify string components through, not their names, but their positions or indicies in the string. Instead of making the component identifier part of the name of its getter method, String makes it a parameter of the getter method. Thus, String does not provide different getter methods for different components but a single getter method that takes the component index as an argument and returns a character: public char charAt ( int); To be consistent with the naming scheme for getter methods of fixed structures, it would have been better to call this method, getCharAt, but the class String, like several other predefined Java classes we will see later, was defined before this scheme was established. String indices start from 0 rather than 1. Thus, if: 1 1 Copyright Prasun Dewan, 2000. Enumeration String s = “hello world” s.charAt(0) == ‘h’; s.charAt(1) == ‘e’ In general, we access the ith character of string, s, as: s.charAt(i-1); Not all string indices are legal. An index that is smaller (greater) than the index of its first (last) character is illegal. Thus, both of the following accesses will raise a StringIndexBounds exception : s.charAt(11) s.charAt(-1) The instance function, length(), returns the number of characters in a string. Thus: "helloworld".length() == 11 "".length() == 0 We can use this function to define the range of legal indices of an arbitrary string s: 0 .. (s.length() - 1) Sub-Strings Besides individual characters, we may also wish to retrieve sub-strings of a string, that is, sequences of consecutive characters that appear in the string. The Java function: public String substring (int beginIndex, int endIndex) when invoked on a string, s, returns a new string that consists of the character sequence starting at beginIndex and ending at endIndex – 1, that is: s.charAt(beginIndex) .. s.charAt(endIndex - 1) It raises the StringIndexOutOfBounds exception if beginIndex is greater than endIndex. If they are both equal, it returns the empty string. Thus: "hello world".substring(4, 7) "o w" "hello world".substring(4, 4) "" "hello world".substring(7, 4) throws StringIndexOutOfBounds While String provides getter methods to read string characters and sub strings, it provides no setter method. This is because Java strings are readonly or immutable, that is, they cannot change. A separate class, StringBuffer, which we will not study in this course, defines mutable strings. The class String does, as we have seen before, provide the + operation on strings to create new strings from existing strings. Thus: “hello” + “world” == “hello world” Here, we do not change either string, but instead, create a new string that stores the result of appending the second string to the first one. String provides several other useful methods. Two of them, relevant to the scanning problem we will solve here, are toUpperCase() and toLowerCase(), which can be invoked on a string to return another string that has the same letters as the first one, except that they are all in upper case and lower case, respectively. Thus: "Hello World".toLowerCase() == "hello world" Variable-Size Type vs Variable-Size Instance A particular string, like all the other objects we have seen so far, has a fixed structure, that is, its size (and, in fact, contents) is fixed during its lifetime. In contrast, as we have seen above, the type String defines a variable structure, that is, different instances of it can have different sizes. Thus, types can define fixed or variable structure; and a type with a variable structure can have instances with fixed or variable structures. All the primitive types and some of the object types we have seen have fixed structure. String (and array types we will study in the next section) define variable structures, but their instances have fixed structure. Later, we will see types with variable structure whose instances also have variable structure. We will refer to these three kinds of types as fixed, variable, and dynamic types, respectively. 2 Enumeration For Loop The following program fragment illustrates string manipulation, printing the characters of a string in separate lines: int i = 0; // initialization of loop variables while (i < s.length()) { // continuation condition System.out.println (s.charAt(i)); // real body i++; // resetting loop variables } This is an example of a counter-controlled loop, since the variable i serves as a counter that is incremented in each iteration of the loop. It is also an example of an index-controlled loop, which is a special case of a counter-controlled loop in which the counter is an index . Let us look at this code in more depth to grasp the general nature of a loop. We can decompose it into the following components: 1. Continuation condition: the boolean expression that determines if the next iteration of the loop should be executed. 2. Initialization of loop variables: the first assignment of variables that appear in the continuation condition and are changed in the loop body. 3. Resetting loop variables: preparing for the next iteration by reassigning one or more loop variables 4. Real body: the rest of the while body, which does the "real-work" of each iteration. The while loop separates the continuing condition from the rest of the components, thereby ensuring we do not forget to enter it. However, it does not separate the remaining components - we have added comments to do so in the example above. As a result, we may forget to create one or more of these components. For instance, we may forget to reset the loop variables, thereby creating an infinite loop. Java provides another loop, the for loop or for statement, illustrated below, which explicitly separates all four components: for (int i = 1; i < s.length(); i++) { System.out.println (s.charAt(i)); } This loop is equivalent to the program fragment above. In general, a for statement is of the form: for (S1;E;S2) S3 It is equivalent to: S1; while (E) { S3; S2; } In other words, the for loop first executes S1 and then essentially executes a while loop whose condition is E and whose body is S3 followed by S2. S1 and S2 are expected to initialize and reset, respectively, the loop variables. In comparison to a while loop, a for loop is more compact. More important, its structure reminds us to enter S1 and S2, that is, initialize and reset the loop variables. Any of the three parts of a for loop, S1, E, and S2 can, in fact, be missing. A missing S1 or S2 is considered a null statement, while a missing E is considered true. Thus: for (;;) { S3; } is equivalent to: while (true) { S3; } The break statement can be used to exit both kinds of loops. 3 Enumeration Scanning Consider a more interesting string-manipulation example, which allows a user to enter a string and prints the upper case letters in the string, as shown in Figure 1. Figure 1 Finding Upper Case Letters This is a simple example of a problem that involves scanning an input stream for tokens. A stream is a sequence of values of some type, T; and a token in a sequence of consecutive stream elements in which we are interested. The sequence of tokens generated from the input stream forms another stream called the token stream. Thus, the process of scanning involves converting an input stream to a token stream. Input stream token token Token stream Figure 2 The Concept of Scanning We saw concept of scanning before, when we used DataInputStream. Recall that an an instance of this class, dataIn, converted the sequence of characters entered by a user in a terminal window to a sequence of lines, as shown in Figure 3. Multi-line input stream Line 1 Line 2 Line stream Figure 3 Scanning the Stream of Input Characters for Lines In the problem we have to solve, the input stream is the sequence of characters in the next input line and the tokens in which we are interested are upper case letters. The tokens are one-character long since each uppercase letter forms a separate token, as shown in Figure 4. Our job, in this chapter, will be to write a scanner for it. 4 Enumeration J o token h n F token . K e n token n e d y Input Line Uppercase Letters Figure 4 Scanning a Line of Characters for Upper Case letters Scanning problems occur in many familiar domains. As we have seen, interactive programs scan the input for lines, integers, and other kinds of values. Similarly, file-based programs must often scan a file for various values. Word processors scan documents for occurences of a particular string. Perhaps the most important application is in compilers, which scan programs for identifiers, literals,operators, and other tokens. Scanners In general, such applications can be conceptually broken into two parts: one that converts the input stream to a token stream, and another that actually processes the input stream by, for instance, printing it on the screen. While it is possible to create a single object that performs both tasks, algorithms that do so tend to be messy. Therefore, we will develop a solution that creates separate objects for the two parts. The object that performs the first task, converting the input stream to a token stream, is called a scanner. A data input stream is an example of such an object, converting, as we saw above, a character stream to a line stream. Before we write our own scanner, let us look at an application that uses a data input stream as a scanner: DataInputStream dataIn = new DataInputStream (System.in); int product = 1; while (true) { int num = Integer.parseInt (dataIn.readLine()); if (num < 0) break; product = product*num; } System.out.println (product); This familiar code is fairly straightforward. It processes input lines until a negative sentinel is entered, converting each line to the corresponding number, and printing out the product of these numbers. The reason it is simple because it is using a separate object, dataIn, to produce the line tokens. Let us look in more depth at how dataIn is used in the program to understand how a scanner is constructed and used First, let us consider how the object is constructed: DataInputStream dataIn = new DataInputStream (System.in) System.in is a Java object that represents the stream of characters input by the user. It forms the input stream of dataIn, and is, therefore, passsed as an argument to the constructor of the object. The scanner produces elements of the token stream on demand, returning the next element each time the instance method readLine() is invoked on it. The first invocation returns the first token in the stream, and each subsequent invocation returns the token following the last one it returned. Thus we see here how the concept of a scanner allows us to separate the parts of the program that scans for tokens and the part that processes them. The latter part simply instantiates the scanner, passing it the input stream in the constructor, and calls a method to repeatedly request successive elements of the output token stream. If we could create a scanner for the input and token streams of our problem, then we would have a similar separation in our problem. 5 Enumeration Enumerations Let us consider, first, the methods we should be able to invoke on the scanner. We need a method like readLine() that returns the next token. Our tokens are characters, not lines; thus the return type of this function is a char and not a String. We are not necessarily reading user input, simply asking for next element in the token stream; therefore, let us name it nextElement rather than readElement. The method declaration, then is: public char nextElement(); One of the problems with dataIn is that it does not provide a convenient way to determine if there in another line left in the token stream. It raises an IOException if we ask for another line and the user has terminated input. This is bad design, since exceptions should be raised on truly exceptional conditions, not on normal termination of input. To avoid using exceptions, our algorithm had to implement the notion of a sentinel value, a detail best left out of users of the scanner. We will overcome this problem of DataInputStream by defining an explicit scanner operation that lets us know if there are any more elements left in the token stream: public boolean hasMoreElements(); Thus, the scanner implements two operations: one that returns the next element in the token stream and another that indicates if the stream has more elements. We can now define the interface of our scanner: public interface CharEnumeration { public char nextElement(); public boolean hasMoreElements(); } Again, we are diverging from the design of the class DataInputStream. This class does not implement an interface, thereby not giving us the associated benefits such as polymorphism and ease of change. The interface, CharEnumeration, is an example of an enumeration interface. To better understand the nature of such an interface, here is another example of it, which produces string rather than char tokens public interface StringEnumeration { public String nextElement(); public boolean hasMoreElements(); } Thus is the interface DataInputStream should have implemented. An enumeration interface defines an operation to return the next element in a stream and another to determine if there are more elements in the stream. Different enumeration interfaces differ in the type of the elements produced by them. We will use the same names for the two enumeration operations in all enumeration interfaces we create. Thus, the headers of these operations will be: public <Type> nextElement(); public boolean hasMoreElements(); where <Type> is some Java type – primitive or object – defining elements of the token stream. When we study Vectors, you will see another example of this interface, called Enumeration, which is used extensively in Java. Enumeration is an alternative to indexing for accessing elements of (an instance of) a variable type. It is somewhat more rigid than indexing in that the elements must be accessed sequentially from first to last. Indexing, on the other hand, allows random access, that is, allows us to access the elements in any order. It is better to use enumeration instead of indexing in a scanner application because (a) it has a simpler implementation and (b) lack of random access is not a problem since the enumerated elements are processed in order. Let us look more closely at CharEnumeration, the interface we must implement in this problem. It allows any character to be enumerated – not just upper case letters. The reason is that the type of the enumerated elements is 6 Enumeration char, which includes all characters – upper case letters, lowercase letters, numbers, space, and so on. It will be the responsibility of the implementation to restrict these characters to uppercase letters. Let us call the implementation, therefore, AnUpperCaseEnumeration. Like DataInputStream, its constructor takes the input stream as an argument, which in this example is a string Thus: new AnUpperCaseEnumeration (s) creates a scanner that produces all the upper case letters in string s. Using an Enumeration Before we define AnUpperCaseEnumeration, let us see how it (and the interface it implements) is used in the top level of our solution, that is, the main class: public class UpperCasePrinter { public static void main (String args[]) { String input = getInput(); printUpperCase(input); } public static String getInput() { System.out.println("Please enter a string"); return Keyboard.readLine(); } public static void printUpperCase(String s) { System.out.println("Upper Case Letters:"); printChars (new AnUpperCaseEnumeration(s)); } public static void printChars (CharEnumeration charEnumeration) { while (charEnumeration.hasMoreElements()) System.out.print(charEnumeration.nextElement()); } } Figure 5 Using an Enumeration As before, we have been careful to separate the input and output code in the class. It is the output code that uses the enumeration. It creates a new instance of AnUpperCaseEnumeration for scanning the input string returned by getInput(). It then repeatedly invokes nextElement() on the scanner to output the stream of upper case letters in the string. It stops when there are no more elements in the stream, that is, the method hasMoreElements() returns false. We are now ready to develop the scanner implementation, AnUpperCaseEnumeration. The following sections discuss it in depth. However, to better appreciate the solution given here, it is best to first try to develop one on your own. Scanner Data Structures The scanner's job is to scan each character of the string from left to right, looking for upper case letters. Let us first consider the variables or data structures it needs to do its job. Clearly, it needs the string to be scanned. It also needs a marker variable to tell it how much of the string it has scanned so far, that is, which characters it has already looked at. 7 Enumeration J o h n F Scanned Line . K e n n e d y Input Line UnScanned Line marker Figure 6 Scanner Marker Every time it is asked for a new element in the token stream, the scanner advances the marker. There is some flexibility in how much of the string is scanned when the scanner is asked for a new element. Two choices are: At next element: The string is scanned to the next element to be returned by the scanner. When the scanner is asked for the new element, it returns the token at the marked position and advances the marker to the next token. Thus, in the figure above, it would return the letter 'F' and advance the marker to 'K'. At last element: The string is scanned to the last element returned by the scanner. When the scanner is asked for the new element, it advances the marker to the next token and returns this token. Thus, in the example above, it advances the marker to the letter 'K' and returns 'K'. These two approaches correspond to advancing a loop counter at the end or start of the loop body. In both approaches, when the marker is advanced, there may be no more elements left to return. In this case, we will let the marker go beyond the end of the input stream. The first approach makes it easier to determine if a stream has more elements, since all we have to do is check if the marker is beyond the end of the input stream. The function, hasMoreElements(), thus, can be a pure function without the side effect of changing the global marker variable. Therefore, we will use it in our solution, and call the marker variable nextElementPos,since it stores the position of the next element to be returned. If there is a next element, it stores the index of this element; otherwise it stores an index beyond the string. Scanner Algorithms Now that we have identified the data structures of a class, let us next determine the algorithms used by the operations defined by the class. In general, when tacking a complex problem such as scanning a stream, it is best to first consider the data structures we need, then the algorithm, and finally the code. The algorithm for hasMoreElements() is straightforward. 1. Return true if nextElementPos is beyond end of string; false otherwise. The algorithm for for nextElement() is more complicated: 1. 2. 3. Return the element at nextElementPos. Move nextElementPos beyond the element returned. Skip to the next upper-case letter or end of string, whichever comes first. To illustrate these steps, assume that nexElementPos is at ‘F’. 8 Enumeration J o h n F . K e n n e d y Input Line nextElementPos Consider what happens when the scanner is asked for the next element. It first returns ‘F’. Next it advances the marker beyond the returned element: J o h n F . K e n n e d y Input Line nextElementPos All it did was increment nextElementPos, since the length of the returned token is 1 character. In general, it would need to determine the length of the token and add it to the marker. If the next character were an uppercase letter, it would stop here. However, as we see here, an upper case letter may not be followed immediately by another upper case letter. Therefore, we must perform the third step of skipping nonuppercase letters. J o h n F . K e n n e d y Input Line nextElementPos The next time the scanner is asked for an element, it will return the upper case character at the new position of nextElementPos. In this example, there was a remaining (unscanned) upper case letter in the string. If this was not the case, nextElement() should skip beyond the end of the string, so that hasMoreElements() can return false. Scanner Code We are finally ready to code the scanner class: public class AnUpperCaseEnumeration implements CharEnumeration { String string; int nextElementPos = 0; public AnUpperCaseEnumeration(String theString) { string = theString; skipNonUpperCaseChars(); } 9 Enumeration public boolean hasMoreElements() { return nextElementPos < string.length(); } public char nextElement() { char retVal = string.charAt(nextElementPos); movePastCurrentElement(); skipNonUpperCaseChars(); return retVal; } void movePastCurrentElement() { nextElementPos++; } // keep advancing nextElementPos until we hit the next upper case or go // beyond the end of the string. void skipNonUpperCaseChars() { while (nextElementPos < string.length() && !Character.isUpperCase(string.charAt(nextElementPos))) nextElementPos++; } } Figure 7 Scanner Code Each instance of this class enumerates its own stream of uppercase letters. The instance variable, string, stores the string being scanned, and the instance variable, nextElementPos is the marker for this string. Both variables are initialized by the constructor, which takes as an argument the string to be scanned. The constructor stores its argument in string, and advances nextElementPos to the first uppercase character. The other methods follow from the algorithm described earlier. The function nextElement() is impure – it returns the character at nextElementPos and also changes this variable. Thus, it performs the kind of side effect we had banned in functions – changing a global variable. This is an example of the kind of side effect readLine() also performs – returning a token from the stream and changing the token marker. Since, as a Java programmer, we are used to such a side effect when processing a stream, we make an exception for it – allowing it to change a global variable. As we know from using readLine(), it is convenient to call one method to perform both tasks, since, we rarely perform one without the other. However, we must be careful to perform them in the correct order. When nextElement() is called, nextElementPos is at the character the function must return. Therefore, we use the marker to retrieve the character before we calculate its new value. The retrieved character is stored in the variable retVal, which is then returned at the end of the function. nextElement() expects to find another element in the token stream. It is the job of the user of the enumeration to check that it hasMoreElements() before calling nextElement(). To detect upper case letters, the class uses the method, isUpperCase, of the predefined Java class, Character, which returns true if its argument is an upper case letter. Event and Counter-Controlled Loop Consider the loop in skipNonUpperCaseLeters(). When it exits depends on two factors: Counter: The counter, nextElementPos. If it goes past the end of the string, the loop terminates. Event: The event of finding an upper case letter. As soon as this event occurs, the loop terminates. 10 Enumeration Earlier we had seen loops that were either counter-controlled or event-controlled. As we see here, it is possible to have loops with both kinds of control. Let us look more closely at the condition of the loop: (nextElementPos < string.length() && !Character.isUpperCase(string.charAt(nextElementPos))) We need to make sure that: string.charAt(nextElementPos) does not give a subscript error, that is, the index, nextElementPos is in the range: 0 .. string.length() – 1 Are we guaranteed that? nextElementPos starts at 0 and is never decremented. Therefore, it will never be less than 0. Can it become greater than string.length() – 1 in this procedure? If there are no remaining uppercase letters when the procedure is called, then this loop will make it string.length on the last iteration. If it is then used to index the string, we would indeed get a subscript error. Fortunately, however, when this happens, the first operand of &&: nextElementPos < string.length() becomes false. Since && is a short-circuit operation, it does not evaluate the second operand, and thus does not use nextElementPos to index the string. As a result, we do make sure that the string index is in the correct range. This would not be the case if we reversed the order of the operands of && or used the & operation. In loops that are both event and index-controlled, the event condition often causes a subscript error on the last iteration, and should therefore be made the second operand of a short-circuit operation. We see here two more examples of methods that are not declared public. Both skipNonUpperCaseLetters() and movePastCurrentElement() are created as internal methods because they not meant to be called directly from outside the class. If they were declared public, we would not follow the principle of least privilege, since there is no need for other classes to call them. In fact, if these methods were called directly by other classes, the two public methods could fail to work correctly (see exercise 5). Why Separate Enumerator We have now seen both parts of our solution, the scanner user (Figure 5) and the scanner implementation (Figure 6). In fact, we could have combined these two parts in one class, as shown in Figure 7. public class UpperCasePrinter { public static void main (String args[]) { String input = getInput(); int index = 0; System.out.println("Upper Case Letters :"); while (index < input.length()) { char nextChar = input.charAt(index); if (Character.isUpperCase(nextChar)) System.out.print(nextChar); // token processing index++; } } public static String getInput() { System.out.println("Please enter a string"); return Keyboard.readLine(); } } Figure 8 Single-Class Solution Here enumeration and printing of the uppercase characters are combined in the main method. The method creates a loop that looks at all the characters of the string, from the leftmost to the rightmost, and prints all uppercase characters it finds. 11 Enumeration This program is much simpler and easier to understand. Its main disadvantage is that it combines enumeration of elements of a stream and processing of them. In this example, the processing consists of simply printing the elements. But we might wish to do other things with the tokens. For instance, we may wish to concatenate all the tokens into a string. In our original program, we would use the same enumeration interface and class, changing only the token processing: String input = getInput(); String s = ""; CharEnumeration charEnumeration = new AnUpperCaseEnumeration(input)); while (charEnumeration.hasMoreElements()) { s += charEnumeration.nextElement(); } Figure 9 Reuse of Scanner In the new version, as enumeration and processing are tightly integrated in the loop, we would not be able to execute the original enumeration code. We would be forced to create a brand new loop consisting of the old enumeration code and the new processing code: String input = getInput(); String s = ""; // processing code int index = 0; while (index < input.length()) { char nextChar = input.charAt(index); if (Character.isUpperCase(nextChar)) s += nextChar; // processing code index++; } Figure 10 Code Duplication with Single-Class Solution Of course, we can use copy and paste capabilities of an editor to reuse lines of the previous example. But recall that cut and paste is more work that direct reuse, and requires us to update all copies of the enumeration code if we decide to change it. To better appreciate the problems of combining scanning and processing of tokens, consider the more complex example of scanning the user input for lines. Currently, Java separates these two tasks, providing a separate class, DataInputStream, for reading lines, which we can import in different classes that process these tokens. If instead, it had decided to combine the two tasks, it would have provided us with a loop in which tokens would be generated. Our task would be add processing code into this loop: initialize while there is more input set next line; user code to process next line move next line markers This means that all of our classes that process lines would have this program fragment. Moreover, as readLine() is deprecated and replaced by an alternative method, we would have to change all classes that have this code! Enumerating Vs Scanning Scanning a stream is only one way to enumerate a sequence of values. The following, alternative, implementation of CharEnumeration illustrates that there are other ways: public class AnotherUpperCaseEnumeration implements CharEnumeration { char nextLetter = 'A'; public boolean hasMoreElements() { 12 Enumeration return nextLetter <= 'Z'; } public char nextElement() { char retVal = nextLetter; nextLetter = (char) (nextLetter + 1); return retVal; } } Figure 11 Enumeration Implementation without Scanning An instance of this class enumerates all upper case letters. Instead of scanning characters in a string, it calculates them using their ordinal numbers. The first time nextElement() is called, it returns ‘A’. Each subsequent time, it returns the character following the one it returned last time it was called. The variable nextLetter stores the letter to be returned by the next call to nextElement(). Once we have written this class, we can output all characters in the output stream by simply calling the print of the original slution, passing it an instance of this class: print (new AnotherUppercaseEnumeration()); Recall that the print invokes the two scanner operations on its scanner argument to print all characters produced by it. We can reuse print because this method expects an instance of the type, CharEnumeration, which is implemented by AnotherUppercaseEnumeration. More on Interface as Syntactic Specification Unlike the case of other interfaces we have seen before such as BMISpreadsheet of Chapter 4, the two implementations of CharEnumeration have very different behaviors. In one case uppercase letters in a string are print, while in the other case all uppercase letters in the alphabet are print. One cannot substitute one implementation of an interface with the other and get the same result. All we are guaranteed is that the two implementations enumerate a sequence of characters. This is sufficient guarantee for the print() method, which does not really look at which characters are enumerated. It may not be for other methods. This further highlights the fact that an interface is just a syntactic specification of a type and not a semantic specification. To take the car analogy, two cars may have identical ways of pressing the horns, but emit different sounds when you press the horn. Some drivers may consider the horns equivalent because they are pressed in the same way, while others may not because of the differences in the sounds emitted. The important thing is that the same action is taken in both cars, which corresponds to writing polymorphic methods such as print that work with different implementations of an interface. Summary 13 A structure type is a variable type if different instances of it have different number of components. Indexing and enumerations are used to access instances of variable types. Enumerations are easier to implement while indexing allows random access. For loops are more appropriate than while loops for problems indexing and other kinds of problems. Unlike while loops, they have special components for initializing and resetting loop variables. A problem such as scanning that involves processing of a stream should have a separate object for enumerating the elements of the stream. When tacking a complex problem such as scanning a stream, it is best to first consider the data structures we need, then the algorithm, and finally the code. Loops that are both index-controlled and event-controlled must typically use a short-circuit Boolean operation in the loop condition to avoid subscript errors. If we do not declare a constructor in a class, Java automatically adds to the object code of the class an argumentless constructor with a null body. Different implementations of an interface can have very different external behaviors. Enumeration Exercises 1. 2. 3. 4. What are the advantages of using for loops rather than while loops? Why should a stream processing application have a separate object for enumerating the elements of the stream? Write a procedure that prints the alternate characters of its string argument starting from the first one. What would happen if nextElement() of Figure 7 is called when the enumeration does not have more elements? 5. Suppose that the two internal methods of AnUpperCaseEnumeration had been declared as public. Show, using an example, that a user of this class could cause it to output an incorrect token stream. 6. Define a type (both interface and class) that enumerates all odd numbers in a sentinel-terminated list of integers input by the user. Use –1 as the value of the sentinel. 7. Print the sum of all numbers enumerated by an instance of this type. 8. Write a program that lets the user enter a sequence of words in a line and outputs all the words in it. An input word is a sequence of lowercase or uppercase letters. An output word should consist only of lowercase letters. You can assume that each word is followed by exactly one space and that a user will only enter letters and spaces in an input line. To make sure you do not accidentally include a space in a word, print a left parenthesis immediately before the first letter and a right parenthesis immediately after it. 9. 14 Extend your solution to the previous problem by allowing the last word on a line to have no succeeding blank, allowing more than one blank between words, and allowing non-letters between words. Enumeration 10. 15