Universal/generic boxing from Any to AnyRef
Asked Answered
A

1

6

Is it possible to force runtime boxing in scala dynamically? I would like a function:

    def box(value :Any) :AnyRef

or

    def box[T](value :T) :AnyRef

I have a generic class which may be parameterized with AnyVals but need to pass them to legacy java methods accepting collections of objects. Of course, I could implement it myself with pattern matching, but it's a bit annoying to have to do it time and time again, and it wouldn't work for user value classes.

Edit

The answer turned out as simple as surprising. Now, can I do it through reflection? Assume

class Box[T :TypeTag](private var value :T) {
    def get :T = value
    def set(o :Any) {
        ...
    }
}

I would like to do a safe set, checking in the runtime if o is a subclass of T like this:

runtimeMirror(getClass.getClassLoader).classSymbol(o.getClass).toType <:< typeOf[T]

Unfortunately, typeOf[T] for Box[Long] will be a primitive, and the following check will fail on java.lang.Long, which is the runtime type of elements of Seq[Long] for example. Bottom line, when using generics with AnyVals, the compiler sometimes boxes them making runtime class checks unpredictible.

Actinology answered 15/7, 2014 at 11:27 Comment(1)
Your added part doesn't seem to be about boxing from Any to AnyRef. (Looks like it has more to do with unboxing?) How about making it a second, separate question?Reade
R
7

Just cast to AnyRef with asInstanceOf and Scala will turn it into an AnyRef:

scala> val x = 13
x: Int = 13

scala> val xBoxed = x.asInstanceOf[AnyRef]
xBoxed: AnyRef = 13

scala> xBoxed.getClass()
res0: Class[_ <: AnyRef] = class java.lang.Integer

For value classes, this will box it in an instance of the value class, instead of the Java class. You could use a generic trait to be able to get the Java boxed versions from your value classes, without using reflection. For example:

trait ValueClass[T] extends Any {
  def value: T
  def javaBoxed: AnyRef = value.asInstanceOf[AnyRef]
}

case class MyInt(value: Int) extends AnyVal with ValueClass[Int]

case class MyReal(asDouble: Double) extends AnyVal with ValueClass[Double] {
  def value = asDouble
}

However, this requires you mix your trait into all your value classes. For value classes that extend Product, like all case classes do, there's a quicker way using productElement:

def javaBox(x: Product): AnyRef = {
  if (x.productArity == 1) x.productElement(0).asInstanceOf[AnyRef]
  else x.asInstanceOf[AnyRef] // or throw an exception if you prefer
}

Unfortunately, it looks to me like both methods (mix-in trait and Product) cause Scala to do its boxing, meaning when called on an unboxed value, the value goes from unboxed → Scala boxed → unboxed → Java boxed, instead of straight to the Java boxed value.

Reade answered 15/7, 2014 at 12:13 Comment(1)
Awesome, thanks! It is quite amusing though, as 42.asInstanceOf[AnyVal] will produce a compiler warning saying it's always false, but will evaluate to true. Also, it doesn't seem to work for user value classes. I added a bonus question to the original question for additional credit.Actinology

© 2022 - 2024 — McMap. All rights reserved.