【问题标题】:General Questions about Akka and Typesafety关于 Akka 和类型安全的一般问题
【发布时间】:2017-01-27 23:27:09
【问题描述】:

问题 1:

JVM 不知道泛型,因此 Scala(和 Java)中的类型参数仅在编译时存在。它们在运行时不存在。由于 Akka 是一个 Scala(和 Java)框架,它也有这个缺点。它尤其受此影响,因为在 Akka 中,actor 之间的消息(显然)仅在运行时交换,因此这些消息的所有类型参数都丢失了。到目前为止正确吗?

问题 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会设置成什么? Any?如果是这样,以下函数是否有效地等效于上述函数(假设它只会像上面描述的那样被调用):

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 打印到控制台。所以类型信息毕竟没有被删除?我很困惑。

【问题讨论】:

    标签: java scala jvm akka type-erasure


    【解决方案1】:

    问题 1

    将类型擦除称为“缺点”似乎有点像在乞求问题,但无论如何,这一段对我来说听起来相当合理,也许对清单和类标签以及“存在”的含义有些狡辩。 :)

    问题 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 中在运行时调度类型!

    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,但它仍然是一个合理且一致的选择)。事实上,我们有部分类型擦除和损坏的类型大小写匹配。

    【讨论】:

    • 对于结论中的示例,编译器发出此警告:“类型模式 List[Int](List[Int] 的基础)中的非变量类型参数 Int 未选中,因为它已被消除通过擦除”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多