Itération sur des collections Java dans Scala

113

J'écris du code Scala qui utilise l' API Apache POI . Je voudrais parcourir les lignes contenues dans le java.util.Iteratorque je reçois de la classe Sheet. Je voudrais utiliser l'itérateur dans une for eachboucle de style, j'ai donc essayé de le convertir en une collection Scala native mais je n'aurai pas de chance.

J'ai regardé les classes / traits du wrapper Scala, mais je ne vois pas comment les utiliser correctement. Comment parcourir une collection Java dans Scala sans utiliser le while(hasNext()) getNext()style verbeux de loop?

Voici le code que j'ai écrit en fonction de la bonne réponse:

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
la source
Je n'arrive pas à inclure la ligne "for (val row <- rows) {" sans que l'analyseur ne pense que le caractère '<' est une balise de fermeture XML? Les backticks ne fonctionnent pas
BefittingTheorem
Vous devriez pouvoir convertir implicitement en IteratirWrapper, ce qui vous permet d'économiser un peu de syntaxe. Google pour les conversions implicites dans Scala.
Daniel Spiewak

Réponses:

28

Il existe une classe wrapper ( scala.collection.jcl.MutableIterator.Wrapper). Donc, si vous définissez

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

alors il agira comme une sous-classe de l'itérateur Scala, donc vous pouvez le faire foreach.

James
la source
Il devrait lire: scala.collection.jcl.MutableIterator.Wrapper
samg
37
Cette réponse est obsolète dans Scala 2.8; voir stackoverflow.com/questions/2708990/…
Alex R
256

Depuis Scala 2.8, tout ce que vous avez à faire est d'importer l'objet JavaConversions, qui déclare déjà les conversions appropriées.

import scala.collection.JavaConversions._

Cela ne fonctionnera pas dans les versions précédentes.

ttonelli
la source
24

Edit : Scala 2.13.0 est obsolète scala.collection.JavaConverters, donc depuis la 2.13.0 vous devez utiliser scala.jdk.CollectionConverters.

Scala 2.12.0 est obsolète scala.collection.JavaConversions, donc depuis la version 2.12.0, une façon de procéder serait quelque chose comme:

import scala.collection.JavaConverters._

// ...

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

(notez l'importation, nouveau est JavaConverters, obsolète est JavaConversions)

antonone
la source
2
Je cherchais "toScala" ^ _ ^!
Profiterole
15

La bonne réponse ici est de définir une conversion implicite de Java Iteratorvers un type personnalisé. Ce type doit implémenter une foreachméthode qui délègue au sous-jacent Iterator. Cela vous permettra d'utiliser une forboucle Scala avec n'importe quel Java Iterator.

Daniel Spiewak
la source
9

Pour Scala 2.10:

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

Avec Scala 2.10.4+ (et peut-être plus tôt), il est possible de convertir implicitement java.util.Iterator [A] en scala.collection.Iterator [A] en important scala.collection.JavaConversions.asScalaIterator. Voici un exemple:

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
la source
4

Vous pouvez convertir la collection Java en un tableau et l'utiliser:

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

Ou continuez et convertissez le tableau en une liste Scala:

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

Si vous effectuez une itération dans un ensemble de données volumineux, vous ne souhaitez probablement pas charger toute la collection en mémoire avec .asScalaune conversion implicite. Dans ce cas, une approche pratique consiste à implémenter le scala.collection.Iteratortrait

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

Il a un concept similaire mais moins verbeux IMO :)

Max
la source
2

Si vous souhaitez éviter les implicits dans scala.collection.JavaConversions, vous pouvez utiliser scala.collection.JavaConverters pour convertir explicitement.

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

Notez l'utilisation de la asScalaméthode pour convertir le Java Iteratoren Scala Iterator.

Les JavaConverters sont disponibles depuis Scala 2.8.1.

kilo
la source
J'ai utilisé import scala.collection.JavaConverters._ et puis javaList.iterator().asScala de votre exemple et cela a fonctionné
kosiara - Bartosz Kosarzycki