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)
        }
    }
}

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.


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.


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)


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.


9

Pour Scala 2.10:

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

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
  }

}

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)

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 :)


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.


J'ai utilisé import scala.collection.JavaConverters._ et puis javaList.iterator().asScala de votre exemple et cela a fonctionné
kosiara - Bartosz Kosarzycki
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.