The Enumeration Interface

5 301 0
The Enumeration Interface

Đang tải... (xem toàn văn)

Thông tin tài liệu

Chapter 4: The Enumeration Interface The Enumeration interface defines a way to traverse all the members of a collection of objects. And although some collections define a specific order of traversal, the Enumeration interface does not. The key tidbit to know is that if you make your way through all the elements, the interface will ensure that each and every element of the collection is visited. Enumeration Basics The Enumeration interface follows the Iterator pattern defined by the Gang of Four in Design Patterns (Addison−Wesley, 1995). This interface has been around since the beginning of Java time and has been duplicated with extra capabilities in the Iterator interface of the Collections Framework. Chapter 7 covers the Iterator interface in detail. For now, let's look now at the Enumeration interface definition in Table 4−1. Table 4−1: Summary of Interface Enumeration VARIABLE/METHOD NAME VERSION DESCRIPTION hasMoreElements() 1.0 Checks for more elements in the enumeration. nextElement() 1.0 Fetches next element of the enumeration. Nothing too complicated here: just two methods. When working with an enumeration, the hasMoreElements() method checks to see if there are more elements in the enumeration and then returns a boolean. If there are more elements in the enumeration, nextElement() will return the next element as an Object. If there are no more elements in the enumeration when nextElement() is called, the runtime NoSuchElementException will be thrown. When working with an Enumeration, the basic structure for walking through the elements is as follows: Enumeration enum = . . .; while (enum.hasMoreElements()) { Object o = enum.nextElement(); processObject(o); } If you prefer a for−loop, you can do the same thing with: for (Enumeration enum = . . .; enum.hasMoreElements(); ) { Object o = enum.nextElement(); processObject(o); } If followed to the end, both the while−loop and for−loop will visit all members of the original collection. The key difference with the for−loop is that the scope of the enum variable is limited to the looping construct, whereas the enum variable is visible, though somewhat useless, outside the while−loop. This leaves us with one question. Where do we get the enumeration from? Why, ask the collection, of course! Each of the historical collection classes provides a method to give you the set of elements within them: 47 * Vector, Stack: elements() * Dictionary, Hashtable, Properties: elements(), keys() While we'll explore the Collections Framework more in Part Two, there is one method of the Collections class that should be mentioned here, too: Collection col = . . .; Enumeration enum = Collections.enumeration(col); This allows you to convert from a new collection type to the older enumeration for looping through the elements. You may find this technique necessary when using a third−party library that hasn't yet been upgraded to handle the newer Collections Framework. Warning One should never modify the underlying collection when using an enumeration. This will result in undefined behavior. For safety's sake, place the processing of elements within a synchronized block: synchronized (col) { /* get enum & process elements */ }. Of course, this only works if other accesses to the underlying collection are synchronized, too, and synchronized on the same object to boot. Thankfully, this access is already synchronized for the historical collections like Vector and Hashtable. The SequenceInputStream Class To demonstrate the use of an Enumeration, look at the SequenceInputStream class found in the java.io package. The class has a constructor that accepts an enumeration: public SequenceInputStream(Enumeration enum) To use a SequenceInputStream, create a set of input streams. While we could define our own set, the simplest way to do this would be to create a Vector and then pass the enumeration of elements in the vector to the SequenceInputStream constructor. The elements() method of Vector will give you an Enumeration, and the elements of the enumeration will be used in the order in which they were added (index order). Vector v = new Vector(3); v.add(new FileInputStream("/etc/motd")); v.add(new FileInputStream("foo.bar")); v.add(new FileInputStream("/temp/john.txt")); Once you have filled your vector, you can create the SequenceInputStream and read from it. If you wish to read characters from the combined input stream, you can combine them en masse with an InputStreamReader instead of having to combine individual streams. This is like creating your own SequenceReader class, as shown here: Enumeration enum = v.elements(); SequenceInputStream sis = new SequenceInputStream(enum); InputStreamReader isr = new InputStreamReader(sis); BufferedReader br = new BufferedReader(isr); String line; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); Note When you call the close() method of SequenceInputStream, all streams are closed. The SequenceInputStream Class 48 StringTokenizer In most cases, the implementations of the Enumeration interface are done in private classes. There is at least one exception to that rule in the standard Java libraries: the StringTokenizer class, which implements the interface as shown in Figure 4−1. Figure 4−1: The StringTokenizer class hierarchy. The purpose of the StringTokenizer class is to break apart a string based upon a set of delimiter tokens. You can then call the Enumeration methods to loop through the elements: StringTokenizer tokenizer = new StringTokenizer("This is a test"); while (tokenizer.hasMoreElements()) { Object o = tokenizer.nextElement(); System.out.println(o); } Besides implementing the Enumeration interface, the StringTokenizer class also provides a second set of methods for the same purpose—hasMoreTokens() and nextToken(): StringTokenizer tokenizer = new StringTokenizer("This is a test"); while (tokenizer.hasMoreTokens()) { String s = tokenizer.nextToken(); System.out.println(s); } While hasMoreTokens() is functionally equivalent to hasMoreElements(), the nextToken() method is slightly different. Instead of returning a generic Object, the nextToken() method returns a String object. Thus, by using the new methods of StringTokenizer, you can avoid making a cast to a String or calling the toString() method if you wish to treat the return value of nextElement() as a String type. Note There are other public classes in the extension libraries that implement Enumeration. For example, javax.commerce.base.JCMIterator, javax.commerce.database.IndexEnumerator, javax.naming.NamingEnumeration, and javax.smartcard.IFDRegistry.SlotEnumeration. In most cases, the name makes it clear what it is. Creating Custom Enumerations In most cases, it isn't necessary to create your own enumerations. Only if you are defining your own collection class or desire to extend the basic functionality of enumerating through a collection (like for filtering) do you need to create a custom enumeration. All of the predefined collections allow you to get back an enumeration to work with. There is one exception to that rule, however—the array. If for some reason you wish to treat an array as an enumeration, you'll either have to store the elements in another collection, like a vector, or define your own array enumeration class. To demonstrate how to create our own Enumeration implementation, we'll do the latter. StringTokenizer 49 Creating a custom Enumeration implementation is no different than implementing any Java interface. Define a class that states it implements the interface. Then provide the implementation. In the case of the Enumeration interface, the implementation takes the form of two methods: boolean hasMoreElements() and Object nextElement() as shown in Listing 4−1. Once you add in a constructor, you're essentially done. Listing 4−1: The ArrayEnumeration class. public class ArrayEnumeration implements Enumeration { private final int size; private int cursor; private final Object array; public ArrayEnumeration(Object obj) { Class type = obj.getClass(); if (!type.isArray()) { throw new IllegalArgumentException("Invalid type: " + type); } size = Array.getLength(obj); array = obj; } public boolean hasMoreElements() { return (cursor < size); } public Object nextElement() { return Array.get(array, cursor++); } } Note The ArrayEnumeration constructor argument is an Object, rather than an Object[ ]. Thus, we can work with arrays of primitives, too. To demonstrate our new ArrayEnumeration class, we need to create a test program (see Listing 4−2). If you add the following main() method to the class definition, you'll have three good test cases. The first uses the simple String array passed into the main() method. The second creates an int array and then makes an ArrayEnumeration out of that. You should realize that the nextElement() call for this enumeration actually returns Integer objects, not int primitives, even though it is an array of int primitives. This works because we are using reflection to fetch individual elements from the array. The final test case involves an attempt to create an array enumeration from something other than an array. This test case will cause an IllegalArgumentException to be thrown. Listing 4−2: Testing the ArrayEnumeration. public static void main(String args[]) { Enumeration enum = new ArrayEnumeration(args); while (enum.hasMoreElements()) { System.out.println(enum.nextElement()); } Object obj = new int[] {2, 3, 5, 8, 13, 21}; enum = new ArrayEnumeration(obj); while (enum.hasMoreElements()) { System.out.println(enum.nextElement()); } try { enum = new ArrayEnumeration(ArrayEnumeration.class); } catch (IllegalArgumentException e) { System.out.println("Oops: " + e.getMessage()); } StringTokenizer 50 } If you were to run the sample program with the following command line: java ArrayEnumeration Do Re Mi You would then get the following output: Do Re Mi 2 3 5 8 13 21 Oops: Invalid type: class java.lang.Class Be aware that the ArrayEnumeration class doesn't make a copy of the array to start. This means that the set of elements enumerated over can change in the middle of stepping through. Note If you're interested in creating your own enumeration, a filtering enumeration is potentially useful. Given some filtering method that returns a boolean, have the enumeration call the filtering method for each element and return only those elements that return true from the filtering method. This example will be revisited when discussing the Iterator interface in Chapter 7. Summary In this chapter, we explored traversing through an historical collection with the help of the Enumeration interface. We also showed how to get an Enumeration for one of the new collection classes and how to create our own Enumeration implementations. Finally, we examined the SequenceInputStream and StringTokenizer classes, including how each takes advantage of the Enumeration interface. In the next chapter, we'll explore the Dictionary class and its subclasses, Hashtable and Properties. Each provides key−value mappings instead of ordered, sequential access. Summary 51 . Java interface. Define a class that states it implements the interface. Then provide the implementation. In the case of the Enumeration interface, the implementation. order of traversal, the Enumeration interface does not. The key tidbit to know is that if you make your way through all the elements, the interface will ensure

Ngày đăng: 05/10/2013, 12:20

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan