18

I'm going through the intro to MongoDB for java. There's some example code to retrieve all the documents in a collection. The code works, but I find it a bit...clunky for lack of a better word. I'm wondering if there's a specific reason that makes it necessary. The given example is:

FindIterable<Document> iterable = db.getCollection("restaurants").find();
iterable.forEach(new Block<Document>() {
    @Override
    public void apply(final Document document) {
        System.out.println(document);
    }
});

Is there some reason a Block instance has to be created in every iteration of the forEach in the above example? Why not something a little more straightforward like:

FindIterable<Document> iterable = db.getCollection("restaurants").find();
for (Document document : iterable) {
    System.out.println(document);
}
Hal50000
  • 639
  • 1
  • 6
  • 16
  • check out this post, it explains how you can obtain the solution you want, https://groups.google.com/forum/#!topic/mongodb-user/pcVX84PPwM0 – faljbour May 24 '15 at 16:34
  • @faljbour - This doesn't answer my question. That thread is about API changes from one Mongo version to another. Namely, it doesn't discuss the instantiation of the `Block` instance in the forEach loop. – Hal50000 May 24 '15 at 20:37
  • 1
    @Hal50000 I think there is only one object of an anonymous class implementing Block which is created as the parameter to iterable.forEach() method. – Hongfei Dec 22 '15 at 03:54

2 Answers2

43

While you can certainly use the form that you suggested:

for (Document document : col.find()) {
    // do something
}

it introduces a problem when the body of the for loop throws an exception: if this happens the cursor will not be closed. The proper idiom to guard against that is to use MongoCursor (which implements Closeable) explicitly:

try (MongoCursor<Document> cursor = col.find().iterator()) {
    while (cursor.hasNext()) {
        System.out.println(cursor.next());
    }
}

The forEach method is just a bit of syntactic sugar to avoid the need for application code to worry about having to close the cursor manually like this.

If you don't want to create a new Block for each iteration, you can refactor your code pull out the anonymous inner class creation, e.g.:

Block<Document> block = new Block<Document>() {
    @Override
    public void apply(final Document document) {
        System.out.println(document);
    }
};
col.find().forEach(block);

Of course that's even clunkier, so if you are able to use Java 8, you can replace the whole thing with a lambda:

col.find().forEach((Block<Document>) document -> {
    System.out.println(document);
});

or in this case simply:

col.find().forEach((Block<Document>) System.out::println);

The lambda metafactory will ensure that no unnecessary objects are created.

jyemin
  • 3,743
  • 23
  • 16
  • Thanks for the detailed reply. The thing is, I'm still not clear on the purpose of instantiating a `Block` and then using its `apply(...)` method. Is there a relationship between the `Cursor` and the `Block`? – Hal50000 May 27 '15 at 16:59
  • 2
    Any `MongoIterable` type - like what is returned from `find()` provides helper methods that can be used when iterating. These are `forEach`, `into`, `map` and of course `iterator`. The `forEach` method takes a `Block` that encompasses the logic that you want to perform for every document. Internally, it will iterated the `MongoCursor` for you and ensure that it is closed correctly. – Ross May 28 '15 at 08:10
  • @Ross -thanks. That cleared it up a lot - So in a real world example, there might be some logic more complicated than just `System.out.println(...)` inside apply(...) – Hal50000 May 30 '15 at 17:42
  • works flawlessly! – Gaurav Oct 11 '22 at 10:18
17

I asked myself the same question and I found pretty easy the following code to handle that situation:

List<Document> restaurants = db.getCollection("restaurants").find().into(new ArrayList<Document>());

for (Document restaurant : restaurants) {
    System.out.println(restaurant);
}
Weslor
  • 22,180
  • 2
  • 20
  • 31
  • The question is: is the cursor automatically closed in case of an exception when using `into()`? – Jan Bodnar Apr 28 '16 at 11:43
  • 3
    This copies the whole result set into a collection in memory. Depending on the size of the result, that may be impractical or even impossible. forEach() and company on the other hand process the entries one by one, which works for results set of arbitrary size. – jschreiner Jun 16 '16 at 08:47