Scala如何处理方法返回类型中Any到AnyVal的协变?
好问题!咱们结合你给出的代码和Java的对比情况,来详细说说Scala在这里的处理:
先回顾你给出的代码示例:
class A { def foo: Any = new Object } class B extends A { override def foo: AnyVal = 42 }
先明确Java的限制原因
Java里父类方法返回Object,子类重写时不能返回int这种基本类型——因为JVM的重写规则要求返回类型必须是引用类型的协变子类型,而基本类型和引用类型在JVM层面是完全不同的类型体系,所以只能返回Integer这种包装类型,必然会有装箱操作。
Scala的处理逻辑
Scala的类型系统比Java更灵活,但它最终还是要编译成JVM字节码,所以也受JVM规则的约束:
当通过子类类型引用调用时:
如果你用B类型的引用调用foo,比如:val b: B = new B val result = b.foo // 这里result的类型是Int(编译器自动推断)此时Scala编译器知道实际返回的是
Int值类型,会在调用后自动拆箱,但因为父类A的foo方法在JVM层面返回的是Object,所以子类B的foo方法在内部必须先把42装箱成Integer才能符合父类的方法签名。这一步装箱是受JVM规则限制无法避免的,但Scala编译器会自动帮你处理拆箱,不需要手动写转换代码。当通过父类类型引用调用时:
如果用A类型的引用调用foo,比如:val a: A = new B val result = a.foo // 这里result的类型是Any此时返回的是
Any类型(对应JVM的Object),所以42必须装箱成Integer,而且调用方拿到的是引用类型,如果你要把它当成值类型使用,还需要手动拆箱,这时候的装箱拆箱是完全无法避免的。
有没有办法完全避免装箱/拆箱?
如果想要彻底避免装箱拆箱操作,你需要调整父类的方法签名,让它返回AnyVal而不是Any:
class A { def foo: AnyVal = 0 } class B extends A { override def foo: Int = 42 }
这种情况下,子类B的foo方法在JVM层面可以直接返回int类型,完全不需要装箱拆箱——因为AnyVal在Scala里是值类型的统一特质,编译器会根据实际返回的具体值类型生成对应的基本类型字节码,同时完全符合重写规则。
总结一下:在你给出的原代码场景中,因为父类方法返回Any(对应JVM的Object),子类重写时必须遵循JVM的重写规则,所以无法完全避免装箱操作,但Scala编译器会在子类类型调用时自动处理拆箱,减少你的手动工作量。
内容的提问来源于stack exchange,提问作者adutra

