如何在Scala中实例化由类型参数表示的类型的实例

46
例子:
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夜间版)... - scaling_out
尽管按照您的建议使用传递的fac()函数,仍然在“方法mailboxSize无法在T中访问”上出现错误。我对此结果感到惊讶,因为编译器知道T是<:Actor,并且Actor确实具有.mailboxSize(在同一BalanceActor类中访问,如所示)。我想知道这是否是我正在使用的特定版本2.8夜间版中的错误???正如您自己所述,对.mailboxSize的访问不应该编译吗?您是否已经在2.7.5.final Eclipse插件或独立的scalac编译中获得了类似的工作? - scaling_out
感谢oxbow_lakes和Walter Chang为实例化问题提供了不同但都可行的解决方案。 - scaling_out
4个回答

30

编辑 - 抱歉,我刚刚才注意到你的第一个错误。在运行时无法实例化 T,因为类型信息在程序编译时丢失(通过类型擦除)。

您需要传入一些工厂来实现构建:

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)

尝试了您关于类型规范的建议,但错误并没有因此改变。 - scaling_out
抱歉 - 我改变了我的答案 - 我只看到了第二个错误,没有注意到 new T 行。 - oxbow_lakes
感谢您的建议。 工厂的存在如何帮助调用Actor子类特定方法(例如示例中显示的mailboxSize)时出现的错误?感谢您提醒“until”的使用,但是从C和Java的几十年习惯中摆脱很难...;-) - scaling_out
第二个错误是错误的 - 修复第一个问题,它就会消失。我又编辑了我的答案。 - oxbow_lakes
当我说“错误”的时候,我只是指出错误之所以存在是因为你的BalanceActor类没有正确地编译其泛型类型信息。 - oxbow_lakes

16
使用清单文件:
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夜间版中也可以工作。


@Paul 我也感到困惑。在2.7.5的REPL中,“self.mailboxSize”可以成功执行,但在2.8.0中会引发“无法访问scala.actors.Actor中的方法mailboxSize”的错误。而“def workerMailboxSizes”在2.7.5中也可以编译通过。 - Walter Chang
@Paul 在查看了Actor主干上的当前源代码后,我认为他们已经从trait Actor中删除了"mailboxSize"。你需要在对象Actor中使用"mailboxSize"。但这可能不是你想要的,因为它只返回"self"的邮箱大小。 - Walter Chang
@Walter Chang:谢谢您检查这个问题... 我只看了2.8的javadocs(在scaladocs.jcraft.com/2.8.0),其中并没有显示mailboxSize被删除的def,因此我认为它仍然像2.7.x版本中一样存在。 - scaling_out
@Paul,抱歉造成困惑。在第17883次修订中,我们将mailboxSize的访问级别从public更改为protected[actors]。 - Walter Chang
2
由于m.erasure现在已被弃用,您应该使用m.runtimeClass.newInstance().asInstanceOf[D].get.toString - Jirka Helmich
显示剩余2条评论

14
现在有一种适当且更安全的方法来做这件事。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的所有类型信息,因此在编译时(预擦除)可用的相同信息现在也可以在运行时使用,使我们能够构造Array[T]。
请注意,仍然不可能做到:
class BalanceActor[T <: Actor :ClassTag] extends Actor {
    val actors = Array.fill[T](10)(new T())
}

这样做不起作用的原因是编译器无法知道类T是否有一个无参构造函数。

这种方法与被接受的答案相比有什么优势?您的代码示例并没有很明确地表达这一点。 - 2rs2ts
抱歉,我的示例不太好 - 我现在已经改进了它。被接受的答案实际上无法编译。new Array[T](workers) 不起作用,因为类型 T 在运行时是未知的。第二个带有 Manifest 的答案更好,但 TypeTags 已经取代了 Manifest。 - Josh
顺便提一下,这个解决方案比 Manifest 更好的原因在于使用 Manifest 是不安全的 - 它假定 T 有一个无参构造函数,但实际上可能并非如此。 - Josh
2
@scaling_out 这可能是被接受的答案 - 自从'09年以来,事情已经发展了,我认为这是对于在'16年来到这个问题的人们最好的发现。 - akauppi
如果这个类需要构造函数参数,你会如何修改它? - Nick Resnick

2

如前所述,由于类型擦除的原因,您无法实例化T。在运行时,不存在T。这与C++的模板不同,其中替换发生在编译时,并且对于每个实际使用的变量,实际上编译了多个类。

显式解决方案很有趣,但假设T有一个不需要参数的构造函数。您不能假定这一点。

至于第二个问题,方法mailboxSize受保护,因此您无法在另一个对象上调用它。更新:这仅适用于Scala 2.8。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接