Reputation: 13686
Converting from java.util.ArrayList
to scala.collection.immutable.List
, the 2.10 compiler and run-time may seem to be in disagreement, about the type of val emits
:
import org.ahocorasick.trie._
import scala.collection.JavaConverters._ // convert Java colllections to Scala ones
object wierd {
val trie = new Trie
def trieInit(patterns: List[String]) {
trie.onlyWholeWords();
for (pattern <- patterns)
trie.addKeyword(pattern)
}
def patternTest(text : String) : List[String] =
{
val emitsJ = trie.parseText(text)
val emits = emitsJ.asScala map (i => i.getKeyword)
println(s"converted from ${emitsJ.getClass} to ${emits.getClass}")
//return(emits)
return (List.empty[String])
}
trieInit(List("hello"))
patternTest("hello")
}
Yields:
converted from class java.util.ArrayList to class scala.collection.immutable.$colon$colon
Now changing to return the real value by changing only the return
line -
import org.ahocorasick.trie._
import scala.collection.JavaConverters._ // convert Java colllections to Scala ones
object wierd {
val trie = new Trie
def trieInit(patterns: List[String]) {
trie.onlyWholeWords();
for (pattern <- patterns)
trie.addKeyword(pattern)
}
def patternTest(text : String) : List[String] =
{
val emitsJ = trie.parseText(text)
val emits = emitsJ.asScala map (i => i.getKeyword)
println(s"converted from ${emitsJ.getClass} to ${emits.getClass}")
return(emits)
//return (List.empty[String])
}
trieInit(List("hello"))
patternTest("hello")
}
Yields a compilation error:
[error] reproduce.scala:23: type mismatch;
[error] found : Iterable[String]
[error] required: List[String]
[error] return(emits)
[error] ^
[error] one error found
[error] (compile:compile) Compilation failed
What would be the simple explanation for this? How would I better approach the conversion?
Upvotes: 3
Views: 234
Reputation: 11366
the return type of trie.parseText
is declared as java.util.Collection[Emit]
. This isn't very specific, there are lots of possible subtypes of Collection
, they aren't specifying what specific type they are going to return, it could be a TreeSet
, it could be a Vector
, it could be an ArrayList
. But as far as the compiler is concerned, it could be anything which is a sub-type of Collection
. You have inspected it at run-time and seen that for some particular input, it happened to return an ArrayList
, but there is no way the compiler could know this.
When you call .asScala on this, you are using this implicit definition from JavaConverters
implicit def iterableAsScalaIterableConverter[A](i: java.lang.Iterable[A]): convert.Decorators.AsScala[Iterable[A]]
Which converts a java.lang.Itaerable into a scala.collection.Iterable.
There is no converter for Collection
so you get a converter for the next most specific thing, Iterable
. you call map on this Iterable
and get an Iterable
back.
Now, just like you have inspected the runtime value of the Collection
returned from parseText and seen that it was a ArrayList
you have inspected the value returned from this map operation and have seen that it is a scala.collection.immutable.List
, but again, the compiler can't know this, all that it can know is that you gotten something which is a subclass of Iterable
back.
Simply calling .toList on the resulting Iterable
should be all you need to do.
Upvotes: 1
Reputation: 13959
So what's happening here is that the underlying object returned to you from asScala
is a List
but it's been downcast to an Iterable
since List <: Iterable
this is all fine until you want to use the Iterable
as a list. The easiest option is to call toList
on it.
For a little more detail you can browse the source:
case class JCollectionWrapper[A](underlying: ju.Collection[A]) extends AbstractIterable[A] with Iterable[A] {
def iterator = underlying.iterator
override def size = underlying.size
override def isEmpty = underlying.isEmpty
def newBuilder[B] = new mutable.ArrayBuffer[B]
}
So your call to asScala
gives you back this wrapper. Next, your call to map is using this CanBuildFrom
which is then used in this map operation, which finally gives you the result which happens to be a List
because the builder is a ListBuffer
and the result
is a List
, which again happens to be an Iterable
because the can build from pattern downcasts to Iterable
. Hope that explains everything :)
Upvotes: 1
Reputation: 4182
JavaConverters convert to the Scala collection closest to the Java collection that it has pimped. You still have to call toList to further transform it to the collection you want:
val emits = emitsJ.asScala.toList map (i => i.getKeyword)
See related: What is the difference between JavaConverters and JavaConversions in Scala?
Upvotes: 1