Iterowanie po kolekcjach Java w Scali

113

Piszę trochę kodu Scala, który wykorzystuje API Apache POI . Chciałbym iterować po wierszach zawartych w tym, java.util.Iteratorktóre otrzymałem z klasy Sheet. Chciałbym użyć iteratora w for eachpętli stylów, więc próbowałem przekonwertować go na natywną kolekcję Scala, ale bez powodzenia.

Przyjrzałem się klasom / cechom opakowania Scala, ale nie widzę, jak ich poprawnie używać. Jak iterować kolekcję Java w Scali bez używania szczegółowego while(hasNext()) getNext()stylu pętli?

Oto kod, który napisałem na podstawie poprawnej odpowiedzi:

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
źródło
Wydaje się, że nie mogę dołączyć wiersza „for (val row <- rows) {” bez analizatora składni myślącego, że znak „<” jest znacznikiem zamykającym XML? Lewe klawisze nie działają
BefittingTheorem
Powinieneś być w stanie niejawnie przekonwertować do IteratirWrapper, oszczędzając sporo składni. Google do niejawnych konwersji w Scali.
Daniel Śpiewak

Odpowiedzi:

28

Istnieje klasa opakowująca ( scala.collection.jcl.MutableIterator.Wrapper). Więc jeśli zdefiniujesz

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

wtedy będzie działać jako podklasa iteratora Scala, więc możesz to zrobić foreach.

James
źródło
Powinien brzmieć: scala.collection.jcl.MutableIterator.Wrapper
samg
37
Ta odpowiedź jest przestarzała w Scali 2.8; patrz stackoverflow.com/questions/2708990/…
Alex R
256

Od wersji Scala 2.8 wszystko, co musisz zrobić, to zaimportować obiekt JavaConversions, który już deklaruje odpowiednie konwersje.

import scala.collection.JavaConversions._

To nie zadziała jednak w poprzednich wersjach.

ttonelli
źródło
24

Edycja : Scala 2.13.0 jest przestarzała scala.collection.JavaConverters, więc od 2.13.0 musisz użyćscala.jdk.CollectionConverters .

Scala 2.12.0 jest przestarzała scala.collection.JavaConversions, więc od wersji 2.12.0 jednym ze sposobów byłoby coś takiego:

import scala.collection.JavaConverters._

// ...

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

(zwróć uwagę na import, nowy to JavaConverters, przestarzały to JavaConversions)

Antonone
źródło
2
Szukałem „toScala” ^ _ ^!
Profiterole
15

Poprawną odpowiedzią jest tutaj zdefiniowanie niejawnej konwersji z języka Java na Iteratortyp niestandardowy. Ten typ powinien implementować foreachmetodę, która jest delegowana do bazowego Iterator. Umożliwi to użycie forpętli Scala z dowolną Javą Iterator.

Daniel Śpiewak
źródło
9

Dla Scala 2.10:

// Feature warning if you don't enable implicit conversions...
import scala.language.implicitConversions
import scala.collection.convert.WrapAsScala.enumerationAsScalaIterator
FP Dowolnie
źródło
5

Ze Scala 2.10.4+ (i prawdopodobnie wcześniejszą) można niejawnie przekonwertować java.util.Iterator [A] na scala.collection.Iterator [A], importując scala.collection.JavaConversions.asScalaIterator. Oto przykład:

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
źródło
4

Możesz przekonwertować kolekcję Java na tablicę i użyć tego:

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

Lub przejdź dalej i przekonwertuj tablicę na listę Scala:

val list = List.fromArray(array)
Fabian Steeg
źródło
4

Jeśli przeglądasz duży zestaw danych, prawdopodobnie nie chcesz ładować całej kolekcji do pamięci za pomocą .asScalaniejawnej konwersji. W tym przypadku wygodnym sposobem jest zaimplementowanie scala.collection.Iteratorcechy

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

Ma podobną koncepcję, ale mniej rozwlekłe IMO :)

Maks
źródło
2

Jeśli chcesz uniknąć implikacji w scala.collection.JavaConversions , możesz użyć scala.collection.JavaConverters do jawnej konwersji.

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

Zwróć uwagę na użycie asScalametody do konwersji Java Iteratordo ScalaIterator .

JavaConverters są dostępne od wersji Scala 2.8.1.

kilogram
źródło
Skorzystałem import scala.collection.JavaConverters._ a potem javaList.iterator().asScala z twojego przykładu i zadziałało
kosiara - Bartosz Kosarzycki