问题 1:
JVM 不知道泛型,因此 Scala(和 Java)中的类型参数只存在于编译时。它们在运行时不存在。由于 Akka 是一个 Scala(和 Java)框架,它也有这个缺点。它尤其受到影响,因为在 Akka 中,参与者之间的消息(显然)仅在运行时交换,因此这些消息的所有类型参数都丢失了。到目前为止正确吗?
问题 2:
假设我定义了以下采用一个类型参数的案例类:
case class Event[T](t: T)
现在,我实例化一个 Event[Int](42)
并将其发送到我的 testActor
。我的 testActor
基本上接收到一个 Event[Any]
并且不知道 t
是什么类型,这是否正确?
问题 3:
比如,在我的 testActor
中,存在一个也接受类型参数的函数:
def f[T](t: T) = println(t)
testActor
在接收到 Event
时调用 f
:
override def receive: Receive = {
case Event(t) => f(t)
}
这样调用函数时,f
的类型参数T
会被设置成什么? 任何
?如果是这样,下面的函数是否有效地等同于上面的函数(假设它只会像上面描述的那样被调用):
def f2(t: Any) = println(t)
问题 4:
现在,考虑 f
的定义:
def f[T](t: T) = println(t.getClass)
我没有更改调用站点:
override def receive: Receive = {
case Event(t) => f(t)
}
这不应该总是向控制台打印 Any
吗?不过,当我将 Event[Int](42)
发送到我的 testActor
时,它确实会向控制台打印 java.lang.Integer
。所以类型信息毕竟没有被删除?我很困惑。
最佳答案
问题一
将类型删除称为“缺点”似乎有点像在回避问题,但不管怎样,这一段对我来说听起来很合理,可能对 list 和类标签以及“存在”的含义有一些质疑。 :)
问题2
不完全是。考虑以下类似的案例类和方法:
case class Foo[T](v: T, f: T => Int)
def doSomething(x: Any): Unit = x match {
case Foo(v, f) => println(f(v))
case _ => println("whatever")
}
这很好用:
scala> doSomething(Foo("hello world", (_: String).size))
11
所以我们不只是将 Foo
视为 Foo[Any]
,因为 (_: String).size
是不是有效的Any => Int
:
scala> val stringSize: Any => Int = (_: String).size
<console>:11: error: type mismatch;
found : String => Int
required: Any => Int
val stringSize: Any => Int = (_: String).size
^
所以编译器知道一些成员的类型。
问题3
调用 f(t)
时推断出的 T
将是某种存在类型,因此不完全是 Any
,但在此案例在道德上等同于它。但是,正如上面的 Foo
案例所示,如果 Event
具有涉及 T
的其他成员或方法,则编译器会知道它是相同的 T
。
问题4
当我们说 JVM 删除类型时,我们实际上只是指“在通用上下文中”。每个对象(在 JVM 意义上)都有一个与之关联的类:
scala> val x: Any = "foo"
x: Any = foo
scala> x.getClass
res0: Class[_] = class java.lang.String
但是……
scala> val y: Any = Seq(1, 2, 3)
y: Any = List(1, 2, 3)
scala> y.getClass
res1: Class[_] = class scala.collection.immutable.$colon$colon
这里有两点需要注意。首先,我们得到的类值是几个子类型关系,甚至比如果我们不使用 : Any
归因(我正在挥手比较类和类型,但你知道我的意思)。其次,由于泛型的类型删除,我们无法从 y.getClass
中获取有关元素类型的任何信息,而只能获取值的“顶级”类。
结论
在我看来,就类型删除而言,这是所有可能世界中最糟糕的一种。当然,您可以在 Scala 中在运行时分派(dispatch)类型!
def foo(x: Any): Unit = x match {
case s: String => println(s"I got a string: $s")
case d: Double => println("numbers suck!")
case xs: List[Int] => println(f"first int is ${ xs.head }%d")
case _ => println("something else")
}
然后:
scala> foo("bar")
I got a string: bar
scala> foo(List(1, 2, 3))
first int is 1
然后:
scala> foo(List(true, false))
java.lang.ClassCastException: java.lang.Boolean cannot be cast to java.lang.Integer
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
at .foo(<console>:15)
... 31 elided
我个人更喜欢在运行时完全删除类型(至少在程序员可以看到的范围内)并且根本没有类型大小写匹配。或者我们可以使用 .NET 风格的具体泛型(在这种情况下我可能不会使用 Scala,但它仍然是一个合理且一致的选择)。事实上,我们有部分类型删除和损坏的类型大小写匹配。
关于java - 关于 Akka 和类型安全的一般问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41904658/