Every Java library can be used in Scala, which is, for me, one of the good parts of the JVM world. But Java libs are
mostly object-oriented and not functional, therefore full of side effects and somtimes “ugly” to use in Scala. But
there are some approaches how to make Java libs (or their interfaces) more functional, so they can almost be used like
a Scala lib.
Java 8 Type Conversion
Many Java types like Map
or List
, but also functional types (Java 8) like Optional<T>
have Scala pendents. As this
is one of the really log hanging fruits, convert those types to Scala. All collection types of Java can be converted by
using Scalas own scala.collection.JavaConverters._
. After importing the implicits, one can call .asScala
on every
Java collection:
import java.util
import scala.collection.JavaConverters._
import scala.collection.mutable
val jList: util.ArrayList[String] = new java.util.ArrayList[String]()
val sBuf: mutable.Buffer[String] = jList.asScala
val sList: List[String] = sBuf.toList
The Optional<T>
of Java 8 is, in my humble opinion, a huge benefit, but a bit disfunctional compared to Scalas
Option[T]
. When using Java interfaces that return optionals, it can also be converted:
import java.util.Optional
implicit class Java8Optional[T](in: Optional[T]){
def asScala: Option[T] = if(in.isPresent) Option(in.get()) else None
}
val jOpt: Optional[String] = Optional.of("foo")
val sOpt: Option[String] = jOpt.asScala
Also, Java 8 functional interfaces, like java.util.Function<A,B>
can be converted:
implicit class Java8Function[A, B](in: java.util.function.Function[A, B]) {
def asScala: A => B = (a: A) => in.apply(a)
}
val jFunc: java.util.function.Function[String, String] = ???
val sFunc: String => String = jFunc.asScala
Note: Despite those conversions work, they are only meant for demonstration purposes. Scala has a compatibility library
scala-java8-compat to convert Java to Scala types. Use it.
Implicit Classes as Decorators
As you could see, implicit classes are very handy to decorate Java objects, which you cannot change. This becomes
especially handy when using Java libraries, like, for example
SolrJ.
To simplify object construction (good old Java setters in that case) and minify observable side effects, functional
object builders can be implemented that way. I used this approach, to design a Scala interface for the
org.apache.solr.common.SolrDocument
:
/** Implicit ops for solr documents */
private[search] implicit class SolrDocumentOps(in: SolrDocument) {
def asScala = new ScalaSolrDocument(in)
}
/** Typesafe representation of a solr document
*
* @param underlying The original (wrapped) solr document
*/
private[search] class ScalaSolrDocument(val underlying: SolrDocument)
/** Helper method to add fields to existing result documents **/
def withField(fieldName: String, value: Any): ScalaSolrDocument = {
val doc = new SolrDocument()
underlying.entrySet().asScala.foreach(f => doc.addField(f.getKey, f.getValue))
doc.addField(fieldName, value)
doc.asScala
}
/** Transform function to maintain fluid interface, because document is not monadic */
def transform[T](f: SolrDocument => T) = f(underlying)
}
To prevent confusion of the newly added Scala methods with the existing interface, the only method on the implicit
decorator class is asScala
which returns the custom ScalaSolrDocument
which wraps the original object. An example
to get rid of side effects is the method withField
which constructs a new underlying SolrDocument
every time a field
is modified.
The usage is pretty simple:
val doc = new SolrDocument
val sDoc: ScalaSolrDocument = doc.asScala.
withField("name", "Frank"),
withField("profession", "programmer")
val res: SolrDocument = sDoc.underlying
Implicit Conversion
Another approach to convert between the original SolrDocument
and the custom ScalaSolrDocument
is Scalas
implicit conversions feature.
By providing two implicit values containing the converter functions, the Scala compiler will implicitely convert between
the two types where needed:
import scala.language.implicitConversions
implicit val solrDocToScala = (in: SolrDocument) => new ScalaSolrDocument(in)
implicit val solrDocFromScala = (in: ScalaSolrDocument) => in.underlying
// could be a method of an interface which needs the original document
def save(doc: SolrDocument) = ???
val jDoc = new SolrDocument() // original doc
// implicit conversion to ScalaSolrDocument
val sDoc = jDoc.withField("name", "Frank")
// implicit conversion back to SolrDocument
save(sDoc)
Note: Implicit conversions are considered an advanced language feature, because things can get confusing fast.
Don’t overuse them.