如何在Scala中实例化由类型参数表示的类型实例
Posted
技术标签:
【中文标题】如何在Scala中实例化由类型参数表示的类型实例【英文标题】:How to instantiate an instance of type represented by type parameter in Scala 【发布时间】:2009-08-20 11:34:58 【问题描述】:示例:
import scala.actors._
import Actor._
class BalanceActor[T <: Actor] extends Actor
val workers: Int = 10
private lazy val actors = new Array[T](workers)
override def start() =
for (i <- 0 to (workers - 1))
// error below: classtype required but T found
actors(i) = new T
actors(i).start
super.start()
// error below: method mailboxSize cannot be accessed in T
def workerMailboxSizes: List[Int] = (actors map (_.mailboxSize)).toList
.
.
.
请注意,第二个错误表明它知道参与者项是“T”,但不知道“T”是参与者的子类,如类通用定义中所限制的那样。
如何更正此代码以使其正常工作(使用 Scala 2.8)?
【问题讨论】:
...忘了提一下,我为此使用了 Eclipse Scala 插件(每晚 2.8)... 尽管按照您的建议使用了传入的 fac() 函数,但仍然收到“无法在 T 中访问方法邮箱大小”的错误。我对这个结果感到惊讶,因为编译器知道 T 是 <: actor .mailboxsize balanceactor eclipse scalac> 感谢 oxbow_lakes 和 Walter Chang 为实例化问题提供了不同但可行的解决方案。 【参考方案1】:编辑 - 抱歉,我刚刚注意到您的第一个错误。无法在运行时实例化 T
,因为编译程序时类型信息会丢失(通过类型 erasure)
你必须通过一些工厂才能完成建造:
class BalanceActor[T <: Actor](val fac: () => T) extends Actor
val workers: Int = 10
private lazy val actors = new Array[T](workers)
override def start() =
for (i <- 0 to (workers - 1))
actors(i) = fac() //use the factory method to instantiate a T
actors(i).start
super.start()
这可能与某些演员 CalcActor
一起使用,如下所示:
val ba = new BalanceActor[CalcActor]( () => new CalcActor )
ba.start
顺便说一句:您可以使用until
代替to
:
val size = 10
0 until size //is equivalent to:
0 to (size -1)
【讨论】:
尝试了您关于类型规范的建议,但错误并没有因此而改变 抱歉 - 改变了我的答案 - 我只看到了第二个错误,没有注意到new T
行
感谢您的建议。工厂的存在如何帮助解决调用特定于 Actor 子类的方法的错误(例如示例中显示的 mailboxSize)?感谢您对“直到”的提醒,但很难打破 C 和 Java 几十年来的习惯...... ;-)
第二个错误是错误的 - 修复第一个问题,它就会消失。我再次编辑了我的答案
当我说 "erroneous" 时,我的意思是存在错误,因为您的 BalanceActor
类实际上并未使用其泛型类型信息正确编译。【参考方案2】:
使用清单:
class Foo[A](a: A)(implicit m: scala.reflect.Manifest[A])
def create: A = m.erasure.newInstance.asInstanceOf[A]
class Bar
var bar1 = new Bar // prints "bar1: Bar = Bar@321ea24" in console
val foo = new Foo[Bar](bar1)
val bar2 = foo.create // prints "bar2: Bar = Bar@6ef7cbcc" in console
bar2.isInstanceOf[Bar] // prints "Boolean = true" in console
顺便说一句,Manifest 在 2.7.X 中没有记录,因此请谨慎使用。同样的代码也适用于 2.8.0 nightly。
【讨论】:
你的方法和 oxbow_lakes' 一样有效,用于解决创建 A 的新实例的问题(T 是我的原始示例)但是......错误“方法mailboxSize 无法在A 中访问”遗迹。知道为什么吗? @Paul 这也让我感到困惑。 “self.mailboxSize”在 2.7.5 REPL 中成功执行,但在 2.8.0 中引发“无法在 scala.actors.Actor 中访问方法 mailboxSize”错误。 "def workerMailboxSizes" 在 2.7.5 中也可以正常编译。 @Paul 在查看 Actor 主干上的当前源代码后,我认为他们已经从 trait Actor 中删除了“mailboxSize”。您将需要在对象 Actor 中使用“mailboxSize”。但这可能不是您想要的,因为它只返回“self”的邮箱大小。 @Walter Chang:感谢您检查...。我只查看了 2.8 的 javadocs(在 scaladocs.jcraft.com/2.8.0 上),它没有显示 def 邮箱大小已删除,所以我希望它仍然像 2.7.x 一样存在 由于 m.erasure 现在已弃用,您应该使用 m.runtimeClass.newInstance().asInstanceOf[D].get.toString【参考方案3】:现在有一种适当且更安全的方法来执行此操作。 Scala 2.10 引入了 TypeTags,它实际上使我们能够克服使用泛型类型时的擦除问题。
现在可以将您的类参数化如下:
class BalanceActor[T <: Actor :ClassTag](fac: () => T) extends Actor
val actors = Array.fill[T](10)(fac())
通过这样做,我们需要一个隐式 ClassTag[T] 在类被实例化时可用。编译器将确保是这种情况,并将生成将 ClassTag[T] 传递给类构造函数的代码。 ClassTag[T] 将包含关于 T 的所有类型信息,因此编译器在编译时(预擦除)可用的相同信息现在也将在运行时可用,使我们能够构造一个数组[T]。
请注意,仍然无法做到:
class BalanceActor[T <: Actor :ClassTag] extends Actor
val actors = Array.fill[T](10)(new T())
这不起作用的原因是编译器无法知道类 T 是否具有无参数构造函数。
【讨论】:
与接受的答案相比,这有什么优势?您的代码示例并不清楚。 对不起,我的例子不是很好——我现在已经改进了。接受的答案实际上没有编译。new Array[T](workers)
不起作用,因为类型 T 在运行时未知。 Manifest 的第二个答案更好,但 TypeTags 已取代 Manifest。
顺便说一句,这个解决方案比 Manifest 更好的原因是使用 Manifest 是不安全的 - 它假设 T 有一个无参数构造函数,但情况可能并非如此。
@scaling_out 这可能是公认的答案 - 自 09 年以来事情一直在发展,我认为这是 16 年提出问题的人们的最佳发现。
如果类采用构造函数参数,你将如何修改它?【参考方案4】:
如前所述,由于擦除,您不能实例化 T
。在运行时,没有T
。这不像 C++ 的模板,替换发生在编译时,并且实际上编译了多个类,对于实际使用中的每个变体。
清单解决方案很有趣,但假设T
有一个不需要参数的构造函数。你不能假设。
至于第二个问题,方法mailboxSize
是受保护的,所以你不能在另一个对象上调用它。 更新:这仅适用于 Scala 2.8。
【讨论】:
以上是关于如何在Scala中实例化由类型参数表示的类型实例的主要内容,如果未能解决你的问题,请参考以下文章
如何绕过 Scala 上的类型擦除?或者,为啥我不能获取我的集合的类型参数?