Iterando sobre colecciones de Java en Scala

113

Estoy escribiendo un código Scala que usa la API de Apache POI . Me gustaría iterar sobre las filas contenidas en el java.util.Iteratorque obtengo de la clase Hoja. Me gustaría usar el iterador en un for eachbucle de estilo, así que he intentado convertirlo en una colección nativa de Scala, pero no tengo suerte.

He mirado las clases / rasgos de envoltura de Scala, pero no puedo ver cómo usarlas correctamente. ¿Cómo iterar sobre una colección de Java en Scala sin usar el while(hasNext()) getNext()estilo detallado de bucle?

Aquí está el código que escribí según la respuesta correcta:

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)
        }
    }
}
Brian Heylin
fuente
Parece que no puedo incluir la línea "para (val fila <- filas) {" sin que el analizador piense que el carácter '<' es una etiqueta de cierre XML. Las
comillas inversas
Debería poder convertir a IteratirWrapper implícitamente, lo que le ahorrará un poco de sintaxis. Google para conversiones implícitas en Scala.
Daniel Spiewak

Respuestas:

28

Hay una clase contenedora ( scala.collection.jcl.MutableIterator.Wrapper). Así que si defines

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

entonces actuará como una subclase del iterador Scala para que pueda hacerlo foreach.

James
fuente
Debe leer: scala.collection.jcl.MutableIterator.Wrapper
samg
37
Esta respuesta está obsoleta en Scala 2.8; ver stackoverflow.com/questions/2708990/…
Alex R
256

A partir de Scala 2.8, todo lo que tiene que hacer es importar el objeto JavaConversions, que ya declara las conversiones apropiadas.

import scala.collection.JavaConversions._

Sin embargo, esto no funcionará en versiones anteriores.

ttonelli
fuente
24

Editar : Scala 2.13.0 está obsoleto scala.collection.JavaConverters, por lo que desde 2.13.0 debe usar scala.jdk.CollectionConverters.

Scala 2.12.0 está en desuso scala.collection.JavaConversions, por lo que desde 2.12.0 una forma de hacer esto sería algo como:

import scala.collection.JavaConverters._

// ...

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

(observe la importación, nuevo es JavaConverters, obsoleto es JavaConversions)

antonone
fuente
2
Estaba buscando "toScala" ^ _ ^!
Profiterole
15

La respuesta correcta aquí es definir una conversión implícita de Java Iteratora algún tipo personalizado. Este tipo debería implementar un foreachmétodo que delegue al subyacente Iterator. Esto le permitirá utilizar un forbucle de Scala con cualquier Java Iterator.

Daniel Spiewak
fuente
9

Para Scala 2.10:

// Feature warning if you don't enable implicit conversions...
import scala.language.implicitConversions
import scala.collection.convert.WrapAsScala.enumerationAsScalaIterator
FP libremente
fuente
5

Con Scala 2.10.4+ (y posiblemente anterior) es posible convertir implícitamente java.util.Iterator [A] en scala.collection.Iterator [A] importando scala.collection.JavaConversions.asScalaIterator. Aquí hay un ejemplo:

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
  }

}
user4322779
fuente
4

Puede convertir la colección de Java en una matriz y usar eso:

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

O continúe y convierta la matriz en una lista Scala:

val list = List.fromArray(array)
Fabian Steeg
fuente
4

Si está iterando a través de un conjunto de datos grande, probablemente no desee cargar la colección completa en la memoria con .asScalaconversión implícita. En este caso, un enfoque práctico es implementar el scala.collection.Iteratorrasgo

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

Tiene un concepto similar pero menos detallado en mi opinión :)

Max
fuente
2

Si desea evitar las implicaciones en scala.collection.JavaConversions , puede usar scala.collection.JavaConverters para convertir explícitamente.

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

Tenga en cuenta el uso del asScalamétodo para convertir Java Iteratora Scala Iterator.

Los JavaConverters están disponibles desde Scala 2.8.1.

kilo
fuente
Solía import scala.collection.JavaConverters._ y después javaList.iterator().asScala de su ejemplo y funcionó
kosiara - Bartosz Kosarzycki