Iterando sobre coleções Java em Scala

113

Estou escrevendo um código Scala que usa a API Apache POI . Eu gostaria de iterar sobre as linhas contidas nojava.util.Iterator que obtenho da classe Folha. Eu gostaria de usar o iterador em um for eachloop de estilo, então estou tentando convertê-lo em uma coleção nativa de Scala, mas não tive sorte.

Eu olhei para as classes / características do wrapper Scala, mas não consigo ver como usá-los corretamente. Como faço para iterar em uma coleção Java no Scala sem usar o verbosewhile(hasNext()) getNext() estilo de loop?

Aqui está o código que escrevi com base na resposta correta:

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
fonte
Não consigo incluir a linha "for (val row <- rows) {" sem que o analisador pense que o caractere '<' é uma tag de fechamento XML? Os backticks não funcionam
BefittingTheorem
Você deve ser capaz de converter para IteratirWrapper implicitamente, economizando um pouco de sintaxe. Google para conversões implícitas em Scala.
Daniel Spiewak

Respostas:

28

Existe uma classe wrapper ( scala.collection.jcl.MutableIterator.Wrapper). Então, se você definir

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

em seguida, ele atuará como uma subclasse do iterador Scala para que você possa fazer foreach.

James
fonte
Ele deve ser: scala.collection.jcl.MutableIterator.Wrapper
samg
37
Essa resposta é obsoleta no Scala 2.8; consulte stackoverflow.com/questions/2708990/…
Alex R
256

A partir do Scala 2.8, tudo o que você precisa fazer é importar o objeto JavaConversions, que já declara as conversões apropriadas.

import scala.collection.JavaConversions._

No entanto, isso não funcionará nas versões anteriores.

ttonelli
fonte
24

Edit : Scala 2.13.0 está obsoleto scala.collection.JavaConverters, então desde 2.13.0 você precisa usarscala.jdk.CollectionConverters .

Scala 2.12.0 está obsoleto scala.collection.JavaConversions, portanto, desde 2.12.0, uma maneira de fazer isso seria algo como:

import scala.collection.JavaConverters._

// ...

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

(observe a importação, novo é JavaConverters, obsoleto é JavaConversions)

antonone
fonte
2
Eu estava procurando por "toScala" ^ _ ^!
Profiterole
15

A resposta correta aqui é definir uma conversão implícita de Java Iteratorpara algum tipo personalizado. Este tipo deve implementar um foreachmétodo que delega ao subjacente Iterator. Isso permitirá que você use um forloop Scala com qualquer Java Iterator.

Daniel Spiewak
fonte
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 Livre
fonte
5

Com Scala 2.10.4+ (e possivelmente anterior), é possível converter implicitamente java.util.Iterator [A] em scala.collection.Iterator [A] importando scala.collection.JavaConversions.asScalaIterator. Aqui está um exemplo:

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
fonte
4

Você pode converter a coleção Java em uma matriz e usar isso:

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

Ou converta a matriz em uma lista Scala:

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

Se você estiver iterando por meio de um grande conjunto de dados, provavelmente não deseja carregar a coleção inteira na memória com .asScalaconversão implícita. Neste caso, uma abordagem de forma prática é implementar o scala.collection.Iteratortraço

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

Tem um conceito semelhante, mas menos prolixo da IMO :)

Max
fonte
2

Se desejar evitar os implícitos em scala.collection.JavaConversions, você pode usar scala.collection.JavaConverters para converter explicitamente.

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

Observe o uso do asScalamétodo para converter o Java Iteratorem um Scala Iterator.

Os JavaConverters estão disponíveis desde Scala 2.8.1.

quilo
fonte
Usei import scala.collection.JavaConverters._ e, em seguida, javaList.iterator().asScala do seu exemplo e funcionou
kosiara - Bartosz Kosarzycki