定义以下内容后:
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
转换为类型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")
^
问题1:为什么有关类型字段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:嗯?特性B
限制类型T
是String的子类型,但现在print
方法适用于整数。为什么这有效?
答案 0 :(得分:5)
问题1 - “将我们的值a转换为带有B的类型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
,即使test
未修复,也可以调用T
。
问题2 - 当然有效。您可以使编译器允许使用类型转换进行任何调用。转换为A with B {type T = Int}
后,强制编译器接受T = Int
。现在,您调用的toString
方法是为java.lang.Object
定义的,由于A
的通用结构,您的Int
被列入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
答案 1 :(得分:2)
问题1:为什么在演员表之后关于类型字段T的信息会丢失?
因为你为什么要演员?演员意味着:嘿编译器,它是我无所不能的程序员。我想做一些你不理解的事情。这样做 - 没有矛盾!
这就是编译器所做的 - 它会删除他所有的知识,因为这就是你告诉他的。
类型成员是一些编译时信息,如果你用你的强制转换删除它,你也删除了编译器的知识。
问题2:[...]为什么这有效?
因为你是无所不能的程序员而且编译器服从。它没有机会证明你告诉他的是否正确。
好吧,最后一句话并不完全正确,因为我们的智能编译器知道程序员永远不会无所不能 - 即使有人相信这一点。因此,它并不总是信任程序员。它会玩自己的游戏并遵循自己的规则来保护程序员。但即使我们的编译器也不是无所不能的。有时它必须信任程序员的指令 - 就像大多数演员一样。