【问题标题】:why AnyVal can be converted into AnyRef at run time in Scala?为什么 AnyVal 可以在 Scala 运行时转换为 AnyRef?
【发布时间】:2014-11-13 21:53:44
【问题描述】:

以下代码可以正常编译。

val a: Int = 1
val b = a.asInstanceOf[AnyRef]

这让我很困惑,因为 Int 扩展了 AnyVal,它不是 AnyRef 的子类而是兄弟。

但是,如果按以下方式使用归属:

val a: Int = 1
val b: AnyRef = a

它不起作用。

error: type mismatch;
 found   : Int
 required: AnyRef
Note: an implicit exists from scala.Int => java.lang.Integer, but
methods inherited from Object are rendered ambiguous.  This is to avoid
a blanket implicit which would convert any scala.Int to any AnyRef.
You may wish to use a type ascription: `x: java.lang.Integer`.
   val b: AnyRef = a

我的理解:

asInstanceOf 在运行时执行,它强制编译器相信 val a 是 AnyRef。 但是,由于在编译时,转换无法通过类型检查,所以出现“类型不匹配”错误。

我的问题:

  1. 基本上,为什么转换在运行时起作用?
  2. 如果 AnyRef 在 JVM 中被视为 java.lang.Object,那么 AnyVal 呢?它是运行时的对象吗?
    • (如果是,它的类型是什么?java.lang.Object ?但是 AnyVal 是 AnyRef 的兄弟,不是吗?)
    • (如果没有,我们如何使用一些AnyVal的子类作为Object,比如Int、Double)
    • scala 编译器有什么技巧吗?

【问题讨论】:

    标签: scala


    【解决方案1】:

    这是因为自动装箱:

    scala>val a: Int = 1
    a: Int = 1
    scala> a.getClass
    res2: Class[Int] = int
    scala> val b = a.asInstanceOf[AnyRef]
    b: AnyRef = 1
    scala> b.getClass
    res1: Class[_ <: AnyRef] = class java.lang.Integer
    

    通过强制转换为 AnyRef (java.lang.Object),您会触发从 int 到 java.lang.Integer 的自动装箱

    如果 AnyRef 在 JVM 中被认为是 java.lang.Object,那么 AnyVal 呢? 它是运行时的对象吗?

    AnyRef 确实是java.lang.Object 的别名 AnyVal 是一个“虚拟”类型,为了类型系统的完整性,它只存在于编译时。

    在运行时,扩展 AnyVal 的实例将转换为相应的本机类型(intdouble 等),但 String 转为 java.lang.String,它本身扩展了 java.lang.Object,但有特殊处理在 JVM 中。

    但是AnyValAnyRef 的兄弟,不是吗?)

    AnyValAnyRef 都扩展了 Any 类型,但它们不相互扩展。

    scala 编译器有什么技巧吗?

    加载:)

    有关 Scala 类型层次结构的更完整解释,我建议您先阅读:http://docs.scala-lang.org/tutorials/tour/unified-types.html

    【讨论】:

    • 小修正:JVM不知道自动装箱,这需要在运行时实现。它的所有 Scala 编译器技巧。
    • 您的意思是自动装箱实际上是在字节码中“手动”触发的。我也学到了一些东西:)
    • 是的,JVM 完全没有装箱或拆箱的概念。这完全是在 Java 语言级别上实现的。
    【解决方案2】:

    补充“因为自动装箱”,可以观察使用了什么魔法。

    -Xprint:all 将显示哪个编译器阶段发挥了作用。

    -Ytyper-debug 显示了 typer 做出的决定。

    例如,给定val a: Int,则val b = a.isInstanceOf[AnyRef]在擦除时更改为Int.box(a).$asInstanceOf[Object],其中$asInstanceOfObject的名义成员。

    关于这些转换有一个很好的评论in erasure

    /**  Replace member references as follows:
     *
     *   - `x == y` for == in class Any becomes `x equals y` with equals in class Object.
     *   - `x != y` for != in class Any becomes `!(x equals y)` with equals in class Object.
     *   - x.asInstanceOf[T] becomes x.$asInstanceOf[T]
     *   - x.isInstanceOf[T] becomes x.$isInstanceOf[T]
     *   - x.isInstanceOf[ErasedValueType(tref)] becomes x.isInstanceOf[tref.sym.tpe]
     *   - x.m where m is some other member of Any becomes x.m where m is a member of class Object.
     *   - x.m where x has unboxed value type T and m is not a directly translated member of T becomes T.box(x).m
     *   - x.m where x is a reference type and m is a directly translated member of value type T becomes x.TValue().m
     *   - All forms of x.m where x is a boxed type and m is a member of an unboxed class become
     *     x.m where m is the corresponding member of the boxed class.
     */
    

    相比之下,由于 OP 错误消息中提到的归属而发生的转换是因为 Predef 中的隐含:

    scala> val I: java.lang.Integer = a
    [[syntax trees at end of                     typer]] // <console>
    
    private[this] val I: Integer = scala.this.Predef.int2Integer($line3.$read.$iw.$iw.a);
    

    【讨论】:

    • 是否有此类命令的列表显示编译器输出/调试?如果您不知道具体要寻找什么,就很难找到它们。
    • @EndeNeu scalac -Xscalac -Y。我认为即使您知道要寻找什么,也很难找到它们。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-25
    • 2011-03-31
    • 2013-08-31
    • 2019-12-15
    • 2019-06-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多