3

定义以下内容后:

abstract class A {
  type T
  def print(p: T) = println(p.toString)
}

trait B extends A {
  type T <: String
}

正如预期的那样,我们不能创建一个对象T = Int

scala> val a = new A with B {type T = Int}
<console>:9: error: overriding type T in trait B with bounds >: Nothing <: String;
 type T has incompatible type
       val a = new A with B {type T = Int}
                                  ^

正如预期的那样,我们可以创建一个对象T = String

scala> val a = new A with B {type T = String}
a: A with B{type T = String} = $anon$1@692dec

scala> a.print("test")
test

在将我们的值a转换为 type之后,我们在调用该方法A with B时得到一个错误。print似乎类型字段T丢失了有关类型(?)的信息。

scala> val b = a.asInstanceOf[A with B]
b: A with B = $anon$1@1927275

scala> b.print("test")
<console>:15: error: type mismatch;
 found   : java.lang.String("test")
 required: b.T
              b.print("test")
                      ^

问题一:为什么在T强制转换后类型字段的信息会丢失?

好的,所以我们再次尝试使用强制类型转换将类型字段显式设置为T类型String

scala> val c = a.asInstanceOf[A with B {type T = String}]
c: A with B{type T = String} = $anon$1@1927275

scala> c.print("test")
test

好的,这行得通 - 很好。

现在让我们尝试一些疯狂的事情:

scala> val d = a.asInstanceOf[A with B {type T = Int}]
d: A with T{type T = Int} = $anon$1@1927275

scala> d.print(3)
3

问题2:嗯?TraitB将类型限制T为 String 的子类型,但现在该print方法适用于整数。为什么这行得通?

4

2 回答 2

5

问题 1—— “在使用 B 将我们的值 a 转换为类型 A 后,调用 print 方法时出现错误。” T演员表之后有什么信息?这正是其中的内容B

type T <: String

因此类型是未知的,只是它的上限。下面显示了为什么禁止print调用:A with B

trait X
trait Y extends X { def hallo() = () }

trait A {
  type T
  def test(t: T) = ()
}

trait B extends A {
  type T <: X
}

val y = new A with B { type T = Y; override def test(t: T): Unit = t.hallo() }
y.test(new X {})     // refused -- rightfully
y.test(new Y {})     // ok

val yc = y: A with B // note how we can cast type safe this way!
yc.test(new X {})    // refused -- rightfully (would try to call inexistent 'hallo')

因此,出现在逆变(方法参数)位置的类型会发生什么问题是一个问题。如果您B通过缩小下限来定义,即,即使没有固定type T >: X也可以调用。testT


问题 2——当然可以。您可以使编译器允许使用类型转换进行任何调用。转换为之后A with B {type T = Int},您强制编译器接受它T = Int。现在toString您调用的方法是为 定义的java.lang.Object,并且由于 的​​通用结构A,您Int被装箱为 a java.lang.Integer,因此在调用时您不会看到任何运行时问题toString

但是认为你在这里做正确的事情是错误的。例如:

abstract class A {
  type T
  def print(p: T) = println(p.toString)
}

trait B extends A {
  type T <: String
  override def print(p: T) = println(p.toUpperCase) // !
}

val a = new A with B { type T = String }
val b = a.asInstanceOf[A with B { type T = Int }]
b.print(33)  // bang -- runtime class cast exception
于 2012-07-01T23:54:41.397 回答
2

问题1:为什么类型字段T的信息在转换后丢失了?

因为你为什么要做演员?演员表的意思是:嘿编译器,我是万能的程序员。我想做一些你不明白的事情。做到这一点 - 没有矛盾!

这就是编译器所做的——它抹去了他所有的知识,因为这就是你告诉他的。

类型成员是一些编译时间信息,如果你用你的演员删除它,你也会删除编译器的知识。

问题 2:[...] 为什么这行得通?

因为你是无所不能的程序员,编译器服从。它没有机会证明你告诉他的是否正确。

好吧,最后一句话并不完全正确,因为我们的智能编译器知道程序员永远不可能是无所不能的——即使人们相信这一点。因此它并不总是信任程序员。它玩自己的游戏,遵循自己的规则,以保护程序员。但即使是我们的编译器也不是万能的。有时它必须相信程序员的指令——就像大多数演员一样。

于 2012-07-01T23:48:10.827 回答