390 likes | 400 Views
Iteration Abstraction. Gang Qian Department of Computer Science University of Central Oklahoma. Objectives. Motivation for Iteration Abstraction Iteration in Java Iterator Specification Using Iterators Implementing Iterators Design Issues. Why Iteration Abstraction.
E N D
Iteration Abstraction Gang Qian Department of Computer Science University of Central Oklahoma
Objectives • Motivation for Iteration Abstraction • Iteration in Java • Iterator Specification • Using Iterators • Implementing Iterators • Design Issues
Why Iteration Abstraction • Iteration abstraction or iterator is a generalization of the iteration mechanism in programming languages • Allows users to iterate over arbitrary types of data conveniently for each element of the set do action • Example: IntSet provides no convenient way for iteration • If we want to compute the sum of the set, we have to utilize both choose and remove (see next slide) • However, it is usually not desirable to destroy the object while iterating
public static int setSum (IntSet s) throws NullPointerException { int[] a = new int[s.size()]; int sum = 0; for (int i = 0; i < a.length; i++) { a[i] = s.choose(); sum += a[i]; s.remove(a[i]); } // restore s for (int i = 0; i < a.length; i++) s.insert(a[i]); return sum; }
To support iteration adequately, we need to access all elements in a collection efficiently and without destroying the collection • One solution may be to provide a members method /** EFFECTS: Returns an array containing the elements of this, each exactly once, in some arbitrary order. */ public int[] members () • Then the setSum procedure can be implemented based on members
public static int setSum (IntSet s) throws NullPointerException { int[] a = s.members(); int sum = 0; for (int i = 0; i < a.length; i++) sum += a[i]; return sum; } • However, this approach of using members is not efficient • Two data structures for the same thing • We may not need the whole collection • A search might stop in the middle of the array
Another solution may be to return the rep • A very bad idea • A fourth solution may be change the IntSet abstraction to include the notion of indexing: IndexedSet • Added complexity • Not intrinsic to the notion of a set • This method may be ok for some collections, but not good for IntSet • What is needed is a general mechanism of iteration that is convenient and efficient and that preserves the abstraction • Solution: iteration abstraction (or iterator)
An iterator is a special kind of procedure that causes the items over which we want to iterate to be produced incrementally for each result item i produced by iterator A do perform some action on i • Note that in the using code, the iterator is responsible for producing one new item at a time, while the code in the loop body defines the action on the items • Iterators do not have the problems of other solutions
Iteration in Java • An iterator in Java returns a special kind of data object called a generator • Iterators can be methods of data abstractions or stand-alone procedures • A generator keeps track of the state of an iteration in its rep • All generators are subtypes of the generic interface Iterator<E> in java.util • It has a hasNext method for determining whether there are more elements remain to be produced • It also has a next method to get the next element and advance the state of the generator object to reflect the returning of that element
public interface Iterator<E> { /** EFFECTS: Returns true if there are more elements to yield; else return false. */ public boolean hasNext (); /** MODIFIES: this EFFECTS: If there are more results to yield, returns the next result and modifies the state of this to record the yield. Otherwise, throws NoSuchElementException. */ public E next () throws NoSuchElementException; }
Using generators // loop controlled by hasNext Iterator<Integer> g = primesLT100(); while (g.hasNext()) { int x = g.next(); // use x now } // loop controlled by exception Iterator<Integer> g = primesLT100(); try { while (true) { int x = g.next(); // use x now } } catch (NoSuchElementException e) {}
Note: • Using code obtains a generator of a collection data type by calling its iterator • E.g., primsLT100 • The generator is typically used in a while loop
Specifying Iterators • The specification of an iterator explains: • How the iterator used its arguments to produce a generator, and • the behavior of the generator • Note that the specification of the generator is quite generic • So exactly what a generator does is explained in the specification of the iterator
Examples: Two iterator methods public class Poly { // as before plus: /** EFFECTS: Returns a generator that produces exponents of non-zero terms of this up to the degree, in order of increasing exponent. */ public Iterator<Integer> terms () } public class IntSet { // as before plus: /** EFFECTS: Returns a generator that produces all the elements of this, each exactly once, in arbitrary order. REQUIRES: This must not be modified while the generator is in use. */ public Iterator<Integer> elements ()
Note: • The specification explains that the returned generator allows iteration over this, and • indicates the meaning of the object that will be produced by the generator • There might be a REQUIRES clause that requires the object not to be modified while the generator is in use • A generator over a mutable object almost always has such a requirement • The REQUIRES clause is about the use of the generator, not the iterator • So it is put at the end of the specification • An iterator might have two REQUIRES clauses
An iterator usually does not throw exceptions • E.g., no need to worry about empty set • A data abstraction might provide more than one iterators • Iterators usually do not modify this • It is also possible to have standalone iterator procedures public class Num { /** EFFECTS: Returns a generator that produces all primes (as Integers), each exactly once, in increasing order. */ public static Iterator<Integer> allPrimes () }
Using Iterators • See WebCT /** MODIFIES: System.out EFFECTS: Prints all the primes less than or equal to m on System.out */ public static void printPrimes (int m) { Iterator<Integer> g = Num.allPrimes(); while (g.hasNext()) { int p = g.next(); if (p > m) return; System.out.println("The next prime is: " + p); } }
/** Modifies: g EFFECTS: If g is null, throws NullPointerException; else if g is empty, throws EmptyException; else returns the largest int in g. */ public static int max (Iterator<Integer> g) throws EmptyException, NullPointerException { try { int m = g.next(); while (g.hasNext()) { int x = g.next(); if (m < x) m = x; } return m; } catch (NoSuchElementException e) { throw new EmptyException("Comp.max"); } }
Note: • Another way to use generators is to pass them as arguments to routines • E.g., the generator may come from IntSet or a standalone iterator • Using code must obey the constraints imposed on it by the iterator’s REQUIRES clause • All using code is written in terms of the generic Iterator<E> type • Implementation: The iterator actually returns a subtype of Iterator<E>, which is invisible to using code
Implementing Iterators • Need to implement: • the iterator itself (method or standalone procedure), and • a class for the generator • A separate class is needed for each iterator • The classes are invisible to users • The classes implements the Iterator<E> interface • Example: Poly
public class Poly { private int[] trms; private int deg; // Specification repeats here (previously provided) public Iterator<Integer> terms () { return new PolyGen(this); } /** Overview: Inner generator class */ private static class PolyGen implements Iterator<Integer> { private Poly p; //the Poly being iterated private int n; //the next term to consider /** REQUIRES: it != null */ PolyGen(Poly it) { p = it; if (p.trms[0] == 0) n = 1; else n = 0; } (continued on next slide)
public boolean hasNext () { return n <= p.deg; } public Integer next () throws NoSuchElementException { for (int e = n; e <= p.deg; e++) if (p.trms[e] != 0) { n = e + 1; return new Integer(e); } throw new NoSuchElementException("Poly.terms"); } // unsupported public void remove() { throw new UnsupportedOperationException( "Poly.terms"); } } // end PolyGen }
Notes: • The generator class PolyGen is defined as a subclass of Iterator<Integer> • PolyGen is implemented as a static inner class • As an inner class, its constructor can be called within the Poly class • It can access private instance variables and methods of Poly • Therefore, the inner class must preserve the rep invariant of Poly • PolyGen is private so it is invisible to using code • The using code can only obtain PolyGen objects by calling the iterator terms, and • use it through the Iterator<Integer> interface
No specification for the generator class is needed • Already given in the iterator • The remove method is optional • Exceptions thrown by the methods of the generator class identify the iterator as the source of the problem • The using code has no idea about the existence of PolyGen
Special notes on the generic Interface Iterator<E> • It is natural to use the generic Iterator<E> in generic collection classes • For example, a generic Set<E> needs a generic generator that is a subclass of Iterator<E> • If the collection class itself is not generic, such as IntSet or Poly, then we should NOT use a generic Iterator<E> • Because the element type is known beforehand • Integer in the Poly example • How can we solve the problem?
One solution is the old way: Using Iterator directly without the type parameter (as in the textbook) • Not good! • Using a generic class without the type parameter <E> is not recommended after JDK 1.5, since it is allowed in JDK 1.5 only for backward compatibility purposes • New code should always use the generic version of a generic class (See the Polymorphism lecture later) • The second solution is to use the generic Iterator<E> and define a generic inner generator class in a non-generic class • Quite awkward but doable • A warning will occur during compilation: “Note: Poly.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.” • This is the approach presented in the sample code • Poly.bak.java, Num.bak.java
A third option is to create a separate custom iterator interface for non-generic collection classes so that we no longer use the standard Java API Iterator<E> • Advantage: Flexible • Disadvantage: The approach is not general • The best solution is to define a regular subclass of Iterator<Integer> • See class MyDouble • This is presented in the lecture notes
In summary, when we need to define a new inner subclass of a generic class/interface: • Outer class is generic -> generic inner class • Implement the inner class as a generic class with type parameter <E> • private static class SetGen<E> implements Iterator<E> • Outer class is not generic -> non-generic inner class • Implement the inner class as a non-generic class • private static class PolyGen implements Iterator<Integer>
public class Num { // Specification repeats here (previously provided) public static Iterator<Integer> allPrimes() { return new PrimesGen ( ); } /** Overview: Inner generator class */ private static class PrimesGen implements Iterator<Integer> { private Vector<Integer> ps; // primes yielded private int p; // next candidate to try PrimesGen() { p = 2; ps = new Vector<Integer>(); } public boolean hasNext() { return true; } (continued on next slide)
public Integer next() { if (p == 2) { p = 3; return new Integer(2); } for (int n = p; true; n += 2) for (int i = 0; i < ps.size(); i++) { int el = ps.get(i); if (n % el == 0) break; // not a prime if (el * el > n) { //have a prime ps.add(n); p = n + 2; return new Integer(n); } } } // unsupported public void remove() { throw new UnsupportedOperationException( "Num.allPrimes"); } } //end PrimesGen }
Note: • The next method of PrimesGen does not list NoSuchElementException, but • the specification of the Iterator interface indicates that NoSuchElementException can be raised by next • It is acceptable for a subtype method to have fewer exceptions than the corresponding supertype method • Make sense from the user’s point of view
Rep Invariant and Abstraction Function for Generators • Rep invariant • Rep invariants for generators are similar to those for ordinary abstract data types • However, repOk is not necessary • Example: PolyGen // I(c) = c.p != null && (0 <= c.n <= c.p.deg) • The instance variables of Poly are used in the rep invariant of PolyGen • c.p not null is satisfied by the REQUIRES clause of the PolyGen constructor
Example: PrimesGen I(c) = c.ps != null && all elements of c.ps are primes and sorted in ascending order && c.ps include all primes >2 and < c.p. • This rep invariant is quite expensive to check • Abstraction function • What is the abstract state of a generator • All generators have the same abstract state: a sequence of the items that remain to be generated • The abstraction function needs to map the rep of a generator to this sequence
Example: PrimesGen // abstraction function for PrimesGen // AF(c) = [p1, p2, . . .] such that each pi is an // Integer and pi is a prime and pi >= c.p and // every prime >= c.p is in the sequence and // pi > pj for all i > j >= 1. • Example: PolyGen // abstraction function for PolyGen // AF(c) = [xl, ..., xn] such that each xi is an // Integer and every index i >= c.n of a nonzero // element of c.p.trms is in the sequence and no // other elements are in the sequence and // xi > xj for all i > j >= 1. • Example: OrderedIntList (Text page 139)
Use the rep’s iterator method • Sometimes you may not need to follow the standard way to design the iterator, which needs a new inner class as a subclass of Iterator<Integer> • You may use the iterator method of the rep if it is available and the generator it returns satisfy the specification • Many Java API collection types such as Vector<E> and ArrayList<E> have their own iterator methods
The standard way: public class IntSet { Vector<Integer> elms; public Iterator<Integer> elements () { return new IntSetGen(this); } private static class IntSetGen implements Iterator<Integer> { // Implement IntSetGen here } }
The alternative way if applicable: public class IntSet { Vector<Integer> elms; public Iterator<Integer> elements () { return elms.iterator(); } // no need to implement the inner class }
Design Issues • Iterators are mostly used for data types whose objects are collections of other objects • They are often needed for adequacy • A type may have several iterators • For mutable collections, it is almost always desirable that the collection should not to be changed before the iteration completes • Example: If an integer n is deleted from an IntSet object during an iteration, should n be produced by the generator? • One solution is to require that a generator produce the elements in collection at the time it is created by the iterator • Cannot be efficient
Modifications to the collection may be useful in some rare situation • Example: a task queue Iterator g = q.allTasks(); while (g.hasNext()) { Task t = (Task) g.next(); // perform t // if t generates a new task nt, enqueue it }