Pergunta

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]
    }
}
Foi útil?

Solução

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!

Outras dicas

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 TypeTags, 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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top