3

Possible Duplicate:
Why is Java's Iterator not an Iterable?

We all know java's extended for loop:

List<X> list = ...
for(X x : list) { ... }

What I need is:

Iterator<X> listIterator = ...
for(X x : listIterator){ ... }

Java does not allow this. I was wondering if there's a good reason why the specification does not support this.

Here's my usecase:

I'm writing a reader for a kind of file format. Such a file contains entries that are to be read. For the sake of the example assume that I'm trying to reinvent BufferedReader and my elements are Strings.

I'm quite unhappy with the API style of the original BufferedReader which forces me to write ugly code like:

for(String line = reader.readLine(); line != null; line = reader.readLine(){
   ...
}

I'd rather have something nice like

for(String line : reader){
   ...
}

Of course I can make my BufferedReader implement Iterable<String>. But this implies that there is a iterator() method that might be called several times. Since I cannot seek in the file, I cannot really support multiple parallel iterators.

Having my BufferedReader implement Iterator<String> instead of Iterable<String> seems far more reasonable. But then I can't use the for statement :-(

Community
  • 1
  • 1
Stefan
  • 4,187
  • 1
  • 32
  • 38
  • Have you checked whether the advanced loop invokes the "iterator" method multiple times? I would assume that it does it only once.. – Marc-Christian Schulze Dec 18 '11 at 21:28
  • 1
    The answer to the headline question is "because that's the way the language is defined". – Oliver Charlesworth Dec 18 '11 at 21:31
  • 2
    Again, I disagree that it is a duplicate. I'm very aware of the conseptional difference of the two interfaces. I'm asking why this difference is relevant in respect to the for loop. I'm also aware that its specified this way. I am wondering why it was specified this way. – Stefan Dec 18 '11 at 21:32
  • @coding.mof It is only called once according to the spec. – Stefan Dec 18 '11 at 21:32
  • there is no effective difference between extended for loop and a for loop with iterators. the extended for-loop `for(X x : myList)` is just a shorter version of `for(Iterator it = myList.iterator(); it.hasNext(); ){X x=it.next();...}`. In your case it would try to get an iterator from an iterator (which does not makes sense). But targeting your use-case: try `String line=null; while((line = reader.readLine()) != null){ ... }`. If you dont know the number of lines a while-loop is more logical – thomas Dec 18 '11 at 21:45

2 Answers2

1

Of course you can support multiple parallel iterators. You can open the file more then once.

class BufferedReader() implements Iterable<String> {
    String fileName;
    InputStreamReader in;

    public BufferedReader(String fileName) {
      this.fileName = fileName;
      in = new InputStreamReader(new FileInputStream(fileName));
    }
    public Iterator<String> iterator() {
        return new Iterator<String>() {
            BufferedReader in = new BufferedReader(fileName);
            String nextLine = in.readLine();
            public boolean hasNext() {
                return nextLine != null;
            }
            public String next() {
              String str = nextLine;
              nextLine = in.readLine();
              return str;
            }
        }
    }
}
Tesseract
  • 8,049
  • 2
  • 20
  • 37
  • You are not handling `IOException` – McDowell Dec 18 '11 at 21:57
  • That's a good idea. I've should have specified that I don't really read from a file. I only have an InputStream to start with. This is for example the case when you read directly from a network socket. – Stefan Dec 18 '11 at 22:22
  • You could also read in all the data into an ArrayList and then your BufferedReader reads from that list. Then you can seek. Or you just implement Iterable anyway. The for loop will call the itarator method only once. So there should be no problems. – Tesseract Dec 18 '11 at 23:53
1

Because the contract for the foreach loop is to iterate over every element. If it was allowed to just take an Iterator you could give it a half-consumed Iterator and it wouldn't know the difference.

Set<String> set = new HashSet(Arrays.asList(new String [] {"One", "Two", "Three"}));
Iterator i = set.iterator();
// Consume the first.
String first = i.next();
for ( String s : i ) { // Error here.

}

I accept that it would make things easier but it could also introduce some serious bugs because you wont know you have covered all entries in the collection.

// Works fine because Set is Iterable and the contract is satisfied 
// that this will iterate over <b>all</b> of the entries.
for ( String s : set ) {
}

You can always hack it around by making a transient Iterable. I cant test this right now but something like:

for ( String s : new Iterable { 
  public iterator<String> () {
    return set.iterator ();
  }
} ) {
}

Cumbersome but it should work.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • you can make a cleaner implementation of your hack with a static method - I've just added one to the [related question](http://stackoverflow.com/a/8555153/304) – McDowell Dec 18 '11 at 22:02
  • Good point Paul. While syntactically elegant, the semantics of foreach would be changed when starting in the middle of the file. Since reading from a non-seekable stream cannot keep up with the foreach semantics it would be wrong to use it even with a work around. – Stefan Dec 18 '11 at 22:06
  • @McDowell - Thanks ... I'm on a machine right now that doesn't have java 5 so I couldn't test it. That is clever how you've made it a once-only `Iterator`. @Lawnmower - It is important to stress that is is wrong to abuse the foreach loop, especially by making your `Iterator` `Iterable` by returning a `this`. That is full of danger. Thanks for the reminder. – OldCurmudgeon Dec 18 '11 at 22:46