116

I'm writing some Scala code which uses the Apache POI API. I would like to iterate over the rows contained in the java.util.Iterator that I get from the Sheet class. I would like to use the iterator in a for each style loop, so I have been trying to convert it to a native Scala collection but will no luck.

I have looked at the Scala wrapper classes/traits, but I can not see how to use them correctly. How do I iterate over a Java collection in Scala without using the verbose while(hasNext()) getNext() style of loop?

Here's the code I wrote based on the correct answer:

class IteratorWrapper[A](iter:java.util.Iterator[A])
{
    def foreach(f: A => Unit): Unit = {
        while(iter.hasNext){
          f(iter.next)
        }
    }
}

object SpreadsheetParser extends Application
{
    implicit def iteratorToWrapper[T](iter:java.util.Iterator[T]):IteratorWrapper[T] = new IteratorWrapper[T](iter)

    override def main(args:Array[String]):Unit =
    {
        val ios = new FileInputStream("assets/data.xls")
        val workbook = new HSSFWorkbook(ios)
        var sheet = workbook.getSheetAt(0)
        var rows = sheet.rowIterator()

        for (val row <- rows){
            println(row)
        }
    }
}
Meredith
  • 3,928
  • 4
  • 33
  • 58
BefittingTheorem
  • 10,459
  • 15
  • 69
  • 96
  • I can't seem to include the line "for (val row <- rows){" without the parser thinking the '<' character is an XML closing tag? The backticks do not work – BefittingTheorem Jan 30 '09 at 17:15
  • You should be able to convert to IteratirWrapper implicitly, saving you a fair bit of syntax. Google for implicit conversions in Scala. – Daniel Spiewak Jan 30 '09 at 18:01

9 Answers9

259

As of Scala 2.8, all you have to do is to import the JavaConversions object, which already declares the appropriate conversions.

import scala.collection.JavaConversions._

This won't work in previous versions though.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ttonelli
  • 28
  • 2
  • 2
  • 4
36

Edit: Scala 2.13.0 deprecates scala.collection.JavaConverters, so since 2.13.0 you need to use scala.jdk.CollectionConverters.

Scala 2.12.0 deprecates scala.collection.JavaConversions, so since 2.12.0 one way of doing this would be something like:

import scala.collection.JavaConverters._

// ...

for(k <- javaCollection.asScala) {
    // ...
}

(notice the import, new is JavaConverters, deprecated is JavaConversions)

antonone
  • 2,045
  • 1
  • 25
  • 35
29

There is a wrapper class (scala.collection.jcl.MutableIterator.Wrapper). So if you define

implicit def javaIteratorToScalaIterator[A](it : java.util.Iterator[A]) = new Wrapper(it)

then it will act as a sub class of the Scala iterator so you can do foreach.

  • It should read: scala.collection.jcl.MutableIterator.Wrapper – samg Dec 30 '09 at 23:21
  • 37
    This answer is obsolete in Scala 2.8; see http://stackoverflow.com/questions/2708990/whats-the-new-way-to-iterate-over-a-java-map-in-scala-2-8-0 – Alex R Apr 25 '10 at 17:09
15

The correct answer here is to define an implicit conversion from Java's Iterator to some custom type. This type should implement a foreach method which delegates to the underlying Iterator. This will allow you to use a Scala for-loop with any Java Iterator.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Daniel Spiewak
  • 54,515
  • 14
  • 108
  • 120
9

For Scala 2.10:

// Feature warning if you don't enable implicit conversions...
import scala.language.implicitConversions
import scala.collection.convert.WrapAsScala.enumerationAsScalaIterator
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
F. P. Freely
  • 1,026
  • 14
  • 24
6

If you are iterating through a large dataset, then you probably don't want to load whole collection into memory with .asScala implicit conversion. In this case, a handy way approach is to implement scala.collection.Iterator trait

import java.util.{Iterator => JIterator}

def scalaIterator[T](it: JIterator[T]) = new Iterator[T] {
  override def hasNext = it.hasNext
  override def next() = it.next()
} 

val jIterator: Iterator[String] = ... // iterating over a large dataset
scalaIterator(jIterator).take(2).map(_.length).foreach(println)  // only first 2 elements are loaded to memory

It has similar concept but less verbose IMO :)

Max
  • 2,065
  • 24
  • 20
5

With Scala 2.10.4+ (and possibly earlier) it is possible to implicitly convert java.util.Iterator[A] to scala.collection.Iterator[A] by importing scala.collection.JavaConversions.asScalaIterator. Here is an example:

object SpreadSheetParser2 extends App {

  import org.apache.poi.hssf.usermodel.HSSFWorkbook
  import java.io.FileInputStream
  import scala.collection.JavaConversions.asScalaIterator

  val ios = new FileInputStream("data.xls")
  val workbook = new HSSFWorkbook(ios)
  var sheet = workbook.getSheetAt(0)
  val rows = sheet.rowIterator()

  for (row <- rows) {
    val cells = row.cellIterator()
    for (cell <- cells) {
      print(cell + ",")
    }
    println
  }

}
4

You could convert the Java collection to an array and use that:

val array = java.util.Arrays.asList("one","two","three").toArray
array.foreach(println)

Or go on and convert the array to a Scala list:

val list = List.fromArray(array)
Fabian Steeg
  • 44,988
  • 7
  • 85
  • 112
2

If you would like to avoid the implicits in scala.collection.JavaConversions you can use scala.collection.JavaConverters to convert explicitly.

scala> val l = new java.util.LinkedList[Int]()
l: java.util.LinkedList[Int] = []

scala> (1 to 10).foreach(l.add(_))

scala> val i = l.iterator
i: java.util.Iterator[Int] = java.util.LinkedList$ListItr@11eadcba

scala> import scala.collection.JavaConverters._
import scala.collection.JavaConverters._

scala> i.asScala.mkString
res10: String = 12345678910

Note the use of the asScala method to convert the Java Iterator to a Scala Iterator.

The JavaConverters have been available since Scala 2.8.1.

kilo
  • 539
  • 4
  • 13