Reputation: 75
I have two classes, Holders (for lack of a better name at the moment) and Holder. Holder has to be interfaced through Holders, which has an array of Holder of any type. As such, it has to take Any type. I want to have the setValue do type checking that the Any input is indeed of type T. I've read a bit about using manifests, but I'm getting somewhat lost. Is there any way to do what I want?
class Holders {
var values = Array[Any]()
var _holders = Array[Holder[_]]()
def setData(index: Int, newValue: Any) {
values(index) = newValue
_holders(index).setValue(newValue)
}
}
class Holder[T](someData: String, initValue: T) {
private var value : T = initValue
def getValue : T = value
def setValue(newValue: Any)(implicit m: Manifest[T]) = {
if (newValue.isInstanceOf[T])
value = newValue.asInstanceOf[T]
}
}
Upvotes: 2
Views: 886
Reputation: 49705
You can.
note: Manifest
is deprecated and replaced with TypeTag
and ClassTag
, but that doesn't affect the remainder of this answer
Often when wanting Manifests/TypeTags you know exactly what's happening at compile time but want that information preserved through to runtime as well. In this case you also have to deal with the fact that, even at compile time, _holders(index)
can't tell you what kind of Holder
it's returning.
Depending on how _holders
will be built, it may be possible to replace it with an Heterogeneous Map from the shapeless library, that would do exactly what you need out-of-the-box.
Otherwise, you have the right idea, testing the type at runtime. The trick is using TypeTag
to capture both the underlying type of the holder and the type of the new value.
Note that the TypeTag
context bound has to be specified on all the nested methods so it can be passed down the call stack in implicit scope. Presence of the TypeTag
is what allows typeOf
to then work.
import scala.reflect.runtime.universe._ //for TypeTag
class Holders {
var values = Array[Any]()
var _holders = Array[Holder[_]]()
def setData[V: TypeTag](index: Int, newValue: V): Unit = {
values(index) = newValue
_holders(index).setValue(newValue)
}
}
class Holder[T: TypeTag](someData: String, initValue: T) {
private var value: T = initValue
def getValue: T = value
def setValue[V: TypeTag](newValue: V): Unit =
if(typeOf[V] <:< typeOf[T]) {
value = newValue.asInstanceOf[T]
}
Or using Manifest
class Holder[T: Manifest](someData: String, initValue: T) {
private var value: T = initValue
def getValue: T = value
def setValue[V: Manifest](newValue: V): Unit =
if(manifest[V] <:< manifest[T]) {
value = newValue.asInstanceOf[T]
}
I'd strongly urge you to favour TypeTag
though!
Upvotes: 2
Reputation: 3547
Type erasure makes this kind of thing... hard. In a nutshell once your code is compiled, all type parameters are replaced with Any
. To understand the implications, consider the following example:
trait Foo[T] { def isT(a: Any): Boolean = a.isInstanceOf[T] }
object Bar extends Foo[String]
Bar.isT("foo") // true
Bar.isT(42) // also true, as Int <: Any
This will produce a warning when compiled with the appropriate options.
In this scenario you have two options; you can compare TypeTag
s, in which case you hope that the provided type parameters are sufficiently accurate (consider the provided type parameter could be any superclass of value
), or you compare the runtime classes of your values (in which case you are out of luck when dealing with generic types). A TypeTag
-based solution might look something like this:
class Holder[T : TypeTag](someData: String, initValue: T) {
private var value = initValue
def setValue[V : TypeTag](v: V): Unit = {
// Works because there are TypeTags for T and V in implicit scope
if(typeOf[V] <:< typeOf[T])
value = v.asInstanceOf[T]
}
}
Now you are looking at this and saying "well doesn't that mean that the assignment is actually value = v.asInstanceOf[Any]
?" and the answer is yes - value
is also erased to Any
. Casting does nothing, in the sense that v.asInstanceOf[T]
does not mean "convert v
to a T
". Instead what you are doing is saying "oh yeah, v
is totally a T
- honest!", and because the compiler is naive, it believes you.
Upvotes: 2