Scala:如何在编译时不知道类型的情况下使用类型参数和清单调用方法?

时间:2013-09-19 13:00:48

标签: scala reflection types implicit

我有一个带有以下签名的函数:

myFunc[T <: AnyRef](arg: T)(implicit m: Manifest[T]) = ???

如果在编译时我不知道参数的确切类型,我怎么能调用这个函数?

例如:

val obj: AnyRef = new Foo()    // At compile time obj is defined as AnyRef,
val objClass = obj.getClass    // At runtime I can figure out that it is actually Foo
// Now I would need to call `myFunc[Foo](obj.asInstanceOf[Foo])`,
// but how would I do it without putting [Foo] in the square braces?

我想写一些逻辑上相似的东西:

myFunc[objClass](obj.asInstanceOf[objClass])

谢谢!

更新

问题无效 - 正如@DaoWen,@ Jermo和@itsbruce正确指出的那样,我试图做的事情完全是胡说八道!我只是严厉地推翻了这个问题。 感谢你们!太糟糕了,我不能接受所有答案都是正确的:)

所以,问题是由以下情况引起的:

我正在使用Salat库将对象序列化为BSON / JSON表示形式。 Salat有一个Grater[T]类,用于序列化和反序列化。 从BSON调用反序列化的方法看起来是这样的:

val foo = grater[Foo].asObject(bson)

这里,类型参数的作用是明确的。我试图做的是使用相同的Grater来序列化来自我的域模型的任何实体。所以我写道:

val json = grater[???].toCompactJSON(obj)

我立即赶紧反思,只是没有看到表面上有明显的解决方案。这是:

grater[Entity].toCompactJSON(obj)  // where Entity...

@Salat trait Entity                // is a root of the domain model hierarchy

有时事情比我们认为的要容易得多! :)

4 个答案:

答案 0 :(得分:5)

看来,当我写这个答案时,问题的作者意识到他不需要在运行时解析Manifest。但是,在我看来,当我编写Yaml [de]序列化库时,我成功解决了完全合法的问题,所以我在这里留下答案。


可以使用ClassTag甚至TypeTag来执行您想要的操作。我不知道有关Manifest的问题,因为该API已弃用且我没有使用它,但我相信,由于它们不像新的Scala反射那样复杂,因此更容易显示。仅供参考,Manifest的继任者是TypeTag

假设您具有以下功能:

def useClasstag[T: ClassTag](obj: T) = ...

def useTypetag[T: TypeTag](obj: T) = ...

然后您需要以obj: AnyRef作为参数进行调用,同时为ClassTag类提供TypeTagobj.getClass作为隐式参数。

ClassTag是最简单的。您可以直接从ClassTag实例创建Class[_]

useClasstag(obj)(ClassTag(obj.getClass))

就是这样。

TypeTag更难。您需要使用Scala反射从对象中获取一个,然后您必须使用Scala反射的一些内部。

import scala.reflect.runtime.universe._
import scala.reflect.api
import api.{Universe, TypeCreator}

// Obtain runtime mirror for the class' classloader
val rm = runtimeMirror(obj.getClass.getClassLoader)

// Obtain instance mirror for obj
val im = rm.reflect(obj)

// Get obj's symbol object
val sym = im.symbol

// Get symbol's type signature - that's what you really want!
val tpe = sym.typeSignature

// Now the black magic begins: we create TypeTag manually
// First, make so-called type creator for the type we have just obtained
val tc = new TypeCreator {
  def apply[U <: Universe with Singleton](m: api.Mirror[U]) =
    if (m eq rm) tpe.asInstanceOf[U # Type]
    else throw new IllegalArgumentException(s"Type tag defined in $rm cannot be migrated to other mirrors.")
}
// Next, create a TypeTag using runtime mirror and type creator
val tt = TypeTag[AnyRef](rm, tc)

// Call our method
useTypetag(obj)(tt)

如您所见,这种机器相当复杂。这意味着只有在真正需要它时才应该使用它,并且正如其他人所说的那样,当你真正需要它时的情况非常罕见。

答案 1 :(得分:3)

这不起作用。以这种方式思考:你要求编译器为一个在运行时才知道的类创建一个类Manifest(在编译时!)。

但是,我觉得你以错误的方式接近问题。 AnyRef在编译时对Foo的类型的了解真的最多吗?如果是这样的话,你怎么能用它做任何有用的事情? (除了为AnyRef定义的少数方法之外,您将无法调用任何方法。)

答案 2 :(得分:2)

目前尚不清楚您想要实现的目标,而且更多的背景可能会有所帮助。无论如何,这是我的2美分。

使用Manifest在这里没有帮助,因为类型参数需要在编译时知道。我的建议是这样的:

def myFunc[T](arg: AnyRef, klass: Class[T]) = {
  val obj: T = klass.cast(arg)
  //do something with obj... but what?
}

你可以这样称呼它:

myFunc(obj, Foo.class)

请注意,我不知道如何在myFunc内做一些有用的事情。在编译时,除了AnyRef可用的方法之外,您不能在类型T的对象上调用任何方法。如果你想使用反射来操纵myFunc的参数,那么就没有必要将它转换为特定的类型。

答案 3 :(得分:0)

这是使用类型安全的OO语言的错误方法。如果你需要这样做,你的设计是错误的。

myFunc[T <: AnyRef](arg: T)(implicit m: Manifest[T]) = ???

当然,这可能是无用的,正如您可能已经发现的那样。你可以在一个可能任何的对象上调用什么样的有意义的函数?您不能直接引用其属性或方法。

  

我想写一些逻辑上相似的东西:

myFunc[objClass](obj.asInstanceOf[objClass])

为什么呢?这种事情通常只对非常专业的案件才有必要。您是在编写一个使用依赖注入的框架吗?如果您没有对Scala的功能进行一些高度技术性的扩展,那么这不是必需的。

我打赌你知道 更多关于这个课程的内容,因为你说你不知道完全类型。基于类的OO工作方式的一个重要部分是,如果要对一般类型的对象(包括其所有子类型)执行某些操作,则将该行为放入属于该类的方法中。如果需要,让子类覆盖它。

坦率地说,你正在尝试的方法是在你对类型有足够了解的上下文中调用函数。