Hey folks, I’m returning to a task with some Scala+Java interop for the first time in 10 years (!!). I’ve solved this problem before, but that knowledge is locked inside a firewall… a few firewalls ago… and I can’t seem to find a public solution here, on StackOverflow, etc. I think I’m just not wording it right in my search terms.
I’m working in Scala 2.12.20 and Java 8—hard requirements.
I’ve got a Scala class that looks like this:
class RankAggregator(
val minNumRecs: Int = 10,
val method: String = "median",
val weights: List[Double] = Nil // regular Scala list
)
I need to instantiate it somehow from some Java code. Now, I know I’d have to use JavaConverters to convert a java.util.ArrayList
to the scala.collection.immutable.List
. But, it seems like the type of the collection is getting erased, such that the Java side complains that the constructor needs a List[Object]
instead of a List[Double]
.
List<Double> weights = new ArrayList<>();
// fails, unconverted from java to Scala, duh
RankAggregator aggregator2 = new RankAggregator(50, "median", weights);
// fails, incompatible types:
// scala.collection.immutable.List<java.lang.Double>
// cannot be converted to
// scala.collection.immutable.List<java.lang.Object>
RankAggregator aggregator3 = new RankAggregator(50, "median",
JavaConverters.asScalaBuffer(weights).toList());
I don’t really want to bring Scala into the Java code as a direct dependency and I control the Scala code, too, so I can just provide a nicer interface, right? I remember this from the last time I did this… 10 years ago.
I distinctly remember creating a set of builders/factories to handle the Java consumers of the Scala code we were writing back then, so I did this up real quick:
import scala.collection.JavaConverters._
import java.util.{List => JavaList}
object RankAggregator {
def create(minNumRecs: Int = 10,
method: String = "median",
weights: JavaList[Double]): RankAggregator = {
new RankAggregator(
minNumRecs=minNumRecs,
method=method,
weights=weights.asScala.toList
)
}
However, this is failing, too, with the type incompatibility because the collection’s type is being dropped.
//fails, incompatible types: java.util.List<java.lang.Double>
// cannot be converted to java.util.List<java.lang.Object>
RankAggregator aggregator = RankAggregator.create(50, "median", weights);
Then I indulged in some thrashing with creating a builder that should be unnecessary:
object RankAggregator {
def builder(): Builder = new Builder()
case class Builder(
minNumRecs: Option[Int] = None,
method: Option[String] = None,
weights: Option[List[Double]] = None) {
def minNumRecs(v: Int): Builder = {
this.copy(minNumRecs = Option(v))
}
def method(v: String): Builder = {
this.copy(method = Option(v))
}
def weights(v: JavaList[Double]): Builder = {
this.copy(weights = Option(v).map(_.asScala.toList))
}
def build(): RankAggregator = {
new RankAggregator(
minNumRecs=minNumRecs.get,
method=method.get,
weights=weights.get)
}
}
}
Then to use it from Java:
// fails,
// incompatible types: java.util.List<java.lang.Double>
// cannot be converted to java.util.List<java.lang.Object>
// (and obviously would break at runtime because of the None.get)
RankAggregator aggregator = RankAggregator.builder().weights(weights).build();
I’m stumped.
- I don’t think I need the Manifest stuff here because I’m not creating generic methods.
- I think think I need the TypeTag, etc. either.
Do I need to give up and cast?