如何使用对象的类型参数化类以在Scala中获取它的实例?

时间:2016-11-07 00:33:50

标签: scala generics parameterization

我想用一个对象类型参数化一个类,以使我的代码更通用。通过这样做,我不需要为扩展某个特征的所有对象实现。

我有以下代码来演示我的目标:

abstract trait Foo {
  def greet
}

object Coo extends Foo {
  def greet = println("Coo!")
}

object Boo extends Foo {
  def greet = println("Boo!")
}

class A[Fooer <: Foo] {
  def moo = asInstanceOf[Fooer].greet()
}

object Main extends App {
  new A[Coo.type].moo() // hopefully prints "Coo!"
}

代码抛出异常:

java.lang.ClassCastException: A cannot be cast to Foo

我相信这是因为asInstanceOf调用似乎隐含地使用this

基本上我的问题是:如何通过参数化在类中获取对象的实例?

1 个答案:

答案 0 :(得分:4)

  

我相信这是因为asInstanceOf调用似乎隐含地使用了this

Scala中的每个类都扩展了Any,它继承了特殊方法asInstanceOf等等。我不会说它发生隐式地 - 它只是你正在调用该类的成员方法,而该类正在调用它。与您撰写def moo = greet相同。

此外,只要您使用asInstanceOf,就会要求编译器撒谎并继续前进。这通常会导致ClassCastException。在这种情况下,它是因为您尝试将实例A强制转换为Foo的某个未知子类型。这永远不会奏效。在特定情况下,您尝试将单件类型Coo.type传递给A的实例,然后创建Coo.type实例。虽然还有许多其他原因导致其无法正常工作,但其中一个原因是您无法创建单例类型的第二个实例 - 因为某种原因,它被称之为。

更一般地说,你不知道你可以简单地构造给定类型的对象。类型没有构造函数,具有。

这可能是一个简化示例的副作用,但您关注的是使用某个类型参数创建A的实例,而无需将其传递给实际对象,但是您需要对象#39;关注已经存在。那么为什么不通过它们呢?

class A[Fooer <: Foo](f: Fooer) {
  def moo = f.greet
}

唯一的另一种方法是提供可以构建Fooer的证据。但是,没有 type 约束来证明某些东西是可构造的类。您可以做的是改为使用Class元对象:

class A[Fooer <: Foo](clazz: Class[Fooer]) {
  def moo = clazz.newInstance.greet
}
然而,这是粗糙的。首先,它不适用于上面的单例类型(因为它们不能再次构建)。第二,我们只能在没有提供构造函数参数的情况下调用newInstance,并且无法强制执行。

所以虽然这会奏效:

scala> class Bar extends Foo { def greet = print("Bar") }

scala> new A(classOf[Bar]).moo
Bar

以下不会:

scala> class Baz(i: Int) extends Foo { def greet = println("Baz") }
defined class Baz

scala> new A(classOf[Baz]).moo
java.lang.InstantiationException: Baz

scala> new A(classOf[Coo.type]).moo
<console>:14: error: class type required but Coo.type found
       new A(classOf[Coo.type]).moo
                        ^

或者,您可以使用允许您一致地构建Foo实例的类型类,但这需要为每个子类创建一个。 e.g:

trait FooBuilder[A] {
    def build(): A
}

implicit object BarBuilder extends FooBuilder[Bar] {
    def build() = new Bar
}

class A[Fooer <: Foo](implicit fb: FooBuilder[Fooer]) {
    def moo = fb.build().greet
}